mybatis源码考究二(sqlsession线程安全和缓存失效)

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 扫描的大致原理

mapper里面混有@Service

其实在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如何管理事务,以及声明式事务原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值