Mybatis整合Spring

  1. spring 集成myBatis
  2. 动态化SQL与脚本解析器

一、spring 集成myBatis


核心使用:

基础集成使用:

1、配置 SqlSessionFactoryBean

2、配置 MapperFactoryBean

3、获取mapper 对像执行业务方法

1
2
3
4
5
6
7
8
9
10
11
12
<bean id="dateSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<constructor-arg name="url" value="${jdbc.url}"></constructor-arg>
<constructor-arg name="password" value="${jdbc.password}"></constructor-arg>
<constructor-arg name="username" value="${jdbc.username}"></constructor-arg>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dateSource"/>
</bean>
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.study.mapper.UserMapper"></property>
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>

对像说明:

FactoryBean:

工厂Bean 用于 自定义生成Bean对像,当在ioc 中配置FactoryBean 的实例时,最终通过bean id 对应的是FactoryBean.getObject()实例,而非FactoryBean 实例本身

SqlSessionFactoryBean:

生成SqlSessionFactory 实例,该为单例对像,作用于整个应用生命周期。常用属性如下:

  • dataSource:数据源(必填)
  • configLocation:指定mybatis-config.xml 的内容,但其设置的 将会失效(选填)
  • mapperLocations:指定mapper.xml 的路径,相当于mybatis-config.xml 中 元素配置,(选填)

MapperFactoryBean:

生成对应的Mapper对像,通常为单例,作用于整个应用生命周期。常用属性如下:

  • mapperInterface:mapper 接口 (必填)
  • sqlSessionFactory:会话工厂实例 引用 (必填)

关于Mapper 单例情况下是否存在线程安全的问题?

在原生的myBatis 使用中mapper 对像的生命期是与SqlSession同步的,不会存在线程安全问题,现在单例的mapper 是如何解决线程安全的问题的呢?

核心流程解析:

SQL session 集成结构:

![image1](/images/myBatis 第三方框架集成/image1.png)

初始化流程

创建会话模板 SqlSessionTemplate
1
2
3
4
5
>org.mybatis.spring.mapper.MapperFactoryBean#MapperFactoryBean()
> org.mybatis.spring.support.SqlSessionDaoSupport#setSqlSessionFactory
> org.mybatis.spring.SqlSessionTemplate#SqlSessionTemplate()
>org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor
//sqlSessionProxy被SqlSessionInterceptor代理拦截
创建接口
1
2
3
4
org.mybatis.spring.mapper.MapperFactoryBean#getObject
org.mybatis.spring.SqlSessionTemplate#getMapper
org.apache.ibatis.session.Configuration#getMapper
org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.binding.MapperProxy<T>) //最终获取到的userMapper是已经代理过的
执行查询
1
2
3
4
5
6
7
8
9
10
11
12
13
com.tuling.mybatis.dao.UserMapper#selectByid
org.apache.ibatis.binding.MapperProxy#invoke
org.mybatis.spring.SqlSessionTemplate#selectOne(java.lang.String)
org.mybatis.spring.SqlSessionTemplate#sqlSessionProxy#selectOne(java.lang.String)
org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke
//重新获取sqlsession
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
org.mybatis.spring.SqlSessionUtils#getSqlSession()
org.apache.ibatis.session.SqlSessionFactory#openSession()
org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne()

每次查询都会创建一个新的 SqlSession 会话,一级缓存还会生效吗?

通过前几次课我们了解到 一级缓存的条件是必须相同的会话.

所以缓存通过和spring 集成之后就不会生效了。除非使用spring 事物 这时就不会在重新创建会话。

事务使用 :

spring 事物没有针对myBatis的配置,都是一些常规事物配置:

1
2
3
4
5
6
<!--添加事物配置-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource"/>
</bean>
<!--事物注解配置-->
<tx:annotation-driven/>

添加事物注解:

1
2
3
4
5
6
7
8
9
10
@Override
@Transactional(rollbackFor = Exception.class)
public void selectById(Integer id) {
User user = userMapper.selectById(id);
User user1 = userMapper.selectById(id);

System.out.println(user);
System.out.println(user1);

}

执行测试发现 当调用selectById方法时两次查询不在重复创建 sqlSession。而是共用一个直到selectById方法结束。

事务与SqlSession 集成原理:

其原理前面讲查询流程时有所涉及。每次执行SQL操作前都会通过 getSqlSession 来获取会话。其主要逻辑是

​ 如果当前线程存在事物,并且存在相关会话,就从ThreadLocal中取出 。如果没就从创建一个 SqlSession 并存储到ThreadLocal 当中,共下次查询使用。

相关源码:

1
2
3
4
5
6
7
org.mybatis.spring.SqlSessionUtils#getSqlSession()
org.springframework.transaction.support.TransactionSynchronizationManager#getResource
org.mybatis.spring.SqlSessionUtils#sessionHolder
org.apache.ibatis.session.SqlSessionFactory#openSession()
org.mybatis.spring.SqlSessionUtils#registerSessionHolder
org.springframework.transaction.support.TransactionSynchronizationManager#isSynchronizationActive
org.springframework.transaction.support.TransactionSynchronizationManager#bindResource

简化Mapper 配置

如果每个mapper 接口都配置MapperFactoryBean相当麻烦 可以通过 如下配置进行自动扫描

<mybatis:scan base-package=”com.tuling.mybatis.dao”/>

其与 spring bean 注解扫描机制类似,所以得加上注解扫描开关的配置

1
<context:annotation-config/>

重点问题:

MyBatis 的多级缓存机制

  1. 一级缓存(Local Cache)
    • 一级缓存是基于 SqlSession 的缓存,在同一个 SqlSession 中执行的查询会将结果缓存起来。
    • 一级缓存是默认开启的,且无法关闭,它对于减少重复的查询操作非常有效。
    • 一级缓存的作用范围是当前的 SqlSession,当 SqlSession 关闭时,一级缓存也会被清空。
  2. 二级缓存(Global Cache)
    • 二级缓存是基于 Mapper 的缓存,多个 SqlSession 共享同一个 Mapper 的缓存。
    • 二级缓存可以跨 SqlSession 和事务,因此可以在不同的 SqlSession 中共享缓存。
    • 二级缓存是默认关闭的,需要手动配置开启,并且需要注意缓存的有效性和并发访问的问题。
    • 二级缓存的配置是在 Mapper.xml 文件中进行的,在 Mapper 接口中添加 `` 标签并配置相应的属性。
  3. 刷新失效(Flush Cache)
    • MyBatis 提供了在增删改操作时自动删除缓存的功能。
    • 当执行了增删改操作后,MyBatis 会自动清空一级缓存和二级缓存中与该操作相关的缓存数据,保证数据的一致性。

需要注意的是,虽然多级缓存可以有效提高性能,但也存在一些需要注意的问题:

  • 缓存的有效性:缓存中的数据应该与数据库中的数据保持一致,需要注意缓存的更新机制。
  • 缓存的并发访问:多个线程同时访问缓存时可能会出现并发问题,需要采取措施保证缓存的正确性。
  • 缓存的大小和清理策略:需要根据实际情况配置合适的缓存大小和清理策略,避免内存溢出等问题。

MyBatis 整合Spring一二级缓存的使用问题

一级缓存:

如果未开启事务,线程安全的SqlSessionTemplate在每次需要 SqlSession 时创建新的实例,并且每次使用之后都关闭。线程安全,但无法使用一级缓存,缓存失效;

如果开启事务,并且存在相关会话,就从ThreadLocal中取出 。如果没就从创建一个 SqlSession 并存储到ThreadLocal 当中,共下次查询使用。能够使用到一级缓存。

二级缓存:

整合Spring后,mapper是单例的,多个线程共享二级缓存,因此存在相同的复杂线程安全问题。分布式时代,不会使用二级缓存。