一.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。