欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

myBatis系列之七:事务管理

程序员文章站 2022-04-16 15:15:53
...

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

 

参考文章:http://czj4451.iteye.com/blog/2037759