编写缘由,发现网上事务级别介绍很多,但真正理解或者运用的感觉不是特别多,特别是结合spring进行使用有不少需要注意点。
我记录一下采坑记录便于以后查询。
Spring事务传播行为网上一堆,不再解释,目前我主要使用spring默认事务级别PROPAGATION_REQUIRES和PROPAGATION_REQUIRES_NEW
传播行为 | 意义 |
---|---|
PROPAGATION_MANDATORY | 表示该方法必须运行在一个事务中。如果当前没有事务正在发生,将抛出一个异常 |
PROPAGATION_NESTED | 表示如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于封装事务进行提交或回滚。如果封装事务不存在,行为就像PROPAGATION_REQUIRES一样。 |
PROPAGATION_NEVER | 表示当前的方法不应该在一个事务中运行。如果一个事务正在进行,则会抛出一个异常。 |
PROPAGATION_NOT_SUPPORTED | 表示该方法不应该在一个事务中运行。如果一个现有事务正在进行中,它将在该方法的运行期间被挂起。 |
PROPAGATION_SUPPORTS | 表示当前方法不需要事务性上下文,但是如果有一个事务已经在运行的话,它也可以在这个事务里运行。 |
PROPAGATION_REQUIRES_NEW | 表示当前方法必须在它自己的事务里运行。一个新的事务将被启动,而且如果有一个现有事务在运行的话,则将在这个方法运行期间被挂起。 |
PROPAGATION_REQUIRES | 表示当前方法必须在一个事务中运行。如果一个现有事务正在进行中,该方法将在那个事务中运行,否则就要开始一个新事务。 |
以下参考网上内容,对事务隔离级别描述比较透彻
不可重复度和幻读区别:
不可重复读的重点是修改,幻读的重点在于新增或者删除。
例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导 致A再读自己的工资时工资变为 2000;这就是不可重复读。
例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读。
SQL 标准定义了四个隔离级别:
隔离级别 | 脏读 | 不可重复读 | 幻影读 |
---|---|---|---|
READ-UNCOMMITTED | √ | √ | √ |
READ-COMMITTED | × | √ | √ |
REPEATABLE-READ | × | × | √ |
SERIALIZABLE | × | × | × |
MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)
代码主要有如下TransactionalServiceImpl、TransactionalService2Impl两个方法
每执行一段SQL语句,事务自动提交,即使抛异常,也不回滚
/**
* 无事务处理将同时插入两条记录也不回滚
*/
@Override
public void insertBatch() {
Test test = new Test();
String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
test.setName("hi:" + format);
testMapper.insertSelective(test);
Test2 test2 = new Test2();
test2.setName("hi2:" + format);
test2Mapper.insertSelective(test2);
throw new RuntimeException("异常");
}
2. 在方法头加上事务注解,只要抛出异常,事务均回滚
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
@Override
public void insert() {
//方法1执行代码
//方法2执行代码
throw new RuntimeException("异常");
}
3. TransactionalServiceImpl加上Propagation.REQUIRED注解,TransactionalService2Impl 加上Propagation.REQUIRED_NEW注解。
若执行顺序为方法1 插入,方法2插入,方法1抛出异常。
结果为:方法1回滚,方法2提交
/**
* 异常
* service1 insert
* service2 insert
* service2 throw exception
* service1 和service2事务均回滚
*
* 异常
* service1 insert
* service2 insert
* service1 throw exception
* service1 回滚,service2不回滚
*/
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
@Override
public void insert() {
Test test = new Test();
String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
test.setName("hi:" + format);
testMapper.insertSelective(test);
transactionalService2.insert();
throw new RuntimeException("异常");
}
4. 在提交事务之前直接提交异步线程,因事务尚未提交,可能会导致异步线程无法查询到数据(该示例情况笔者之前也遇到有同学先插入表直接提交MQ并没有先提交插入表事务,大部分情况因MQ消费慢,这种BUG比较难以测试出来,当MQ没有其他消息,马上被消费者消费时就会出现无法找到插入数据情况)。
insertTest() 如果不作为单独方法调用,直接把 testMapper.insertSelective(test) 写入到insertOkAsync,因为在提交异步线程之前insertOkAsync方法有可能还未执行完毕,事务还未提交,asyncJob 无法查询到数据
/**
* 方法中执行insert 在新方法直接提交异步线程
*/
@Override
public void insertOkAsync() {
Long id = insertTest();
transactionalService2.asyncJob(id);
System.out.println("after insert2.1" + new Date());
}
@Transactional(rollbackFor = Exception.class)
@Override
public Long insertTest() {
Test test = new Test();
String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
test.setName("hi:" + format);
testMapper.insertSelective(test);
System.out.println("after insert1" + new Date());
return test.getId();
}
//TransactionalService2Impl
@Async("asyncServiceExecutor")
public void asyncJob(Long id) {
/*try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
Test test = testMapper.selectByPrimaryKey(id);
System.out.println("GET TEST2:"+test+new Date());
}
5. 同一个方法内,事务切面不起作用
方法1 Propagation.REQUIRES_NEW事务,方法2 Propagation.REQUIRES_NEW事务。 调用方法2相当于直接调用,并不能新启动事务。方法1 抛出异常后,方法1和方法2均回滚
@Transactional(rollbackFor = Exception.class)
@Override
public void insertSecond() {
Test test = new Test();
String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
test.setName("hi:" + format);
testMapper.insertSelective(test);
}
/**
* 测试service1 填写注解,同方法调用
* 由于insertRequiresNew抛出异常,并且 insertRequiresNew和insert2为同一个service,数据均回滚
*/
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
@Override
public void insertRequiresNew() {
Test test = new Test();
String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
test.setName("hi:" + format);
testMapper.insertSelective(test);
insert2();
throw new RuntimeException("异常");
}
package com.test.springtest.transaction.service.impl;
import com.test.springtest.transaction.domain.Test;
import com.test.springtest.transaction.domain.Test2;
import com.test.springtest.transaction.mapper.Test2Mapper;
import com.test.springtest.transaction.mapper.TestMapper;
import com.test.springtest.transaction.service.TransactionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* service
*
* @author junqiang.xiao
* @date 2019/4/22 下午2:19
*/
@Service
public class TransactionalServiceImpl implements TransactionService {
@Autowired
private TestMapper testMapper;
@Autowired
private Test2Mapper test2Mapper;
@Autowired
private TransactionalService2Impl transactionalService2;
@Autowired
private ApplicationContext applicationContext;
/**
* 异常
* service1 insert
* service2 insert
* service2 throw exception
* service1 和service2事务均回滚
*
* 异常
* service1 insert
* service2 insert
* service1 throw exception
* service1 回滚,service2不回滚
*/
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
@Override
public void insert() {
Test test = new Test();
String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
test.setName("hi:" + format);
testMapper.insertSelective(test);
transactionalService2.insert();
throw new RuntimeException("异常");
}
/**
* 无事务处理将同时插入两条记录也不回滚
*/
@Override
public void insertBatch() {
Test test = new Test();
String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
test.setName("hi:" + format);
testMapper.insertSelective(test);
Test2 test2 = new Test2();
test2.setName("hi2:" + format);
test2Mapper.insertSelective(test2);
throw new RuntimeException("异常");
}
/**
* 测试在数据库提交线程之前,线程执行情况
* 测试结果
* 1. 若数据库commit之前,线程已经执行,会查询不到最新记录
* 2. Async 在同一个方法中不起作用,同理为Transaction
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void insertAsync() {
Test test = new Test();
String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
test.setName("hi:" + format);
testMapper.insertSelective(test);
System.out.println("after insert1" + new Date());
transactionalService2.asyncJob(test.getId());
System.out.println("after insert1.1" + new Date());
//asyncJob(test.getId()) //该方式无法正常切入切面,因为是同方法调用aop this指的是当前类而不是代理类
TransactionService self2 = applicationContext.getBean(TransactionService.class);
self2.asyncJob(test.getId());
System.out.println("after insert1.2" + new Date());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Test test2 = new Test();
test2.setName("hi:" + format);
testMapper.insertSelective(test2);
System.out.println("after insert2.1" + new Date());
}
/**
* 方法中执行insert 在新方法直接提交异步线程
*/
@Override
public void insertOkAsync() {
Long id = insertTest();
transactionalService2.asyncJob(id);
System.out.println("after insert2.1" + new Date());
}
@Transactional(rollbackFor = Exception.class)
@Override
public Long insertTest() {
Test test = new Test();
String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
test.setName("hi:" + format);
testMapper.insertSelective(test);
System.out.println("after insert1" + new Date());
return test.getId();
}
@Async("asyncServiceExecutor")
@Override
public void asyncJob(Long id) {
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Test test = testMapper.selectByPrimaryKey(id);
System.out.println("GET TEST1:" + test + new Date());
}
/**
* 事务会传递到内层,直至整个方法执行完毕
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void standardInsertBatch() {
Test test = new Test();
String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
test.setName("hi:" + format);
testMapper.insertSelective(test);
insertSecond();
}
@Transactional(rollbackFor = Exception.class)
@Override
public void insertSecond() {
Test test = new Test();
String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
test.setName("hi:" + format);
testMapper.insertSelective(test);
}
/**
* 测试service1 填写注解,同方法调用
* 由于insertRequiresNew抛出异常,并且 insertRequiresNew和insert2为同一个service,数据均回滚
*/
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
@Override
public void insertRequiresNew() {
Test test = new Test();
String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
test.setName("hi:" + format);
testMapper.insertSelective(test);
insert2();
throw new RuntimeException("异常");
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
@Override
public void insert2() {
Test test = new Test();
String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
test.setName("hi2:" + format);
testMapper.insertSelective(test);
}
}
package com.test.springtest.transaction.service.impl;
import com.test.springtest.transaction.domain.Test;
import com.test.springtest.transaction.domain.Test2;
import com.test.springtest.transaction.mapper.Test2Mapper;
import com.test.springtest.transaction.mapper.TestMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* service2
*
* @author junqiang.xiao
* @date 2019/4/26 下午3:50
*/
@Service
public class TransactionalService2Impl {
@Autowired
private TestMapper testMapper;
@Autowired
private Test2Mapper test2Mapper;
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void insert() {
Test2 test = new Test2();
String format= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
test.setName("hi2:"+format);
test2Mapper.insertSelective(test);
}
@Async("asyncServiceExecutor")
public void asyncJob(Long id) {
/*try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
Test test = testMapper.selectByPrimaryKey(id);
System.out.println("GET TEST2:"+test+new Date());
}
}