事务失效
写在前面
Spring的事务是通过AOP这种代理的方式实现的。
事务失效的场景有以下几个
1.访问权限问题
java的访问权限主要有四种:private、default、protected、public,如果我们加注解的方法不是public,那么事务就会返回空。
1 2 3 4 5 6 7 8 9 10
| @Service public class UserService {
@Transactional private void add(UserModel userModel) { saveData(userModel); updateData(userModel); } }
|
2.方法用final修饰
一般用final修饰的方法,都是在该方法不想被子类重写,但是如果将事务方法定义为final,则会导致问题。
1 2 3 4 5 6 7 8 9
| @Service public class UserService {
@Transactional public final void add(UserModel userModel){ saveData(userModel); updateData(userModel); } }
|
Spring的事务,是通过AOP实现的,而AOP则是通过jdk动态代理或者cglib动态代理,来帮我们生成一个代理类,然后重写对应的方法。final修饰的方法没有办法被重写,所以无法使用事务。
3.方法内部调用
在某个Service类的某个方法里,调用另一个事务方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Service public class UserService {
@Autowired private UserMapper userMapper;
@Transactional public void add(UserModel userModel) { userMapper.insertUser(userModel); updateStatus(userModel); }
@Transactional public void updateStatus(UserModel userModel) { doSameThing(); } }
|
这种写法,调用的是该方法本身,想要事务生效,要走spring的调用。即需要调用UserService.add()
, userService.updateStatus()
,spring才能为其生成代理对象,然后事务才能生效,直接调用该方法事务无法生效。
如何解决:
1、新增Service类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @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) { addData1(); updateData2(); }
}
|
2、在Service中注入自己(spring三级缓存会解决循环依赖问题)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @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) { addData1(); updateData2(); } }
|
3、通过AopContent类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @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) { addData1(); updateData2(); } }
|
4.未被spring管理
只有该bean被spring管理的情况下,才能生成代理对象,事务才可以生效。通过@Controller、@Service、@Component、@Repository等注解,可以自动实现bean实例化和依赖注入的功能,如果类没有这类注解,事务无法生效。
5.多线程调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @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
的时候,开启了一个新的线程,这会导致一个问题,即两个方法不是在同一个线程里面调用的,那么他们两个获取到的数据库连接就不是同一个,那他们就是两个事务。
原因:spring的事务是通过数据库连接来实现的,当前线程中保存一个map,key是数据源,value是数据库连接。通常所说的事务,指的是同一个数据库连接,同一个数据库连接才能同时提交和回滚,不同的线程无法实现。