以mysql为例,其MyISAM引擎是不支持事务操作的,InnoDB才支持事务的引擎。从mysql5.5开始默认引擎是InnoDB,之前默认的是MyISAM
事务注解加到service层,如果没有@Service注解,这个类就不会被加载成一个Bean,那这个类就不会被spring管理,事务自然就失效了
根据spring官网,@Transactional只能用于public的方法上,否则事务不会生效,如果非要用在非public方法上,可以开启AspectJ代理模式。AspectJ使用加载时织入的方式,支持所有的pointcut,因此可以支持内部方法的事务设置
如注解配置的不支持事务,使用了Propagation。NOT_SUPPORTED:表示不易事务运行,当前若存在事务则挂起
开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法a,a再调用本类的方法b(不论方法b是用public还是private修饰),但方法A没有声明注解事务,而b方法有。则外部调用方法a之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。
那为啥会出现这种情况?其实还是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。
//@Transactional
@GetMapping("/test")
private void a(User user) throws Exception {
userMapper.insert(user);
b(user);
}
@Transactional
public void b(User user) throws Exception {
doSomething();
}
补充:
如果a方法同样加了 @Transactional注解,此时的事务是否能生效呢?
上面的代码只是放开a方法上的@Transactional注解,此时a方法的事务依然是无法生效的。我们看到在事务方法a中,直接调用事务方法b。从前面介绍的内容可知,b方法拥有事务是因为spring aop生成了代理对象,但是这种方法直接调用了this对象的方法,所以a方法不会生成事务。
由此可见,在同一个类中的方法直接内部调用,会导致事务失效。
那么问题来了,这种场景如何解决呢?
这个方法非常简单,只需要新加一个Service方法,把@Transactional注解加到新的Service方法上,把需要事务执行的代码移到新方法中
@Servcie
public class ServiceA {
@Autowired
prvate ServiceB serviceB;
public void save(User user) {
queryData1();
queryData2();
serviceB.doSave(user);
}
}
@Servcie
public class ServiceB {
@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
userMapper.insert(user);
b(user);
}
}
如果不想再新加一个Service类,在该Service类中注入自己也是一种选择。
@Servcie
public class ServiceA {
@Autowired
prvate ServiceA serviceA;
public void save(User user) {
queryData1();
queryData2();
serviceA.doSave(user);
}
@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
userMapper.insert(user);
b(user);
}
}
这种做法不会造成循环依赖问题,其实spring IOC内部的三级缓存保证了它,不会出现循环依赖问题。
引入依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
开启AOP获取(在SpringBoot启动类或者配置类上加)
@EnableAspectJAutoProxy(exposeProxy = true)
在该Service类中使用AopContext.currentProxy()获取代理对象
@Servcie
public class ServiceA {
public void save(User user) {
queryData1();
queryData2();
((ServiceA)AopContext.currentProxy()).doSave(user);
}
@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
userMapper.insert(user);
b(user);
}
}
把异常吃了,然后又不抛出来,事务怎么回滚呢?
如果想要spring事务能够正常回滚,必须抛出它能够处理的异常。如果没有抛异常,则spring认为程序是正常的。
默默的说句,即使开发者没有手动捕获异常,但如果抛的异常不正确,spring事务也不会回滚。
因为spring事务,默认情况下只会回滚RuntimeException(运行时异常)和Error(错误),对于普通的Exception(非运行时异常),它不会回滚。
默认回滚的是RuntimeException,如果你想要触发其它的异常回滚,需要在注解上配置
@Transactional(rollbackFor = Exception.class)
有时候,某个方法不想被子类重写,这时可以将方法定义成final的。普通方法这样定义没问题,但是如果将事务定义为final,这样会导致事务失效。
为什么呢?
spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。
但如果某个方法被final修饰了,那么在它的代理类中,就无法重写该方法,事务也就失效了。
注意:如果某个方法是static的,同样无法通过动态代理,变成事务方法。
在实际项目开发中,多线程的使用场景还是挺多的。如果spring事务用在多线程场景中,会有问题吗?
@Slf4j
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
new Thread(() -> {
roleService.doOtherThing();
}).start();
}
}
@Service
public class RoleService {
@Transactional
public void doOtherThing() {
System.out.println("保存role表数据");
}
}
从上面的例子中,我们可以看到事务方法add中,调用了事务方法doOtherThing,但是事务方法doOtherThing是在另外一个线程中调用的。
这样会导致两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果想doOtherThing方法中抛了异常,add方法也回滚是不可能的。
如果看过spring事务源码的朋友,可能会知道spring的事务是通过数据库连接来实现的。当前线程中保存了一个map,key是数据源,value是数据库连接。
我们说的同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务。