事务失效

写在前面

Spring的事务是通过AOP这种代理的方式实现的。

事务失效的场景有以下几个

1.访问权限问题

java的访问权限主要有四种:private、default、protected、public,如果我们加注解的方法不是public,那么事务就会返回空。

1
2
3
4
5
6
7
8
9
10
@Service
public class UserService {

@Transactional
// 这里,方法不是public修饰,而spring要求被代理的方法必须是public
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是数据库连接。通常所说的事务,指的是同一个数据库连接,同一个数据库连接才能同时提交和回滚,不同的线程无法实现。