十九、Spring 事务常见错误(上)
Spring 事务管理包含两种配置方式,第一种是使用 XML 进行模糊匹配,绑定事务管理;第二种是使用注解,这种方式可以对每个需要进行事务处理的方法进行单独配置,你只需要添加上 @Transactional,然后在注解内添加属性配置即可。
一.unchecked 异常与事务回滚(@Transactional默认只回滚RuntimeException 或者 Error,需要进行配置,才能触发Exception回滚)
Spring 处理事务的时候,如果没有在 @Transactional 中配置 rollback 属性,那么只有捕获到 RuntimeException 或者 Error 的时候才会触发回滚操作。而我们案例抛出的异常是 Exception,又没有指定与之匹配的回滚规则,所以我们不能触发回滚。
1、SQL表结构
1.学生表
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`realname` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2.学生选课表 student_course,记录学生表 student 和课程表 course 之间的多对多关联。
CREATE TABLE `student_course` (
`student_id` int(11) NOT NULL,
`course_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2、Service
@Service
public class StudentService {
@Autowired
private StudentMapper studentMapper;
@Transactional
public void saveStudent(String realname) throws Exception {
Student student = new Student();
student.setRealname(realname);
studentMapper.saveStudent(student);
if (student.getRealname().equals("小明")) {
throw new Exception("该学生已存在");
}
}
}
3、TEST
public class AppConfig {
public static void main(String[] args) throws Exception {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
StudentService studentService = (StudentService) context.getBean("studentService");
studentService.saveStudent("小明");
}
}
虽然发生了异常,代码中断了,但是数据库还是添加了数据,这个不是我们想要的结果。
4、案例解析
pring 处理事务的时候,如果没有在 @Transactional 中配置 rollback 属性,那么只有捕获到RuntimeException 或者 Error 的时候才会触发回滚操作。而我们案例抛出的异常是 Exception,又没有指定与之匹配的回滚规则,所以我们不能触发回滚。
5、问题修正
指定rollback的Exception类型
@Transactional(rollbackFor = Exception.class)
@Service
public class StudentService {
@Autowired
private StudentMapper studentMapper;
@Transactional(rollbackFor = Exception.class)
public void saveStudent(String realname) throws Exception {
Student student = new Student();
student.setRealname(realname);
studentMapper.saveStudent(student);
if (student.getRealname().equals("小明")) {
throw new RuntimeException("该用户已存在");
}
}
二.试图给 private 方法添加事务(只有当注解为事务的方法被声明为 public 的时候,才会被 Spring 处理)
1、Service
@Service
public class StudentService {
@Autowired
private StudentMapper studentMapper;
@Autowired
private StudentService studentService;
public void saveStudent(String realname) throws Exception {
Student student = new Student();
student.setRealname(realname);
studentService.doSaveStudent(student);
}
@Transactional
private void doSaveStudent(Student student) throws Exception {
studentMapper.saveStudent(student);
if (student.getRealname().equals("小明")) {
throw new RuntimeException("该用户已存在");
}
}
}
异常正常抛出,事务却没有回滚。
2、案例解析
源码比较复杂,暂时记住结果就好。
事务生效的条件有以下几个
1.:allowPublicMethodsOnly()
allowPublicMethodsOnly 返回了 AnnotationTransactionAttributeSource 的 publicMethodsOnly 属性。
protected boolean allowPublicMethodsOnly() {
return this.publicMethodsOnly;
}
而这个 publicMethodsOnly 属性是通过 AnnotationTransactionAttributeSource 的构造方法初始化的,默认为 true。
2.Modifier.isPublic()
这个方法根据传入的 method.getModifiers() 获取方法的修饰符。该修饰符是 java.lang.reflect.Modifier 的静态属性,对应的几类修饰符分别是:PUBLIC: 1,PRIVATE: 2,PROTECTED: 4。这里面做了一个位运算,只有当传入的方法修饰符是 public 类型的时候,才返回 true。
只有当注解为事务的方法被声明为 public 的时候,才会被 Spring 处理。
4、问题修正
将方法指定为public
@Service
public class StudentService {
@Autowired
private StudentMapper studentMapper;
@Autowired
private StudentService studentService;
public void saveStudent(String realname) throws Exception {
Student student = new Student();
student.setRealname(realname);
studentService.doSaveStudent(student);
}
@Transactional
public void doSaveStudent(Student student) throws Exception {
studentMapper.saveStudent(student);
if (student.getRealname().equals("小明")) {
throw new RuntimeException("该学生已存在");
}
}
}