spring 集成myBatis
动态化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 集成结构:

初始化流程 创建会话模板 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
创建接口 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 = 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 的多级缓存机制
一级缓存(Local Cache) :
一级缓存是基于 SqlSession 的缓存,在同一个 SqlSession 中执行的查询会将结果缓存起来。
一级缓存是默认开启 的,且无法关闭,它对于减少重复的查询操作非常有效。
一级缓存的作用范围是当前的 SqlSession,当 SqlSession 关闭时,一级缓存也会被清空。
二级缓存(Global Cache) :
二级缓存是基于 Mapper 的缓存,多个 SqlSession 共享同一个 Mapper 的缓存。
二级缓存可以跨 SqlSession 和事务,因此可以在不同的 SqlSession 中共享缓存。
二级缓存是默认关闭 的,需要手动配置开启,并且需要注意缓存的有效性和并发访问的问题。
二级缓存的配置是在 Mapper.xml 文件中进行的,在 Mapper 接口中添加 `` 标签并配置相应的属性。
刷新失效(Flush Cache) :
MyBatis 提供了在增删改操作时自动删除缓存的功能。
当执行了增删改操作后,MyBatis 会自动清空一级缓存和二级缓存中与该操作相关的缓存数据,保证数据的一致性。
需要注意的是,虽然多级缓存可以有效提高性能,但也存在一些需要注意的问题:
缓存的有效性:缓存中的数据应该与数据库中的数据保持一致,需要注意缓存的更新机制。
缓存的并发访问:多个线程同时访问缓存时可能会出现并发问题,需要采取措施保证缓存的正确性。
缓存的大小和清理策略:需要根据实际情况配置合适的缓存大小和清理策略,避免内存溢出等问题。
MyBatis 整合Spring一二级缓存的使用问题 一级缓存: 如果未开启事务,线程安全的SqlSessionTemplate在每次需要 SqlSession 时创建新的实例,并且每次使用之后都关闭。线程安全,但无法使用一级缓存,缓存失效;
如果开启事务,并且存在相关会话,就从ThreadLocal中取出 。如果没就从创建一个 SqlSession 并存储到ThreadLocal 当中,共下次查询使用。能够使用到一级缓存。
二级缓存:
整合Spring后,mapper是单例的,多个线程共享二级缓存,因此存在相同的复杂线程安全问题。分布式时代,不会使用二级缓存。