mybatis源码考究二
1.mybatis整合spring解决sqlsession线程安全问题
2.mybatis整合spring一级缓存失效问题
mybatis结合spring使用
1.项目依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<!-- mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
<scope>runtime</scope>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
依赖说明:
本项目采用springboot,但不会直接引入mybatis-starter,这一部分选择手动配置,其中spring-mybatis依赖了spring-jdbc,这里直接用starter方式引入
2.整合配置
/**
* @author authorZhao
* @date 2020年05月26日
*/
@Configuration
@EnableTransactionManagement
public class PlusMybatisConfiguration {
/**
* datasource在里jdbc-starter面已经配置了,jdbc默认数据源是HikariCP,据说性能很高
* @param dataSource
* @return
* @throws IOException
*/
/*@Bean
@ConditionalOnMissingBean(DataSource.class)
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/no_name");
dataSource.setPassword("root");
dataSource.setUsername("root");
return dataSource;
}*/
/**
* 配置的是工厂,实际是要生产SqlSessionFactory,通过SqlSessionFactory#opneSession()
* @param dataSource
* @return
* @throws IOException
*/
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) throws IOException {
//数据源
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
//xml文件,注意mapper文件采用注解扫描
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath:mybatis/*.xml");
sqlSessionFactoryBean.setMapperLocations(resources);
//配置类,这个可以读取xml也可以手写
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
//开启突驼峰映射
configuration.setMapUnderscoreToCamelCase(true);
sqlSessionFactoryBean.setConfiguration(configuration);
return sqlSessionFactoryBean;
}
/**
* TransactionManager在jdbc-starter里面已经自动配置了,可忽略
*/
/*@Bean
public TransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
}*/
}
3.使用的话直接注入mapper就行
4.问题分析
spring-mybatis如何解决sqlsession线程安全问题,一级缓存为何失效
1.SqlSessionTemplate
spring默认都是单例模式,假设你有多个mapper那么问题来了,你的这些mapper用的是不是同一个sqlsession,因为在非spring环境mapper是sqlsession里面代理生成
整合spring之后可以看到spring-mybatis提供了mapper扫描注解,
@MapperScan 扫描的大致原理
其实在spring里面为mapper代理的是MapperFactoryBean,也是一个工厂bean
看他的getObject方法
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
这里的sqlsession是SqlSessionTemplate,SqlSessionTemplate是spring-mybatis里面新加的
看源码getsession里面获取SqlSessionTemplate,但是发现SqlSessionTemplate初始化却在这里,问题来了,这个方法什么时候执行
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
}
}
回到MapperFactoryBean方法这里,spring-mybatis扫描的时候有一段代码
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
这个就表明MapperFactoryBean创建完成之后就会进行属性赋值,执行这个set方法
说了这么多mapper是由SqlSessionTemplate生成的
2.线程安全和缓存问题源码跟踪
接着说SqlSessionTemplate为什么线程安全
下面以修改为例说明SqlSessionTemplate的线程安全问题和一级缓存失效问题
@Autowired
private UserService userService;
@Test
public void tsst2(){
User user = new User();
user.setId(2);
user.setName("曹操");
user.setPassword("孟德");
userService.updateUser(user);
}
1.mapper执行方法就会到SqlSessionTemplate里面update方法
2.SqlSessionTemplate里面有一个sqlsession也是代理生成的看代理的方法
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//这个是获取真正干活的sqlsession,本质还是defalutsqlsession
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
//执行操作方法
Object result = method.invoke(sqlSession, args);
//判断有没有开启事务,如果没有开启事务就直接提交,提交之后那么一级缓存也就失效了,提交是会有清空缓存的操作
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
//提交事务
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
//关闭sqlsession,里面有文章,开启事务不会直接关闭,而是计量数减少,没有开启事务就直接关闭
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
先看获取sqlsession的方法
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
//注意这句话,这就是为什么经常有这句话打印的问题,开启事务就不会每次都创建session,不开启就会
LOGGER.debug(() -> "Creating a new SqlSession");
//默认sqlsession获取,本质就是defalutSqlSession
session = sessionFactory.openSession(executorType);
//注册到
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
自己看一下这个TransactionSynchronizationManager类,这个类是spring的
这个类清一色的使用ThreadLocal,因此可以看到每个线程实际上用的是自己的sqlsession,所以能解决线程安全问题
上面一路操作,只能大概分析出线程安全和缓存失效的原因,事务管理还藏在其中
总结:
1.mybatis整合spring解决sqlsession线程安全问题
- 每个线程实际上都是自己的sqlsession,关键点ThreadLocal
2.mybatis整合spring一级缓存失效问题
- 关闭事务,缓存失效
- 事务内部一级缓存有用
下回解决spring如何管理事务,以及声明式事务原理