是不是spring加上@Transactional注解就可以不用管事务了,有没有考虑事务不生效的情况
前段时间帮老大面试,问到了spring的事务,好几个人都表示加上@Transactional注解就可以了,但是有没有去想为什么可以,有哪些情况下@Transactional注解会失效(有很多情况会失效,我这里只说少量的几种)
事务注解工作原理
首先,事务注解工作原理是啥,程序里没有黑科技,别人能做的你大多数能做,首先想想没有事务注解时人们怎么处理事务代码的,其实很简单,curd开始前begin(),curd处理之后根据结果commit() 或者 rollback(),说到这里,应该能猜到,注解就是在你方法之前加上begin逻辑,然后捕获异常(可配置异常种类),查询异常是否在配置的回滚异常里面,如果在配置的异常里面,rollback,如果没有异常或者异常不在配置异常里面,commit
事务注解什么情况下会失效
上面说了事务注解的简要原理。那么,首先能想到的失效的方法是啥?spring的@Transactional注解是根据异常来处理回滚事务或者提交的,那么,我要是把异常捕获,不就让注解失效了吗,以前看有人在service里面加上@Transactional,然后方法里调用操作数据库方法加上try catch,那样是没有用的,除非你捕获后再抛出去,那样就没啥意义了。
然后呢,还有其他事务失效的情况没?这里我说说我写这篇文章的动机,就是在看一些aop类的注解时,想到注解是隐藏很多逻辑,实际上处理注解编写代码很麻烦,那么,@Transactional的逻辑写在哪里呢?我能不能绕过这些逻辑让他失效呢?
绕过@Transactional注解处理逻辑让事务失效
编写简单的代码,详情见注释
1 import org.springframework.beans.factory.annotation.Autowired; 2 import org.springframework.stereotype.Controller; 3 import org.springframework.web.bind.annotation.RequestMapping; 4 import org.springframework.web.bind.annotation.ResponseBody; 5 6 import com.example.demo.dao.StudentDao; 7 import com.example.demo.pojo.Student; 8 import com.example.demo.service.StudentService; 9 import com.example.demo.service.impl.StudentServiceImpl; 10 11 @Controller 12 @ResponseBody 13 public class StudentController { 14 15 private StudentService studentService; 16 17 // 注入service 18 // @Autowired 19 // public StudentController(StudentService studentService) { 20 // this.studentService = studentService; 21 // } 22 23 // 手动创建service 24 @Autowired 25 public StudentController(StudentDao dao) { 26 this.studentService = new StudentServiceImpl(dao); 27 } 28 29 @RequestMapping("get") 30 public Student find(Integer id) { 31 return studentService.findStudentByStudentId(id); 32 } 33 34 @RequestMapping("insert") 35 public Student insertStudent(Student student) { 36 System.out.println(studentService.getClass()); 37 38 boolean res = studentService.insertStudentByData(student); 39 System.out.println(res + "------------------------------"); 40 return student; 41 } 42 43 }
1 import org.springframework.beans.factory.annotation.Autowired; 2 import org.springframework.stereotype.Service; 3 import org.springframework.transaction.annotation.Transactional; 4 5 import com.example.demo.dao.StudentDao; 6 import com.example.demo.pojo.Student; 7 import com.example.demo.service.StudentService; 8 9 @Service 10 public class StudentServiceImpl implements StudentService { 11 12 private StudentDao studentMapper; 13 14 @Autowired 15 public StudentServiceImpl(StudentDao studentMapper) { 16 this.studentMapper = studentMapper; 17 } 18 19 @Override 20 public Student findStudentByStudentId(Integer id) { 21 return studentMapper.getStudentById(id); 22 } 23 24 @Override 25 @Transactional 26 public boolean insertStudentByData(Student student) { 27 int insertStudent = studentMapper.insertStudent(student); 28 29 // 手动制造异常,当年龄为0时报错 by zero 30 if (null != student && null != student.getAge()) { 31 System.out.println("====================" + (3 / student.getAge())); 32 } 33 if (insertStudent > 0) { 34 return true; 35 } 36 return false; 37 } 38 39 }
首先我想到的是,注解生效,那是什么时候生效,如果我直接调用service,那么他begin()commit()逻辑写在哪里,不可能一个请求都用事务包裹起来,事务一般时细节到一个方法的,可是我service调用前后他没办法给我加上逻辑啊。会不会?会不会?会不会我的service根本就不是我的service呢?只是看起来是我的service,看一下数据库的数据
没有id为7这个student
curl调用
curl "localhost:8080/insert?id=7&name=byZero&age=0"
结果如下,报错了,并且加了注解,没有捕获异常,为啥事务没有生效
这里,其实我做了一点晓得改变,注入的service修改为手动new出的service,不使用spring管理的servi
// 手动创建service @Autowired public StudentController(StudentDao dao) { this.studentService = new StudentServiceImpl(dao); }
看看我接下来的测试,为啥会失效相信看了下面的图就会明白了
上面是手动创建service
上面是注入service
不同方式出来的类不一样,注入的service是代理的service,在执行方法时会根据事务注解在前后加上begin rollback等逻辑,方便用户不用把begin这些编码进业务里面,当你要回滚时,直接抛出一个异常就可以了,方便编码。
总结
在现在很多框架减轻我们编码压力时,需要时常想一想他们大概时怎样实现的,如果是我我会怎么做,框架里的哪些设计模式以及编码习惯等我是否可以学。java开源轮子多,最好的学习资料都在哪些开源框架里面,不需要太深,但是要有基本的了解,没有黑魔法,都是一行行代码写出来的。
编码不断,学习不止,努力!!!