Mybatis实现逻辑和代码解析

Mybatis实现逻辑和代码解析

九月 23, 2022

Mybatis 逻辑及代码解读

一、Mybatis的实现逻辑

  1. 在Mybatis的初始化过程中,会生成一个Configuration全局配置对象,里面包含了所有初始化过程中生成的对象(通过XML配置文件)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <configuration>
    <!-- 类型-->
    <typeAliases>
    <typeAlias type="entity.News" alias="news"/>
    </typeAliases>

    <!-- 连接数据库:数据库 -->
    <environments default="development">
    <environment id="development">
    <transactionManager type="JDBC"/>
    <dataSource type="POOLED">
    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8"/>
    <property name="username" value="root"/>
    <property name="password" value="2936283"/>
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    </dataSource>
    </environment>
    </environments>

    <!-- 导入dao的映射文件? -->
    <mappers>
    <mapper class="mapper.NewsMapper"/>
    </mappers>
    </configuration>
  2. 根据 Configuration 创建一个 SqlSessionFactory 对象,用于创建 SqlSession “会话”

1
private SqlSession session = null;
  1. 通过 SqlSession 可以获取到 Mapper 接口对应的动态代理对象,去执行数据库的相关操作(使用了反射,获取Mapping类)
1
2
3
private NewsMapper newsMapper = null;
//反射获取对象
newsMapper = session.getMapper(NewsMapper.class);
  1. 动态代理对象执行数据库的操作,由 SqlSession 执行相应的方法,在他的内部调用 Executor 执行器去执行数据库的相关操作
1
2
3
4
5
6
7
8
9
10
11
<mapper namespace="mapper.NewsMapper">

<select id="findById" resultType="news">
select * from news where id=#{id}
</select>

<insert id="insertNews" parameterType="news">
insert into news(title,content) values(#{title},#{content})
</insert>

</mapper>
  1. 在 Executor 执行器中,会进行相应的处理,将数据库执行结果返回

二、Mybatis的缓存实现逻辑

ad

一级缓存

​ 在应用运行过程中,我们有可能在一次数据库会话中,执行多次查询条件完全相同的 SQL,MyBatis 提供了一级缓存的方案优化这部分场景,如果是相同的 SQL 语句,会优先命中一级缓存,避免直接对数据库进行查询,提高性能。

​ 每个 SqlSession 中持有了 Executor,每个 Executor 中有一个 LocalCache。当用户发起查询时,MyBatis 根据当前执行的语句生成 MappedStatement,在 Local Cache 进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入 Local Cache,最后返回结果给用户。具体实现类的类关系图如下图所示:

ad

  1. MyBatis 一级缓存的生命周期和 SqlSession 一致。
  2. MyBatis 一级缓存内部设计简单,只是一个没有容量限定的 HashMap,在缓存的功能性上有所欠缺。
  3. MyBatis 的一级缓存最大范围是 SqlSession 内部,有多个 SqlSession 或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为 Statement。

二级缓存

​ 在上文中提到的一级缓存中,其最大的共享范围就是一个 SqlSession 内部,如果多个 SqlSession 之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用 CachingExecutor 装饰 Executor,进入一级缓存的查询流程前,先在 CachingExecutor 进行二级缓存的查询,具体的工作流程如下所示。

​ 二级缓存开启后,同一个 namespace 下的所有操作语句,都影响着同一个 Cache,即二级缓存被多个 SqlSession 共享,是一个全局的变量。

当开启缓存后,数据的查询执行的流程为:

二级缓存 -> 一级缓存 -> 数据库

  1. MyBatis 的二级缓存相对于一级缓存来说,实现了 SqlSession 之间缓存数据的共享,同时粒度更加细,能够到 namespace 级别,通过 Cache 接口实现类不同的组合,对 Cache 的可控性也更强。
  2. MyBatis 在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
  3. 在分布式环境下,由于默认的 MyBatis Cache 实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将 MyBatis 的 Cache 接口实现,有一定的开发成本,直接使用 Redis、Memcached 等分布式缓存可能成本更低,安全性也更高。

三、Mybatis中UserMapper.java是接口,为什么实现类还能去调用它的方法?

因为UserMapper.java有对应的xml –> UserMapper.xml,其中

1
<mapper namespace="com.tian.UserMapper">

反射生成namespace的对象:

1
boundType = Resources.classForName(namespace);

JDK动态代理:

Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);

总结:XML 中的 namespace="com.user.UserMapper" 接口 com.user.UserMapper 本身反射 JDK 动态代理实现接口。