mybatis源码考究一

mybatis源码考究一

1.项目结构

在这里插入图片描述

2.mybatis使用步骤介绍

  • 获取SqlSessionFactory

    • 这里面就会用到全局配置,本文的con1.xml
  • 使用SqlSessionFactory打开sqlSession

  • 使用sqlSession操作数据库

3.快速入门

1.构建SqlSessionFactory官网介绍

  • 使用xml构建

    static SqlSessionFactory sqlSessionFactory = null;
        static{
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            try {
                sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis/con1.xml"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
  • 不使用xml构建

    static SqlSessionFactory sqlSessionFactory = null;
        static{
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            //构建配置类
            Configuration configuration = new Configuration();
            //数据源
            DataSource dataSource = new PooledDataSource("com.mysql.cj.jdbc.Driver","jdbc:mysql://localhost:3306/no_name","root","root");
            //事务类型
            JdbcTransactionFactory jdbcTransaction = null;
            jdbcTransaction = new JdbcTransactionFactory();
            //新建环境,就是settings里面的配置
            Environment dev = new Environment("dev", jdbcTransaction, dataSource);
            configuration.setEnvironment(dev);
            //添加接口文件
            configuration.addMapper(UserMapper.class);
    
            //这个类的作用暂时研究不多,看名字就知道对拍错有帮助
            ErrorContext.instance().resource("mybatis/UserMapper.xml");
            InputStream inputStream = null;
            try {
                inputStream = Resources.getResourceAsStream("mybatis/UserMapper.xml");
            } catch (IOException e) {
                e.printStackTrace();
            }
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, "mybatis/UserMapper.xml", configuration.getSqlFragments());
            //添加xml,有时候需要结合注解和xml
            mapperParser.parse();
            //添加拦截器
            configuration.addInterceptor(new MyInterceptor1());
            configuration.addInterceptor(new MybatisOptimisticInterceptor());
            //构建
            sqlSessionFactory = sqlSessionFactoryBuilder.build(configuration);
        }
    

2.获取sqlsession

public static SqlSession getSession(){
        return sqlSessionFactory.openSession();
    }

    public static SqlSession getSessionByType(ExecutorType execType){
        return sqlSessionFactory.openSession(execType);
    }

    public static SqlSession getSessionByTypeAndAutoCommit(ExecutorType execType,boolean autoCommit){
        return sqlSessionFactory.openSession(execType,autoCommit);
    }

3.使用案例

/**
     * 新增测试
     * @param user
     * @return
     */
    public int insert(User user){
        SqlSession session = MybatisSessionUtil.getSession();
        int rint = session.insert("user.insertUser",user); // 第一个参数是mapper xml里的namespace+MappedStatement对应的id
        session.commit();// 不要忘记提交
        return rint;
    }

	//测试使用xml构建的
    @Test
    public void testXml() {
        UserTest1 userDao = new UserTest1();
        User user = new User();
        user.setName("wwwttt");
        user.setPassword("33333");
        if (userDao.insert(user) == 1) {
            System.out.println("insert success...");
        }else{
            System.out.println("insert fail...");
        }

    }

	//测试不使用xml构建的
	@Test
    public void testAnnoAndXml() {

        SqlSession session = AnnoTest.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        User userById = mapper.getUserById(1);
        userById.setId(null);
        userById.setName("渣渣辉");
        mapper.insertUser(userById);
        session.commit();
    }

4.补全上面的几个xml

​ 1.con1.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!--<settings>
        &lt;!&ndash; 这个可以用log4j &ndash;&gt;
        <setting name="logImpl" value="STDOUT_LOGGING" />
    </settings>-->
    <!-- 和Spring整合后environment配置都会被干掉 -->
    <environments default="development">
        <environment id="development">
            <!-- 使用jdbc事务管理,目前由mybatis来管理 -->
            <transactionManager type="JDBC" />
            <!-- 数据库连接池,目前由mybatis来管理 -->
            <dataSource type="POOLED"><!--有关于mysql数据库的各种信息-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3306/no_name" />
                <property name="username" value="root" />
                <property name="password" value="root" />
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!--将操作配置文件User.xml系添加进mapper-->
        <mapper resource="mybatis/user.xml" />
        <!--<mapper class="mybatis/user.xml" />
        <mapper url="mybatis/user.xml" />-->
        <!--<package name=""/>-->
    </mappers>
</configuration>

2.user.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!--<settings>
        &lt;!&ndash; 这个可以用log4j &ndash;&gt;
        <setting name="logImpl" value="STDOUT_LOGGING" />
    </settings>-->
    <!-- 和Spring整合后environment配置都会被干掉 -->
    <environments default="development">
        <environment id="development">
            <!-- 使用jdbc事务管理,目前由mybatis来管理 -->
            <transactionManager type="JDBC" />
            <!-- 数据库连接池,目前由mybatis来管理 -->
            <dataSource type="POOLED"><!--有关于mysql数据库的各种信息-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3306/no_name" />
                <property name="username" value="root" />
                <property name="password" value="root" />
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!--将操作配置文件User.xml系添加进mapper-->
        <mapper resource="mybatis/user.xml" />
        <!--<mapper class="mybatis/user.xml" />
        <mapper url="mybatis/user.xml" />-->
        <!--<package name=""/>-->
    </mappers>
</configuration>

3.UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.mybatis.UserMapper">

    <!--插入用户信息-->
    <insert id="insertUser" parameterType="com.qdz.entity.User">
        <!--<selectKey keyProperty="id" order="BEFORE" resultType="java.lang.String">
            select uuid()
            &lt;!&ndash;            这里是对于主键属性的id进行赋值 &ndash;&gt;
        </selectKey>-->
        insert into user(name,password) values(#{name},#{password})
    </insert>
    <select id="getUserListPageByIds" resultType="com.qdz.entity.User">
       select * from user where id in
       <foreach collection="ids" item="id" separator="," open="(" close=")">
           #{id}
       </foreach>
    </select>

    <update id="updateUsers1">
        update user set name='夏侯惇',password='54321',create_time=now() where id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </update>



    <update id="updateUsers2">
        update user set name='姜维',password='54321',create_time=now() where id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </update>



</mapper>

4.大致执行流程(本人debug出来,以查询为例)

import org.apache.ibatis.executor.SimpleExecutor;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.executor.statement.PreparedStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionManager;
import org.apache.ibatis.session.defaults.DefaultSqlSession;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;
import org.apache.ibatis.transaction.TransactionFactory;

import javax.sql.DataSource;

/**
 * mybatis查询过程
 * @author authorZhao
 * @date 2020年05月23日
 */
public class MyBatisQueryDetail {

    /**
     * 1.构建{@link SqlSessionFactory}
     *      -取配置文件 Configuration
     *          {@link Configuration}里面包含了所有
     *                 - {@link Environment} 这就是setting连的 Environment配置
     *                      - {@link TransactionFactory} 事务
     *                      - {@link DataSource} 数据源
     *                 -{@link Configuration#logImpl} 日志实现
     *                 -{@link Configuration#defaultExecutorType} 执行器类型,默认defalut
     *                 -{@link Configuration#mapperRegistry} mapper注册器
     *                 -{@link Configuration#interceptorChain} 拦截器链
     *                 -{@link Configuration#mappedStatements} 就是每一个具体的mapper,包含.java和.xml的
     *      - 构建{@link SqlSessionFactory}
     *          ->{@link DefaultSqlSessionFactory}
     *          ->{@link SqlSessionManager}
     *
     *
     * 2.获取sqlSession{@link SqlSessionFactory#openSession()}
     *      ->{@link DefaultSqlSessionFactory#openSessionFromDataSource(org.apache.ibatis.session.ExecutorType, org.apache.ibatis.session.TransactionIsolationLevel, boolean)}
     *              ->{@link Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)}
     *                  -> 这里面根据执行器类型创建执行器,然后执行执行器的方法 interceptorChain.pluginAll(executor)
     *
     * 3.获取mapper/selectone
     *  ->{@link DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)}
     *              ->{@link SimpleExecutor#doQuery(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.mapping.BoundSql)}
     *                      ->{@link Configuration#newStatementHandler(org.apache.ibatis.executor.Executor, org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.mapping.BoundSql)}
     *                      -> 上一步也有一个 pluginAll方法
     *                      ->{@link org.apache.ibatis.executor.SimpleExecutor#prepareStatement(org.apache.ibatis.executor.statement.StatementHandler, org.apache.ibatis.logging.Log)}
     *                              -> 默认实现 RoutingStatementHandler,会构建PreparedStatement
     *                              -{@link StatementHandler},构建这个的时候会构建  ParameterHandler、 ResultSetHandler
     *                                      ->{@link Configuration#newParameterHandler(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.mapping.BoundSql)}
     *                                      ->{@link Configuration#newResultSetHandler(org.apache.ibatis.executor.Executor, org.apache.ibatis.mapping.MappedStatement, org.apache.ibatis.session.RowBounds, org.apache.ibatis.executor.parameter.ParameterHandler, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.mapping.BoundSql)}
     *                              ->{@link PreparedStatementHandler#query(java.sql.Statement, org.apache.ibatis.session.ResultHandler)}
     *                  ->{@link ResultSetHandler#handleResultSets(java.sql.Statement)}
     *
     *
     *
     */

    /**
     *
     * 拦截器四大接口
     * 1.Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 拦截执行器的方法;
     * 4.StatementHandler (prepare, parameterize, batch, update, query) 拦截Sql语法构建的处理
     * 2.ParameterHandler (getParameterObject, setParameters) 拦截参数的处理;
     * 3.ResultSetHandler (handleResultSets, handleOutputParameters) 拦截结果集的处理;
     */

}

5.单机mybatis常见问题(单独使用情况)

  • sqlsession线程安全问题

    关于sqlsession线程的安全先行问题

    /**
         * 测试多线程插入时的sqlsession线程安全问题
         */
        @Test
        public void testThread(){
            ExecutorService executorService = Executors.newCachedThreadPool();
            SqlSession session = AnnoTest.getSession();
            List<Future> futures = new ArrayList<>();
            for (int i = 0; i < 10; i++) {
                int j = i;
                //如果在此获取sessionn那么每个线程都是单独的sqlsession
                Future<?> submit = executorService.submit(() -> save(j,session));
                futures.add(submit);
            }
            while(futures.stream().anyMatch(i -> i.isDone() == false)){
                try {
                    Thread.currentThread().sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
        }
    
        private void save(int i,SqlSession session) {
            UserMapper mapper = session.getMapper(UserMapper.class);
            User userById = mapper.getUserById(1);
            userById.setId(null);
            userById.setName("渣渣辉"+i+":  "+Thread.currentThread().getName());
            mapper.insertUser(userById);
            session.commit();
        }
    
  • mybatis缓存

    • 1.一级缓存是sqlsession级别

      同一个sqlsession执行同样的sql是有缓存的,如果执行了commit、flush、close 之后 会失效

    • 2.二级缓存是namespace级别的,(开发中从未使用),不做研究

  • sql解析

    这个其实也是mysql的精髓之一,在xml里面写出不可维护的sql,外加一堆动态标签,你无可替代

    这个未研究,xml解析也为研究

  • 其他使用问题

    • #{}和${}区别,前者在sql出来之后变成了?后者直接替代了
    • 参数接收,mybatis对参数默认采用param1、param2这样的命名,存在map里面
    • 什么对多对一映射略 对多分页参考
    • mybatis的日志实现,提供了几种常见的,项目里面有就会适配

6.结束

参考文章:文中demo的配置抄袭自网上,现在忘记那个了,主题流程来自mybatis官网

本文代码github地址本文代码地址

下一篇会结合spring开发使用,重点分析问题如下

1.spring如何解决sqlsession线程安全问题

2.spring里面一级缓存为何失效的问题

3.spring如何接管事务

4.分页插件和sql打印

5.springboot自动配置和mybatisPlus的单表CRUD实现

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章