MyBatis 原理 及 源码分析( SqlSessionFactory、SqlSession、代理接口,一二级缓存 )

一、回顾Mybatis的使用

Mybatis应该是现在我们项目中使用非常频繁的框架,它几乎消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装,让我们可以使用简单的XML或注解用于配置和原始映射。

还记得我们在配置Mybatis的时候都要写一个 mybatis_config.xml 最常写的应该数据库连接信息,还有Mapper.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>
    <!-- 环境配置 -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <!-- 数据库连接相关配置 ,这里动态获取config.properties文件中的内容-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://www.bixuechao.com:3306/testdb"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <!-- mapping文件路径配置 -->
    <mappers>
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>
</configuration>

上面配置了数据库连接信息,和mapping文件的路径,如果要查询数据库的信息,是不是还要再来个接口和 Mapper.xml 文件:

public interface UserMapper {
    UserEntity getUser(int id);
}
<?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.demo.mapper.UserMapper">

    <select id="getUser" parameterType="int"
            resultType="com.mybatis.demo.entity.UserEntity">
        select * from user where id=#{id}
    </select>

</mapper>

相信上面这种基本上大家都这样写过吧,那再来看下下面的代码是否熟悉:

public class TestMyBatis {

    public static void main(String[] args) {

        try {
            // 基本mybatis环境
            // 1.定义mybatis_config文件地址
            String resources = "mybatis_config.xml";
            // 2.获取InputStreamReaderIo流
            Reader reader = Resources.getResourceAsReader(resources);
            // 3.获取SqlSessionFactory
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
            // 4.获取Session
            SqlSession sqlSession = sqlSessionFactory.openSession();
            // 5.操作Mapper接口
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            UserEntity user = mapper.getUser(175);
            System.out.println(user.getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

上面这是直接使用Mybatis的方式调的mapper,大家在项目中应该大多都是SSM或者SpringBoot 已经封装的自动帮我们实现了上面的这种代码,在这里写上面这种形式更方便下面对源码的解读和大家的理解。

二、Mybatis 的执行原理流程

  1. 当项目启动的时候,会首先读取我们配置的mybatis_config.xml文件,然后读取文件转化为InputStreamReader格式使用SqlSessionFactoryBuilder().build()方法解析XML,并且mybatis_config.xml文件只可被解析一次,如果手动再调用解析会报:Each XMLConfigBuilder can only be used once.错误。
  2. 解析mybatis_config.xml文件时,会将settings、environments、mappers等信息解析装配到Configuration对象中,供其他地方使用,同时Mapper.xml 中的信息比如namespace 接口地址会使用反射拿到Class,存放到 名为knownMappersHashMap中,还有Mapper.xml中的select|insert|update|delete 语句,会根据id为key,value为MappedStatement的对象存放到名为mappedStatements的HashMap中。
  3. 解析完XML会返回一个DefaultSqlSessionFactory 的对象。下面可以通过这个对象openSession()方法拿到一个SqlSession对象。
  4. 在获取SqlSession对象会根据 cacheEnabled 创建执行器 ,注意Mybatis的二级缓存默认是开启的,也就是cacheEnabled默认是为true,所以这里默认创建的是二级缓存的CachingExecutor,如果设成false,返回的便是SimpleExecutor,现在好多网上说二级缓存默认关闭需要注意下,下面看源码的时候可以看下这个变量的值,至于在项目中为何二级缓存没有生效,可以检查下xml配置文件有没有<cache></cache>声明或者是否手动把cacheEnabled设置为了false,在使用默认的二级缓存的时候 Mybatis 是采用的 HashMap作为缓存容器,在并发情况下还是比较容易发生线程安全问题,如果要使用二级缓存,可以使用Redis来缓存,配置到Mybatis中。
  5. 在上面已经拿到一个Executor执行器,通过执行器有为我们创建了一个DefaultSqlSession返回出去。
  6. 在拿到了SqlSession后便可以获取Mapper接口对象了,SqlSession中有个getMapper()方法,通过传入一个Class 便可拿到该对象,是怎么实现的呢,其实就是使用的java的动态代理技术,做了个代理类,在第二点的时候我提到了一个knownMappers的HashMap容器存放namespace中的接口Class,现在在getMapper的时候就是根据传入的Class 取knownMappers容器中取,取到之后 Mybatis 同过 MapperProxy 类实现了InvocationHandler 代理了Mapper接口。
  7. 再通过SqlSessiongetMapper()方法上面已经拿到了Mybatis 给我们的代理对象,所以下面在执行对象中的方法时,肯定执行的时代理类中的invoke方法,在这个方法中 Mybatis 又提供了一个MapperMethod Mapper执行对象,来帮助我们执行下面的SQL操作,并且在这里中为了性能提升,还对MapperMethod 做了个简单的HashMap 缓存。
  8. 上面说道MapperMethod是帮我们执行了下面的SQL操作,其实还是调用了sqlSession中的方法,如果是返回一个对象,就会使用sqlSessionselectOne方法执行,其实selectOne方法还是又调用的selectList。接着如果是一个查询操作,会使用Executor执行器的query方法,会先触发CachingExecutorquery方法先查询缓存这个缓存就是二级缓存,如果没有,会再去父类的BaseExecutorquery在这边会再查询一级缓存,还是没有就会去SimpleStatementHandler这个类下的query方法使用java 原声的Connection、Statement 去查询数据库,并将查询的数据整理成List类型返回,如果List中只有一个数据,就将第0个返回出去。
  9. 上面拿到了数据库的返回结果,会先存放到一级缓存中,如果二级没有关闭然后再放到二级缓存中,然后返还给最终数据给最初的调用者。

别看Mybatis我们使用的那么简单,其实执行原理还是挺多的,其中有很多是需要我们学习和借鉴的东西,如果对上面的流程不太明白,下面便是对源码的解读,方便我们对上面流程的理解。

三、Mybatis源码解读

未完待续。。。

上一篇:mybatis 源码分析之创建sqlSessionFactory


下一篇:Mybatis3详解(十六)——Mybatis运行原理之SqlSessionFactory的构建过程