myBatis系列之七:事务管理
一.myBatis单独使用时,使用SqlSession来处理事务
在前面的MyBatisStudy02工程中,新增MyBatisTxTest.java的测试类。
package com.bijian.study.test; import java.io.Reader; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.BeforeClass; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.bijian.study.dao.IUserMapper; import com.bijian.study.model.User; public class MyBatisTxTest { private static final Logger log = LoggerFactory.getLogger(MyBatisTxTest.class); private static SqlSessionFactory sqlSessionFactory; private static Reader reader; @BeforeClass public static void setUpBeforeClass() throws Exception { try { reader = Resources.getResourceAsReader("Configuration.xml"); sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); } finally { if (reader != null) { reader.close(); } } } @Test public void updateUserTxTest() { SqlSession session = sqlSessionFactory.openSession(false); // 打开会话,事务开始 try { IUserMapper mapper = session.getMapper(IUserMapper.class); User user = new User(1, "Test transaction"); user.setName("bijian2"); user.setAge(18); int affectedCount = mapper.updateUser(user); // 因后面的异常而未执行commit语句 log.info("{} new record was inserted successfully whose id: {}", affectedCount, user.getId()); User user2 = new User(1, "Test transaction continuously"); user2.setName("bijian3"); user2.setAge(28); int affectedCount2 = mapper.updateUser(user2); // 因后面的异常而未执行commit语句 log.info("{} new record was inserted successfully whose id: {}", affectedCount2, user2.getId()); int i = 2 / 0; // 触发运行时异常 session.commit(); // 提交会话,即事务提交 } finally { session.close(); // 关闭会话,释放资源 } } }
测试前数据库表user表的数据如下:
mysql> select * from user; +----+--------+------+-------------------+ | id | name | age | address | +----+--------+------+-------------------+ | 1 | bijian | 120 | hangzhou,westlake | +----+--------+------+-------------------+ 1 row in set (0.00 sec)
运行上面的单元测试,抛出java.lang.ArithmeticException:/ by zero异常。控制台输出如下内容:
21:29:59.797 [main] INFO com.bijian.study.test.MyBatisTxTest - 1 new record was inserted successfully whose id: 1 21:29:59.799 [main] INFO com.bijian.study.test.MyBatisTxTest - 1 new record was inserted successfully whose id: 1
此时我们再查看数据库表user表的数据如下:
mysql> select * from user; +----+--------+------+-------------------+ | id | name | age | address | +----+--------+------+-------------------+ | 1 | bijian | 120 | hangzhou,westlake | +----+--------+------+-------------------+ 1 row in set (0.01 sec)
二.和Spring集成后,使用Spring的事务管理
1.@Transactional方式
将工程MyBatisStudy03工程/WEB-INF/下的applicationContext.xml拷贝到类路径下,加入如下事务配置:
<!-- 事务管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 事务注解驱动,标注@Transactional的类和方法将具有事务性 --> <tx:annotation-driven transaction-manager="txManager" /> <bean id="userService" class="com.bijian.study.service.UserService" />
服务类:
package com.bijian.study.service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; import com.bijian.study.dao.IUserMapper; import com.bijian.study.model.User; @Service("userService") public class UserService { private static final Logger log = LoggerFactory.getLogger(UserService.class); @Autowired private IUserMapper mapper; public int batchUpdateUsersWhenException() { // 非事务性 User user = new User(1, "Before exception"); int affectedCount = mapper.updateUser(user); // 执行成功 log.info("{} new record was inserted successfully whose id: {}", affectedCount, user.getId()); User user2 = new User(1, "After exception"); int i = 1 / 0; // 抛出运行时异常 int affectedCount2 = mapper.updateUser(user2); // 未执行 log.info("{} new record was inserted successfully whose id: {}", affectedCount2, user.getId()); if (affectedCount == 1 && affectedCount2 == 1) { return 1; } return 0; } @Transactional public int txUpdateUsersWhenException() { // 事务性 User user = new User(1, "Before exception"); int affectedCount = mapper.updateUser(user); // 因后面的异常而回滚 log.info("{} new record was inserted successfully whose id: {}", affectedCount, user.getId()); User user2 = new User(1, "After exception"); int i = 1 / 0; // 抛出运行时异常,事务回滚 int affectedCount2 = mapper.updateUser(user2); // 未执行 log.info("{} new record was inserted successfully whose id: {}", affectedCount2, user.getId()); if (affectedCount == 1 && affectedCount2 == 1) { return 1; } return 0; } }
SpringIntegrateTxTest测试类如下:
package com.bijian.study.test; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.bijian.study.service.UserService; public class SpringIntegrationTest { private static ApplicationContext ctx; static { ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); } @Test public void updateUsersExceptionTest() { UserService userService = (UserService) ctx.getBean("userService"); userService.batchUpdateUsersWhenException(); } @Test public void txUpdateUsersExceptionTest() { UserService userService = (UserService) ctx.getBean("userService"); userService.txUpdateUsersWhenException(); } }
运行单元测试updateUsersExceptionTest前数据库表user中的数据如下:
mysql> select * from user; +----+--------+------+-------------------+ | id | name | age | address | +----+--------+------+-------------------+ | 1 | bijian | 120 | hangzhou,westlake | +----+--------+------+-------------------+ 1 row in set (0.00 sec)
运行单元测试updateUsersExceptionTest后数据库表user中的数据如下:
mysql> select * from user; +----+------+------+------------------+ | id | name | age | address | +----+------+------+------------------+ | 1 | NULL | 0 | Before exception | +----+------+------+------------------+ 1 row in set (0.00 sec)
恢复数据库表user中的数据如下:
mysql> select * from user; +----+--------+------+-------------------+ | id | name | age | address | +----+--------+------+-------------------+ | 1 | bijian | 120 | hangzhou,westlake | +----+--------+------+-------------------+ 1 row in set (0.00 sec)
运行单元测试txUpdateUsersExceptionTest后数据库表user中的数据如下:
mysql> select * from user; +----+--------+------+-------------------+ | id | name | age | address | +----+--------+------+-------------------+ | 1 | bijian | 120 | hangzhou,westlake | +----+--------+------+-------------------+ 1 row in set (0.00 sec)
从上面的单元测试结果不难发现txUpdateUsersWhenException方法上面因注解了@Transactional事务生效了。
2.TransactionTemplate方式
在上面的application.xml中添加:
<bean id="txTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <constructor-arg type="org.springframework.transaction.PlatformTransactionManager" ref="txManager" /> </bean>
在UserService类加上如下内容:
@Autowired(required = false) private TransactionTemplate txTemplate; public int txUpdateUsersWhenExceptionViaTxTemplate() { int retVal = txTemplate.execute(new TransactionCallback<Integer>() { @Override public Integer doInTransaction(TransactionStatus status) { // 事务操作 User user = new User(1, "Before exception"); int affectedCount = mapper.updateUser(user); // 因后面的异常而回滚 User user2 = new User(1, "After exception"); int i = 1 / 0; // 抛出运行时异常并回滚 int affectedCount2 = mapper.updateUser(user2); // 未执行 if (affectedCount == 1 && affectedCount2 == 1) { return 1; } return 0; } }); return retVal; }
在SpringIntegrateTxTest类中加上如下测试方法:
@Test public void updateUsersWhenExceptionViaTxTemplateTest() { UserService userService = (UserService) ctx.getBean("userService"); userService.txUpdateUsersWhenExceptionViaTxTemplate(); }
运行单元测试updateUsersWhenExceptionViaTxTemplateTest前数据库表user中的数据如下:
mysql> select * from user; +----+--------+------+-------------------+ | id | name | age | address | +----+--------+------+-------------------+ | 1 | bijian | 120 | hangzhou,westlake | +----+--------+------+-------------------+ 1 row in set (0.00 sec)
运行单元测试updateUsersWhenExceptionViaTxTemplateTest后数据库表user中的数据如下,没有发生变化:
mysql> select * from user; +----+--------+------+-------------------+ | id | name | age | address | +----+--------+------+-------------------+ | 1 | bijian | 120 | hangzhou,westlake | +----+--------+------+-------------------+ 1 row in set (0.00 sec)
说明txUpdateUsersWhenExceptionViaTxTemplate方法因抛出运行时异常事务发生了回滚。
PS:
1.在有@Transactional注解的方法中,不可catch异常后不抛出
在有@Transactional注解的方法中,不可catch异常后不抛出,如不抛出,外围框架捕获不到异常,认为执行正确而提交。
@Transactional public int txUpdateUsersWhenExceptionAndCatch() { // 事务性操作,但是外围框架捕获不到异常,认为执行正确而提交。 try { User user = new User(9, "Before exception"); int affectedCount = mapper.updateUser(user); // 执行成功 User user2 = new User(10, "After exception"); int i = 1 / 0; // 抛出运行时异常 int affectedCount2 = mapper.updateUser(user2); // 未执行 if (affectedCount == 1 && affectedCount2 == 1) { return 1; } } catch (Exception e) { // 所有异常被捕获而未抛出 e.printStackTrace(); } return 0; }
2.The prefix "tx" for element "tx:annotation-driven " is not bound
配置spring是碰到tx:annotation-driven is not bound 的问题,这个错误的原因很简单是在定义申明AOP的时候没有加载schema。修改applicationContext.xml配置文件头如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
3.运行时抛出java.lang.NoClassDefFoundError: org/aopalliance/intercept/MethodInterceptor异常
原因是缺少包aopalliance-1.0.jar。
推荐阅读
-
JAVA/JSP学习系列之七
-
荐 JavaScript之闭包,给自己的Js一场重生(系列七)
-
Spring3MVC+MyBatis+ExtJs3整合开发系列之四:角色管理模块
-
Python猫荐书系列之七:Python入门书籍有哪些?
-
DPDK系列之七:OVS-DPDK的在CentOS安装和测试
-
设计模式系列之组合模式及其在JDK和MyBatis源码中的运用详解
-
MySQL系列之七 MySQL存储引擎
-
MyBatis从入门到精通(七):MyBatis动态Sql之choose,where,set标签的用法
-
带你从零学大数据系列之Java篇---第七章:面向对象三大特性
-
MarTech观察系列之七|MarTech的塑造与赋能