全景预览
对象管理篇
我们在使用mybatis-spring开发的时候, 通常会写一个mapper接口, 然后写对应的xml 。 用起来就会很方便
那么这里就有几个问题
- Mapper是怎么被扫描到spring的呢?
- Mapper的动态代理类是什么?
我们在使用接口的时候, 一定要自定义一个接口扫描器.
/**
* 自定义扫描器
* <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" >
* <property name="basePackage" value="com.company.project.maper"/>
* </bean>
*/
public interface BlogMapper {
Blog selectByPrimaryKey(Long id);
}
扫描器的主要作用就是把符合条件的接口, 封装成一个bean放到 spring 中,
这里BlogMapper被扫描到了,
实际上和以下的定义方式是没有区别的
<bean id="blogMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface"
value="com.company.project.maper.BlogMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
spring就会管理非常多的 org.mybatis.spring.mapper.MapperFactoryBean
MapperFactoryBean 是什么呢?
MapperFactoryBean 是一个实现了spring的FactoryBean
接口。
FactoryBean 是spring提供的一种特殊的定义对象的接口,通过getObject方法创建实际的对象
所以被spring管理的会有两个对象
- blogMapper -> BlogMapperProxy(实现目标接口的动态代理类)
- &blogMapper -> MapperFactoryBean
SqlSession篇
SqlSession 是mybatis的执行主要接口, 可以理解为一个门面(内部自己进行组件交互实现功能)
在对象创建时SqlSession有两层实现
- SqlSessionTemplate 持有sqlSessionFactory,可以创建真正的SqlSession, 这里的相当于一层代理
- $SqlSession(这个类不存在) , 这个类是一个JDK动态代理类,也实现了SqlSession接口, 它的InvocationHandler 是SqlSessionTemplate 的一个内部类SqlSessionInterceptor。 这里主要就是结合spring-tx模块的事务
$SqlSessionProxy 就是和spring事务结合的核心逻辑:
如果当前事务中已经创建过SqlSession, 取出SqlSession
否则开启新的SqlSession
也就是说: 事务内无论执行多少次的数据库交互操作, 在mybatis层面,就只开启了一次会话(会话内有一级缓存特性)
巩固篇
理解了以上的过程和开头的对象创建全景图之后, 来验证一下你是否真正理解了吧.
问1: 以下代码会去数据库查询几次?
@Service
public class BlogService{
@Resource
private BlogMapper blogMapper;
@Transactional
public void doMessage( String id) throws Exception {
blogMapper.selectById(id);
blogMapper.selectById(id);
blogMapper.selectById(id);
}
}
相信很多了解一级缓存的朋友已经知道, 只会去数据库查询一次。
这就是因为每次都是通过$SqlSession这个动态代理类, 和spring-tx模块的结合的时候获取到的同一个会话。
- spring事务拦截器获取Connection
- 第一次,创建会话,获取Connection, 绑定SqlSession到spring-tx
- 第二次,从事务中获取会话,从一级缓存中获取
- 第三次,从事务中获取会话,从一级缓存中获取
- spring提交事务之前, 提交会话,关闭Connection
问2: 取消事务标签后, 会获取几次会话?
答: 3次, mybatis操作数据库和会话绑定,会话和事务绑定。 (和tomcat请求没有关系)
- 第一次,创建会话,获取Connection, 执行查询,提交会话
- 第二次,创建会话,获取Connection, 执行查询,提交会话
- 第三次,创建会话,获取Connection, 执行查询,提交会话
问3: 如何在事务内让一级缓存失效呢?
比如: oracle获取序列
- 细粒度设置
<!-- 加入属性 flushCache="true" -->
<select id="selectByPrimarykey" resultMap="ResultMap" flushCache="true">
select <include refid="Base_comlumn_list"/>
from BLOG
where ID = #{id}
</select>
- 粗粒度设置
在mybatis.xml的settings->setting->localCacheScope=STATEMENT
这种粗粒度的方式也是分布式场景下的推荐使用方式, 避免数据被其他机器更新后,当前机器的多次查询无法取得正确的结果