Spring事务相关笔记
事务的四大特征
- 原子性:事务中的全部操作在数据库中是不可分割的,要么全部执行,要么均不执行
- 一致性:几个并行的事务,其执行结果必须与按某一顺序串行执行的结果相一致
- 隔离性:事务的执行不受其他事务的干扰,事务执行的中间结果对其他事务必须透明
- 持久性:对于已提交的事务,系统必须保证该事务对数据库的改变不被丢失
事务使用
表结构示例:
drop table student;
create table `student`(
`id` int unsigned auto_increment,
`name` varchar(64),
`age` int unsigned,
primary key (`id`)
) engine=InnoDB default charset=utf8mb4;
实体类:
package com.alone.community.transaction.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
/**
* @author Alone
*/
@Data
@Entity
public class Student {
@Id
@GeneratedValue
private Integer id;
private String name;
private Integer age;
public Student() {
}
public Student(Integer id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
dao:
@Repository
public interface StudentRepository extends JpaRepository<Student, Integer> {
}
service:
public interface StudentService {
Student saveStudent(Student student);
}
@Service
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentRepository studentRepository;
@Override
@Transactional(rollbackFor = {Exception.class})
public Student saveStudent(Student student) {
Student save = studentRepository.save(student);
if("Alone".equals(save.getName())) {
throw new RuntimeException("Alone已存在!");
}
return save;
}
}
controller:
@RestController
@RequestMapping("/student")
public class StudentController {
@Autowired
private StudentService studentService;
@RequestMapping("/savestudent")
public Student saveStudent(@RequestBody Student student) {
return studentService.saveStudent(student);
}
}
事务注解@Transactional的常见参数
参数 | 作用 |
---|---|
value | 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器 |
transactionManager | 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器 |
propagation | 事务的传播级别,默认值为Proppagation.REQUIRED |
isolation | 事务的隔离级别,默认值为Isolation.DEFAULT |
timeout | 事务超时时间,默认值为-1,如果超过该时间限制但事务还没有完成,则自动回滚事务 |
rollbackFor | 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型 |
事务隔离级别
参数 | 事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
READ_UNCOMMITTED | 读未提交(read-uncommitted) | 是 | 是 | 是 |
READ_COMMITTED | 不可重复读(read-committed) | 否 | 是 | 是 |
REPEATABLE_READ | 可重复读(repeatable-read) | 否 | 否 | 是 |
SERIALIZABLE | 串行化(serializable)) | 否 | 否 | 否 |
事务传播级别
传播行为 | 解释 |
---|---|
REQUIRED | 如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务 |
SUPPORTS | 如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行 |
MANDATORY | 如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常 |
REQUIRES_NEW | 重新创建一个新的事务,如果当前存在事务,暂停当前的事务 |
NOT_SUPPORTED | 以非事务的方式运行,如果当前存在事务,暂停当前的事务 |
NEVER | 以非事务的方式运行,如果当前存在事务,则抛出异常 |
NESTED | 和REQUIRED效果一样 |
事务失效的场景
- 访问权限问题
spring要求被代理的方法必须是public
的。
在源码的AbstractFallbackTransactionAttributeSource
类的computeTransactionAttribute
方法中判断了,如果目标方法不是public,则TransactionAttribute
返回null。
@Nullable
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
if (this.allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
} else {
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
TransactionAttribute txAttr = this.findTransactionAttribute(specificMethod);
if (txAttr != null) {
return txAttr;
} else {
txAttr = this.findTransactionAttribute(specificMethod.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
} else {
if (specificMethod != method) {
txAttr = this.findTransactionAttribute(method);
if (txAttr != null) {
return txAttr;
}
txAttr = this.findTransactionAttribute(method.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
}
return null;
}
}
}
}
-
方法被final或static修饰
spring内部是通过aop生成代理类来实现事务功能,如果某个方法用final或static修饰了,在代理类中就无法重写该方法,从而添加事务功能。 -
方法内部调用
这篇文章中存在方法内部调用事务成功的情况,有待研究
方法内部调用不会生成代理对象,所以事务失效。如果想要调用,则需注入一下自己。
- 未被Spring管理
- 多线程调用
spring的事务是通过数据库连接来实现的,数据库连接又是通过ThreadLocal来保存的,多个线程同时调用时产生了多个ThreadLocal,则会有多个数据库连接。
- 表不支持事务
- 未开启事务
- 错误的事务传播级别
- 自己吞了异常
- 异常类型不正确