事务
一个使用 MyBatis-Spring 的其中一个主要原因是它允许 MyBatis 参与到 Spring 的事务管理中。而不是给 MyBatis 创建一个新的专用事务管理器,MyBatis-Spring 借助了 Spring 中的 DataSourceTransactionManager 来实现事务管理。
一旦配置好了 Spring 的事务管理器,你就可以在 Spring 中按你平时的方式来配置事务。并且支持 @Transactional 注解和 AOP 风格的配置。在事务处理期间,一个单独的 SqlSession 对象将会被创建和使用。当事务完成时,这个 session 会以合适的方式提交或回滚。
事务配置好了以后,MyBatis-Spring 将会透明地管理事务。这样在你的 DAO 类中就不需要额外的代码了。
Spring JDBC事务
标准配置
要开启 Spring 的事务处理功能,在 Spring 的配置文件中创建一个 DataSourceTransactionManager 对象:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource" />
</bean>
@Bean
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
传入的 DataSource 可以是任何能够与 Spring 兼容的 JDBC DataSource。包括连接池和通过 JNDI 查找获得的 DataSource。
注意:为事务管理器指定的 DataSource 必须和用来创建 SqlSessionFactoryBean 的是同一个数据源,否则事务管理器就无法工作了。
如果添加@EnableTransactionManagement注解,并且引入spring-boot-starter-jdbc包,就会引入DataSourceTransactionManager。
Spring使用以下代码打印PlatformTransacationManager的类型,DataSourceTransactionManager为JDBC事务。
@Bean
public Object testBean(PlatformTransactionManager platformTransactionManager) {
System.out.println(">>>>>>>>>>" + platformTransactionManager.getClass().getName());
return new Object();
}
>>>>>>>>>>org.springframework.jdbc.datasource.DataSourceTransactionManager
在配置事务的时候连续执行2次Mapper观察日志,每次执行都是创建一个新的sqlSession。一个sqlSession属于一个事务。
@Override
public void save() {
sysUserMapper.save();
sysUserMapper.save2();
if (true) {
throw new RuntimeException();
}
}
2019-05-30 09:48:14.042 [http-nio-8080-exec-1] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
2019-05-30 09:48:14.046 [http-nio-8080-exec-1] DEBUG org.mybatis.spring.SqlSessionUtils - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1d7ce1f7] was not registered for synchronization because synchronization is not active
2019-05-30 09:48:14.091 [http-nio-8080-exec-1] INFO com.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited
2019-05-30 09:48:14.660 [http-nio-8080-exec-1] DEBUG o.m.spring.transaction.SpringManagedTransaction - JDBC Connection [com.mysql.jdbc.JDBC4Connection@3f73faa9] will not be managed by Spring
2019-05-30 09:48:14.663 [http-nio-8080-exec-1] DEBUG com.pss.mybatis_spring.dao.SysUserMapper.save - ==> Preparing: insert into sys_user(user_id, user_name) values(-998, '用户1')
2019-05-30 09:48:14.681 [http-nio-8080-exec-1] DEBUG com.pss.mybatis_spring.dao.SysUserMapper.save - ==> Parameters:
2019-05-30 09:48:14.751 [http-nio-8080-exec-1] DEBUG com.pss.mybatis_spring.dao.SysUserMapper.save - <== Updates: 1
2019-05-30 09:48:14.751 [http-nio-8080-exec-1] DEBUG org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1d7ce1f7]
2019-05-30 09:48:14.752 [http-nio-8080-exec-1] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
2019-05-30 09:48:14.752 [http-nio-8080-exec-1] DEBUG org.mybatis.spring.SqlSessionUtils - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@28b107b9] was not registered for synchronization because synchronization is not active
2019-05-30 09:48:14.752 [http-nio-8080-exec-1] DEBUG o.m.spring.transaction.SpringManagedTransaction - JDBC Connection [com.mysql.jdbc.JDBC4Connection@3f73faa9] will not be managed by Spring
2019-05-30 09:48:14.752 [http-nio-8080-exec-1] DEBUG com.pss.mybatis_spring.dao.SysUserMapper.save2 - ==> Preparing: insert into sys_user(user_id, user_name) values(-997, '用户2')
2019-05-30 09:48:14.752 [http-nio-8080-exec-1] DEBUG com.pss.mybatis_spring.dao.SysUserMapper.save2 - ==> Parameters:
2019-05-30 09:48:14.822 [http-nio-8080-exec-1] DEBUG com.pss.mybatis_spring.dao.SysUserMapper.save2 - <== Updates: 1
2019-05-30 09:48:14.822 [http-nio-8080-exec-1] DEBUG org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@28b107b9]
添加了@Transactional后在save()方法内就属于一个事务了,当事务完成sqlSessoin就会提交或回滚。jdbc事务配置成功。
@Transactional
@Override
public void save() {
sysUserMapper.save();
sysUserMapper.save2();
if (true) {
throw new RuntimeException();
}
}
2019-05-30 09:53:36.452 [http-nio-8080-exec-4] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
2019-05-30 09:53:36.452 [http-nio-8080-exec-4] DEBUG org.mybatis.spring.SqlSessionUtils - Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@437d1c47]
2019-05-30 09:53:36.453 [http-nio-8080-exec-4] DEBUG o.m.spring.transaction.SpringManagedTransaction - JDBC Connection [com.mysql.jdbc.JDBC4Connection@5e898c28] will be managed by Spring
2019-05-30 09:53:36.454 [http-nio-8080-exec-4] DEBUG com.pss.mybatis_spring.dao.SysUserMapper.save - ==> Preparing: insert into sys_user(user_id, user_name) values(-998, '用户1')
2019-05-30 09:53:36.454 [http-nio-8080-exec-4] DEBUG com.pss.mybatis_spring.dao.SysUserMapper.save - ==> Parameters:
2019-05-30 09:53:36.517 [http-nio-8080-exec-4] DEBUG com.pss.mybatis_spring.dao.SysUserMapper.save - <== Updates: 1
2019-05-30 09:53:36.517 [http-nio-8080-exec-4] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@437d1c47]
2019-05-30 09:53:36.517 [http-nio-8080-exec-4] DEBUG org.mybatis.spring.SqlSessionUtils - Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@437d1c47] from current transaction
2019-05-30 09:53:36.517 [http-nio-8080-exec-4] DEBUG com.pss.mybatis_spring.dao.SysUserMapper.save2 - ==> Preparing: insert into sys_user(user_id, user_name) values(-997, '用户2')
2019-05-30 09:53:36.517 [http-nio-8080-exec-4] DEBUG com.pss.mybatis_spring.dao.SysUserMapper.save2 - ==> Parameters:
2019-05-30 09:53:36.580 [http-nio-8080-exec-4] DEBUG com.pss.mybatis_spring.dao.SysUserMapper.save2 - <== Updates: 1
2019-05-30 09:53:36.580 [http-nio-8080-exec-4] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@437d1c47]
2019-05-30 09:53:36.580 [http-nio-8080-exec-4] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@437d1c47]
2019-05-30 09:53:36.580 [http-nio-8080-exec-4] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@437d1c47]
2019-05-30 09:53:36.682 [http-nio-8080-exec-4] ERROR o.a.c.c.C.[.[localhost].[/].[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException] with root cause
注:集成Springboot话不需要配置也能生效,否则需要配置扫描事务AOP
<context:component-scan base-package="com.pss"/>
<tx:advice id="advice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut expression="execution(* com.pss.helloworld.service.impl..*.*(..))" id="pointcut"/>
<aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
</aop:config>
交由容器管理事务
如果使用多数据源且数据源事务,SysUser会回滚而untiveAdvisor不会回滚,所以需要使用容器管理事务
@Transactional
public void save(SysUser sysUser) {
sysUserMapper.save(sysUser);
unitiveAdvisorMapper.save(new UnitiveAdvisor());
if (true) {
throw new RuntimeException("自定义报错");
}
}
如果你正使用一个 JEE 容器而且想让 Spring 参与到容器管理事务(Container managed transactions,CMT)的过程中,那么 Spring 应该被设置为使用 JtaTransactionManager 或由容器指定的一个子类作为事务管理器。最简单的方式是使用 Spring 的事务命名空间或使用 JtaTransactionManagerFactoryBean:
@Bean
public JtaTransactionManager transactionManager() {
return new JtaTransactionManagerFactoryBean().getObject();
}
在这个配置中,MyBatis 将会和其它由容器管理事务配置的 Spring 事务资源一样。Spring 会自动使用任何一个存在的容器事务管理器,并注入一个 SqlSession。如果没有正在进行的事务,而基于事务配置需要一个新的事务的时候,Spring 会开启一个新的由容器管理的事务。
接下来试验一下
创建XA协议数据源和SessionFactory
<bean id="xaDataSource" class="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource">
<property name="url" value="jdbc:mysql://localhost:3306/db1"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
</bean>
<bean id="xaDataSource2" class="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource">
<property name="url" value="jdbc:mysql://localhost:3306/db2"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
</bean>
<bean id="dataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean">
<property name="xaDataSource" ref="xaDataSource"/>
<property name="uniqueResourceName" value="dataSource"/>
</bean>
<bean id="dataSource2" class="com.atomikos.jdbc.AtomikosDataSourceBean">
<property name="xaDataSource" ref="xaDataSource2"/>
<property name="uniqueResourceName" value="dataSource2"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath*:mapper/*.xml"/>
<property name="configuration">
<bean class="org.apache.ibatis.session.Configuration">
<property name="mapUnderscoreToCamelCase" value="true"/>
</bean>
</property>
</bean>
<bean id="sqlSessionFactory2" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource2"/>
<property name="mapperLocations" value="classpath*:mapper2/*.xml"/>
<property name="configuration">
<bean class="org.apache.ibatis.session.Configuration">
<property name="mapUnderscoreToCamelCase" value="true"/>
</bean>
</property>
</bean>
创建transactionMananger
<bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close">
<description>UserTransactionManager</description>
<property name="forceShutdown">
<value>true</value>
</property>
</bean>
<bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
<property name="transactionTimeout" value="90000" />
</bean>
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager">
<ref bean="atomikosTransactionManager" />
</property>
<property name="userTransaction">
<ref bean="atomikosUserTransaction" />
</property>
<property name="allowCustomIsolationLevels" value="true" />
</bean>
<bean id="transactionManager2" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager">
<ref bean="atomikosTransactionManager" />
</property>
<property name="userTransaction">
<ref bean="atomikosUserTransaction" />
</property>
<property name="allowCustomIsolationLevels" value="true" />
</bean>
@Transactional
public void save(SysUser sysUser) {
sysUserMapper.save(sysUser);
unitiveAdvisorMapper.save(new UnitiveAdvisor());
if (true) {
throw new RuntimeException("自定义报错");
}
}
[springmvc_mybatis_demo][DEBUG] [2019-06-01 20:22:42] com.atomikos.logging.Slf4jLogger.logDebug(32) | XAResource.rollback ( 3139322E3136382E332E33352E746D313535393339313736303631333030303031:3139322E3136382E332E33352E746D31 ) on resource dataSource represented by XAResource instance com.mysql.jdbc.jdbc2.optional.JDBC4MysqlXAConnection@5e268ce6
[springmvc_mybatis_demo][DEBUG] [2019-06-01 20:22:42] com.atomikos.logging.Slf4jLogger.logDebug(32) | XAResource.rollback ( 3139322E3136382E332E33352E746D313535393339313736303631333030303031:3139322E3136382E332E33352E746D32 ) on resource dataSource2 represented by XAResource instance com.mysql.jdbc.jdbc2.optional.JDBC4MysqlXAConnection@15bc339
[springmvc_mybatis_demo][DEBUG] [2019-06-01 20:22:42] com.atomikos.logging.Slf4jLogger.logDebug(32) | rollback() done of transaction 192.168.3.35.tm155939176061300001
[springmvc_mybatis_demo][DEBUG] [2019-06-01 20:22:42] com.atomikos.logging.Slf4jLogger.logDebug(32) | rollback() done of transaction 192.168.3.35.tm155939176061300001
看日志2个数据源都一起回滚了。