9. Mybatis与Spring集成原理解析

一、引言

现在大家很少直接使用Mybatis了,一般都是与spring集成。下面我们回顾下原生Mybatis的使用示例:

java 复制代码
// 1. 加载MyBatis核心配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 构建SqlSessionFactory(会话工厂)
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 开启数据库会话(try-with-resources自动关闭资源)
// 注意:默认 openSession() 是自动提交(autoCommit=true),需显式设置为 false
try (SqlSession sqlSession = sqlSessionFactory.openSession(false)) {
    // 4. 获取Mapper接口的代理实例
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    // 5. 执行数据库操作
    // 假装有些更新数据库的代码
    // 6. 手动提交事务
    sqlSession.commit();
} catch (Exception e) {
    // 7. 发生异常时回滚事务
    sqlSession.rollback();
} 

这是一个带有事务的示例,使用起来比较复杂,这时好奇的你可能有些疑问,为何在Spring内使用时非常简单呢

  1. 原生的Mybatis,需要依赖SqlSession获取Mapper,还要通过SqlSession做事务管理,最后还要自行关闭,为什么在Spring中居然都没有显示的使用SqlSession?
  2. 原生的Mapper需要从SqlSession中获取,显然不支持并发,为什么在Spring中,Mapper居然是单例的?

二、集成配置

我们先看下,Mybatis集成到Spring,需要的配置(如何在pom引入MyBatis-Spring这里就不演示了):

java 复制代码
@Configuration
@MapperScan("com.example.mapper") // 扫描Mapper接口所在包
@EnableTransactionManagement // 开启事务管理
public class AppConfig {

    // 1. 配置数据源(HikariCP)
    @Bean
    public DataSource dataSource() {
        // 读取数据库配置(实际项目中可放在properties文件中)
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC");
        config.setUsername("root");
        config.setPassword("123456");
        config.setDriverClassName("com.mysql.cj.jdbc.Driver");
        
        // 连接池配置
        config.setMaximumPoolSize(10);
        config.setMinimumIdle(5);
        return new HikariDataSource(config);
    }

    // 2. 配置SqlSessionFactory
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws IOException {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        
        // 设置数据源
        sessionFactory.setDataSource(dataSource);
        
        // 配置MyBatis全局属性(替代mybatis-config.xml),也可以还使用XML,通过setConfigLocation配置。
        org.apache.ibatis.session.Configuration mybatisConfig = new org.apache.ibatis.session.Configuration();
        mybatisConfig.setMapUnderscoreToCamelCase(true); // 下划线转驼峰
        sessionFactory.setConfiguration(mybatisConfig);
        
        // 注册Mapper映射文件(如果使用XML方式)
        org.springframework.core.io.Resource[] mapperLocations = new org.springframework.core.io.Resource[] {
            new org.springframework.core.io.ClassPathResource("mappers/UserMapper.xml")
        };
        sessionFactory.setMapperLocations(mapperLocations);
        
        // 设置事务工厂(与Spring事务集成)
        sessionFactory.setTransactionFactory(new SpringManagedTransactionFactory());
        
        return sessionFactory;
    }

    // 3. 配置事务管理器
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        // 使用Spring的数据源事务管理器,与MyBatis集成
        return new DataSourceTransactionManager(dataSource);
    }
}

解释下都配置了哪些东西

  1. @MapperScan("com.example.mapper"):指定Mapper接口所在包,这个注解会把包下的Mapper都注册到Spring的容器中。
  2. @EnableTransactionManagement: 开启事务管理。
  3. DataSource: 配置数据库连接池。
  4. SqlSessionFactoryBean : SqlSessionFactoryFactoryBean,用于构Mybatis的SqlSessionFactory
  5. PlatformTransactionManager: 事务管理器。

这里的核心是1和4,其他配置,并不是特意给Mybatis用的,无论使用哪种持久层框架,都要配置。下面我们对这两个配置作用,做详细介绍。

三、 MapperScan

我们先概括下@MapperScan的实现:

  1. 通过@Import导入MapperScannerRegistrar,将其纳入 Spring 初始化流程;
  2. MapperScannerRegistrar实现了Spring的ImportBeanDefinitionRegistrar接口,它的registerBeanDefinitions方法,会在Spring容器Bean实例化之前执行,进行Bean定义注册。
  3. registerBeanDefinitions方法中,根据注解的配置,创建 ClassPathMapperScanner 扫描指定包下的Mapper接口,并将接口配置,转换为MapperFactoryBean注册到Spring容器。
  4. Spring实例化Bean时,通过MapperFactoryBeangetObject方法,创建Mapper代理对象,加载到Spring容器中。

题外话:BeanFactoryFactoryBean有什么区别?
答:简单说:BeanFactory是 "容器",管理所有 Bean;FactoryBean是 "Bean 工厂",专注于创建特定 Bean。

我们先看下@MapperScan都有哪些属性:

  1. value/basePackages :指定扫描的包路径(两者功能一致,value 是简写)。例如 @MapperScan("com.example.mapper") 表示扫描该包下的所有接口。
  2. basePackageClasses :通过指定类的方式间接包路径,扫描这些类所在的包。例如 @MapperScan(basePackageClasses = UserMapper.class) 会扫描 UserMapper 所在包下的接口。
  3. annotationClass :指定只扫描标注了特定注解的接口。例如 @MapperScan(annotationClass = Mapper.class) 表示仅扫描带 @Mapper 注解的接口(需配合 @Mapper 使用)。一般不配置,默认扫描包下所有接口,省去使用注解的麻烦。
  4. markerInterface :指定只扫描继承了特定接口的接口。例如 @MapperScan(markerInterface = BaseMapper.class) 表示仅扫描继承 BaseMapper 的子接口,一般不配置。
  5. sqlSessionTemplateRef :指定要注入的 SqlSessionTemplate的Bean名称。当你有自定义SqlSessionTempate的需要时,可以设置。当容器中有多个SqlSessionTemplate时,也可以用这个属性指定具体使用哪一个,不过一般不这样使用,而是使用下面的属性👇🏻。
  6. sqlSessionFactoryRef :指定要注入的 SqlSessionFactory 的 Bean 名称。用于多数据源场景下指定对应的工厂。如果是多数据源,一般是设置这个属性,而不是上一个sqlSessionTemplateRef,因为一般我们也不去主动定义SqlSessionTemplate
  7. factoryBean :自定义创建 Mapper 代理对象的工厂类,默认使用 MapperFactoryBean。可通过继承 MapperFactoryBean 扩展自定义逻辑。

接下来看下MapperScannerRegistrarregisterBeanDefinitions方法,是如何在BeanFactory中注册Bean的定义的。

java 复制代码
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
    // 1. 创建自定义扫描器,设置注册器(Spring 的 BeanDefinitionRegistry)
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    // 2. 省略一堆读取value、basePackages、basePackageClasses等注解属性,并把属性设置到scanner的代码

    // 假装这里是读取注解属性&配置scanner属性的代码
    
    // 3.扫描Mapper&注册
    scanner.scan(StringUtils.toStringArray(basePackages));
}

解释

简单来说,就是创建并配置ClassPathMapperScanner用来扫描并注册Mapper的Bean定义。这个ClassPathMapperScanner继承自Spring的ClassPathBeanDefinitionScanner,复用了他的包扫描能力,重写了doScan方法:将扫描到的接口转换为MapperFactoryBean的Bean定义

java 复制代码
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    // 调用 Spring 父类方法扫描包,获取接口的 BeanDefinition
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
    
    if (beanDefinitions.isEmpty()) {
        logger.warn("未扫描到 Mapper 接口");
    } else {
        // 处理扫描到的 BeanDefinition:转换为 MapperFactoryBean
        processBeanDefinitions(beanDefinitions);
    }
    return beanDefinitions;
}

// 核心处理:将接口的 BeanDefinition 转换为 MapperFactoryBean 的定义
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {  
    GenericBeanDefinition definition;  
    
    for (BeanDefinitionHolder holder : beanDefinitions) {  
        definition = (GenericBeanDefinition) holder.getBeanDefinition();  
        
        // 设置beanDefinition的类是MapperFactoryBean、泛型是Mapper接口类
        definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59  
        definition.setBeanClass(this.mapperFactoryBean.getClass());
        
        // 省略其他配置beanDefinition代码
        boolean explicitFactoryUsed = ...

        // 如果没有设置sqlSessionTemplate、sqlSessionFactory,则设置by类型注入,这样Spring就可以把唯一的sqlSessionTemplate或sqlSessionFactory自动注入了
        if (!explicitFactoryUsed) {  
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);  
        }  
    }  
}

接下来我们看下核心,MapperFactoryBean是如何创建Mapper的

java 复制代码
@Override  
public T getObject() throws Exception {  
    return getSqlSession().getMapper(this.mapperInterface);  
}  
  
@Override  
public Class<T> getObjectType() {  
    return this.mapperInterface;  
}  
  
@Override  
public boolean isSingleton() {  
    return true;  
}

哎呦喂,好些也没啥特别的,指定了类是接口类,单例的,创建方法,和原生的好像也没啥区别,还是使用SqlSession创建Mapper的动态代理类。他怎么就能单例了呢?!

我们看看这个getSqlSession有什么特殊的呢,这个getSqlSession方法,是继承自SqlSessionDaoSupport:

java 复制代码
private SqlSession sqlSession;  
private boolean externalSqlSession;  
  
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {  
    if (!this.externalSqlSession) {  
        this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);  
    }  
}  
  
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {  
    this.sqlSession = sqlSessionTemplate;  
    this.externalSqlSession = true;  
}  
  
public SqlSession getSqlSession() {  
    return this.sqlSession;  
}

注意这两个set方法。如果我们只有单数据源,不需要指定@MapperScan的sqlSessionFactoryRef属性,那么Spring会按照类型,把唯一的SqlSessionFactory自动注入进来。如果没有指定SqlSessionTemplate,那么自动创建一个SqlSessionTemplate作为SqlSession。显然这个SqlSession与原生的DefaultSqlSession不同,这应该是Spring中的Mapper可以单例的核心。

四、SqlSessionFactoryBean & SqlSessionTemplate

1. SqlSessionFactoryBean

在解析SqlSessionTemplate前,我们先看下SqlSessionFactoryBeanSqlSessionFactoryBean也是一个FactoryBean。核心在getObject中。

java 复制代码
@Override  
public SqlSessionFactory getObject() throws Exception {  
    if (this.sqlSessionFactory == null) {  
        afterPropertiesSet();  
    }  
    return this.sqlSessionFactory;  
}

SqlSessionFactory构造在afterPropertiesSet方法中,代码很长,创建了Mybatis的核心配置类Configuration,并配置了很多属性,不过大多数配置与原生的类似,只不过换一种写法。最大的不同,在这里:

java 复制代码
// SqlSessionFactoryBean 内部逻辑
if (this.transactionFactory == null) {
    this.transactionFactory = new SpringManagedTransactionFactory();
}
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));

关键不同点

  1. 事务管理器,是为Spring实现的SpringManagedTransactionFactory
  2. dataSource,使用的是Spring的数据库连接池,这个this.dataSource,就是在配置类那里配置的数据源。

也就是说,核心实现,还是原生的DefaultSqlSessionFactory,但是事务管理器换成了SpringManagedTransactionFactory,数据源换成了Spring的连接池。

2. SqlSessionTemplate

2.1 SqlSessionTemplate

SqlSessionTemplate是Mybatis集成到Spring的核心,他实现了SqlSession接口,可以替换原生的非线程安全的DefaultSqlSession,并且可以与Spring的事务配合,让你可以直接使用Spring的事务管理,而不需要自己手动管理。我们先看下他的构造函数:

java 复制代码
public class SqlSessionTemplate implements SqlSession {
    // 上文提到的SqlSessionFactory
    private final SqlSessionFactory sqlSessionFactory;
    // 执行器类型(SIMPLE/REUSE/BATCH)
    private final ExecutorType executorType;
    // 原生 SqlSession 的代理对象(核心!所有方法调用都会被拦截)
    private final SqlSession sqlSessionProxy;
    // 异常转换器,将 MyBatis 异常转为 Spring 数据访问异常
    private final PersistenceExceptionTranslator exceptionTranslator;

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
                               PersistenceExceptionTranslator exceptionTranslator) {
        // 校验参数
        notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
        notNull(executorType, "Property 'executorType' is required");

        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;
        // 创建 SqlSession 代理对象,拦截所有方法调用
        this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
            SqlSession.class.getClassLoader(),
            new Class[] { SqlSession.class },
            new SqlSessionInterceptor() // 代理处理器
        );
    }
    
    // 所有SqlSession方法都委托给代理对象
    @Override
    public <T> T selectOne(String statement) {
        return sqlSessionProxy.selectOne(statement);
    }
}

2.2 SqlSessionInterceptor

好家伙,又是JDK的动态代理,也就是说,核心实现在SqlSessionInterceptor中。

java 复制代码
private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 1. 获取SqlSession
        SqlSession sqlSession = getSqlSession(
            SqlSessionTemplate.this.sqlSessionFactory,
            SqlSessionTemplate.this.executorType,
            SqlSessionTemplate.this.exceptionTranslator
        );
        try {
            // 2. 调用原生 SqlSession 的目标方法(如 selectOne、update)
            Object result = method.invoke(sqlSession, args);
            // 3. 非事务场景下自动提交
            if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                sqlSession.commit(true); // 自动提交
            }
            return result;
        } catch (Throwable t) {
            // 4. 处理异常:转为 Spring 数据访问异常
            Throwable unwrapped = unwrapThrowable(t);
            if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                sqlSession = null;
                // 转换异常(如将 MyBatis 的 SqlException 转为 Spring 的 DataAccessException)
                Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
                if (translated != null) {
                    unwrapped = translated;
                }
            }
            throw unwrapped;
        } finally {
            // 5. 释放资源:非事务场景下关闭 SqlSession
            if (sqlSession != null) {
                closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            }
        }
    }
}
关键步骤解析:
1. 获取线程绑定的 SqlSessiongetSqlSession 方法)

先看下getSqlSession方法,是如何获取线程安全的SqlSession:

java 复制代码
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
                                       PersistenceExceptionTranslator exceptionTranslator) {
    // 从线程上下文获取 SqlSessionHolder(封装了 SqlSession)
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
        return session; // 获取线程中的sqlSession
    }

    // 当前线程中没有:创建新的 SqlSession
    session = sessionFactory.openSession(executorType);
    // 将新SqlSession绑定到线程
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
    return session;
}

简单说,就是如果存在,就直接返回SqlSession对象,没有则通过SqlSessionFactory创建新的SqlSession,并绑定到线程中。

看下TrasactionSynchronizationManagergetResource方法,从ThreadLocal中获取,原生的SqlSession虽然不是线程安全的,但这里保证了一个SqlSession只被一个线程使用,也就达到了线程安全的目的。

java 复制代码
private static final ThreadLocal<Map<Object, Object>> resources =  
new NamedThreadLocal<Map<Object, Object>>("Transactional resources");

public static Object getResource(Object key) {  
    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);  
    Object value = doGetResource(actualKey);  
    if (value != null && logger.isTraceEnabled()) {  
        logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +  
        Thread.currentThread().getName() + "]");  
    }  
    return value;  
}

在看下注册SqlSession注册到线程的代码:

java 复制代码
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,  
PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {  
    SqlSessionHolder holder;  
    // 开启事务才会注册到TransactionSynchronizationManager
    if (TransactionSynchronizationManager.isSynchronizationActive()) {  
        //省略非核心代码
        holder = new SqlSessionHolder(session, executorType, exceptionTranslator);  
        TransactionSynchronizationManager.bindResource(sessionFactory, holder);  
        //注册同步器
        TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));  
        holder.setSynchronizedWithTransaction(true);  
        holder.requested();
    }  
    } else {  
        LOGGER.debug(() -> "SqlSession [" + session  
        + "] was not registered for synchronization because synchronization is not active");  
    }  
  
}

注意:开启事务,才会绑定到TransactionSynchronizationManager的线程变量中,否则只是打印个日志

2. 事务判断与自动提交 / 回滚

回到SqlSessionInterceptorinvoke方法,获取SqlSession,执行SqlSession目标方法后:

  • 若处于事务中:不自动提交,由 Spring 事务管理器在事务结束时统一提交 / 回滚。
  • 若不处于事务中:方法执行成功后自动提交(sqlSession.commit(true))。

那么如何判断是否在事务中呢:

java 复制代码
private static boolean isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) {
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    return holder != null && holder.getSqlSession() == session;
}

就是判断TransactionSynchronizationManager是否存在SqlSessionHolder,上文中已经分析了,如果不存在SqlSession,会自动创建,但只有在Spring的事务中,才会绑定到TransactionSynchronizationManager。因此靠判断TransactionSynchronizationManager是否存在SqlSessionHolder,就可以判断是否在事务内。

3. 资源释放(closeSqlSession 方法)

SqlSessionInterceptorinvoke方法,在异常和finally代码块中,会执行closeSqlSession方法,乍看起来很奇怪,因为如果在事务中,SqlSession是共享的,怎么可以随意关闭呢,我们看下源码:

java 复制代码
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    if (holder != null && holder.getSqlSession() == session) {
        holder.released(); // 事务内:仅标记释放(计数减一),不关闭
    } else {
        session.close(); // 非事务:直接关闭
    }
}

破案了,只有在非事务场景下,才会执行关闭,否则只是released标记释放(计数减一),不关闭。

2.3 与Spring事务结合

到此为止,已经能够解释,为什么Spring中的Mapper可以是单例的了,因为他依赖的不是原生的SqlSession,而是SqlSessionTemplate,对原生的SqlSession进行了代理:

  • 在Spring的事务内,第一次执行时创建SqlSession,并通过ThreadLocal机制,事务内共享一个SqlSession
  • 非事务内,每次执行创建新的SqlSession,执行后直接关闭。

那么问题来了,SqlSession的创建看起来没什么特别的,那Mybatis与Spring的事务是如何结合起来的呢?

疑问解答
1. SpringManagedTransactionFactory

还记得SpringManagedTransactionFactory么,就是我们在解析SqlSessionFactoryBean时,创建的SqlSessionFactory与原生的核心不同点,就是它的TransactionFactory是专门为Spring环境实现的SpringManagedTransactionFactory,而不是原生的ManagedTransactionFactory

而这个Factory,创建的是这样的一个事务:

java 复制代码
@Override  
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {  
    return new SpringManagedTransaction(dataSource);  
}

这个SpringManagedTransactiongetConnection()方法,会先检查Spring事务是否已绑定连接,若有则复用,否则创建新连接

java 复制代码
public class SpringManagedTransaction implements Transaction {
    private final DataSource dataSource; // Spring 管理的数据源
    private Connection connection; // 数据库连接(复用 Spring 事务的连接)
    private boolean isConnectionTransactional; // 是否处于 Spring 事务中
    private boolean autoCommit; // 连接的自动提交状态

    @Override
    public Connection getConnection() throws SQLException {
        if (this.connection == null) {
            openConnection(); // 首次调用时获取连接
        }
        return this.connection;
    }

    private void openConnection() throws SQLException {
        // 1. 从 Spring 事务同步管理器获取与数据源绑定的连接持有者
        ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
        
        if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
            // 2. 存在连接持有者:复用 Spring 事务的连接
            this.connection = conHolder.getConnection();
            this.isConnectionTransactional = true; // 标记为事务内连接
            this.autoCommit = this.connection.getAutoCommit(); // 记录原始自动提交状态
        } else {
            // 3. 不存在连接持有者:非事务场景,直接从数据源获取新连接
            this.connection = DataSourceUtils.getConnection(dataSource);
            this.isConnectionTransactional = false;
            this.autoCommit = this.connection.getAutoCommit();
        }
    }
}

Spring的事务管理,简单来说,就是在配置的事务开启位置,获取JDBC的Connection,并设置他的autoCommit为false,再把Connection绑定到线程变量中,之后的数据库操作都使用同一个Connection,也就达到了事务自动管理的目的。

另外SpringManagedTransaction的提交与回滚,也只在非事务的场景下执行:

java 复制代码
    @Override
    public void commit() throws SQLException {
        // 事务内:不执行提交(由 Spring 事务管理器统一提交)
        if (this.connection != null && !isConnectionTransactional && !autoCommit) {
            // 非事务场景:执行提交(此时连接是 MyBatis 自己创建的)
            connection.commit();
        }
    }
    @Override
    public void rollback() throws SQLException {
        // 事务内:不执行回滚(由 Spring 事务管理器统一回滚)
        if (this.connection != null && !isConnectionTransactional && !autoCommit) {
            // 非事务场景:执行回滚
            connection.rollback();
        }
    }
2. SqlSessionSynchronization

SpringManagedTransaction在Spring的事务内,是不会真正提交&回滚的,那Mybatis如何感知事务提交呢,比如Mybatis的二级缓存,是提交后才会生效的,如果不感知事务提交,二级缓存不就挂了么?!

还记得在创建新的SqlSession后,如果在Spring事务内,注册到TransactionSynchronizationManager的那段代码么:

java 复制代码
//绑定SqlSession
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);  
TransactionSynchronizationManager.bindResource(sessionFactory, holder);  
//注册同步器
TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));  
holder.setSynchronizedWithTransaction(true);  
holder.requested();

这里除了SqlSession外,还注册了个同步器SqlSessionSynchronization,这个同步器实现了Spring的TransactionSynchronization接口,在事务提交或者回滚后,会调用afterCompletion方法,这也就达到了感知事务结束的目的:

java 复制代码
private static class SqlSessionSynchronization implements TransactionSynchronization {
    private final SqlSessionHolder holder;
    private final SqlSessionFactory sessionFactory;
    private boolean holderActive = true;

    @Override
    public void afterCompletion(int status) {
        // 移除线程绑定的 SqlSessionHolder
        TransactionSynchronizationManager.unbindResource(sessionFactory);
        holderActive = false;
        // 关闭 SqlSession(将连接归还给连接池)
        SqlSessionUtils.closeSqlSession(holder.getSqlSession(), sessionFactory);
    }

    // 其他回调方法(如 suspend/resume,处理线程挂起/恢复时的资源管理)
    @Override
    public void suspend() {
        if (holderActive) {
            TransactionSynchronizationManager.unbindResource(sessionFactory);
        }
    }

    @Override
    public void resume() {
        if (holderActive) {
            TransactionSynchronizationManager.bindResource(sessionFactory, holder);
        }
    }
}

五、总结

总结一下,Mybatis集成到Spring的实现:

  1. @MapperScan : 通过@MapperScan --> ImportBeanDefinitionRegistrar --> ClassPathMapperScanner --> MapperFactoryBean创建Mapper对象,注册到Spring容器中。而这些Mapper,引用的SqlSessionSqlSessionTemplate
  2. SqlSessionTemplateSqlSessionTemplate对原生SqlSession做了动态代理。
    • 非事务场景 :每次执行使用SqlSessionFactory创建新的SqlSession,执行后直接关闭。
    • 事务场景 :第一次执行时,新创建SqlSession,同时把SqlSession绑定到TransactionSynchronizationManager的线程变量中,之后每次都从这个线程变量中获取,保证一个事务内使用相同的SqlSession
  3. SqlSessionSynchronization :事务内,第一次创建SqlSession时,还注册了SqlSessionSynchronization,可以在Spring执行事务结束后,收到回调,执行afterCompletion方法,用于关闭SqlSession
  4. SpringManagedTransactionSqlSessionFactoryBean创建的SqlSessionFactory,配置了特殊的事务管理器(SpringManagedTransaction),可以从Spring事务的线程变量中,获取Connection,进而达到在Spring的事务共用一个Connection的目的。
相关推荐
HyggeBest1 分钟前
Golang 并发原语 Sync Pool
后端·go
Java水解2 分钟前
【RabbitMq C++】消息队列组件
后端·rabbitmq
the beard2 分钟前
深入理解Java多线程:状态、安全、同步与通信
java·开发语言
pengzhuofan15 分钟前
Java设计模式-享元模式
java·设计模式·享元模式
灵魂猎手20 分钟前
10. Mybatis XML配置到SQL的转换之旅
java·后端·源码
掉鱼的猫21 分钟前
10分钟带你体验 Solon 的状态机
java
用户40993225021223 分钟前
如何让FastAPI在百万级任务处理中依然游刃有余?
后端·ai编程·trae
汪子熙23 分钟前
解决 Node.js 无法获取本地颁发者证书问题的详细分析与代码示例
javascript·后端
武子康24 分钟前
大数据-76 Kafka 从发送到消费:Kafka 消息丢失/重复问题深入剖析与最佳实践
大数据·后端·kafka
笃行35024 分钟前
在TencentOS3上部署OpenTenBase:从入门到实战的完整指南
后端