事务分为声明式事务和编程式事务,在这里我们主要说明声明式事务,声明式事务的使用方法在Spring的著配置文件中添加配置
//配置事务
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
//添加数据库
<property name="dataSource" ref="dataSource"/>
</bean>
//开启事务注解
<tx:annotation-driven transaction-manager="transactionManager"/>
紧接着只需要在你认为需要添加业务的方法上添加注解@Transactional 需要注意的时@Transactional只能加到public方法上才会生效
需要注意的时事务在使用的时候默认RuntimeException时运行时异常回滚,Exceptio提交,所以你在使用的时候需要注意了,如说你在添加@Transactional注解的方法中把异常给捕获也就是try…catchl了,那么这时候回滚还是提交就要看你catch中抛出的是什么异常,实例如下
1. catch中抛出运行时异常
@Override
@Transactional
public void addUser() {
try {
User user = new User();
user.setId("35");
user.setUsername("12345");
user.setPassword("132456");
userMapper.insertUser(user);
User user1 = new User();
user1.setId("36");
user1.setUsername("12345");
user1.setPassword("132456");
userMapper.insertUser(user1);
User user2 = new User();
user2.setId("37");
user2.setUsername("12345");
user2.setPassword("132456");
userMapper.insertUser(user2);
User user3 = new User();
user3.setUsername("12345");
user3.setPassword("132456");
userMapper.insertUser(user3);
}
catch (Exception e){
throw new RuntimeException();
}
}
在上面的代码中我故意不给第四个的User对象的id属性赋值,而在数据库中id又是不允许为空的所以就报错了
但是可以看到我的数据库表中并没有因为最后一个出而添加前面的,可以看到表中并没有代码中的设置的几个id
2catch中抛出IllegalAccessException(这个异常属于Exception的子类也就是不属于运行时异常)
@Override
@Transactional
public void addUser() throws IllegalAccessException {
try {
User user = new User();
user.setId("35");
user.setUsername("12345");
user.setPassword("132456");
userMapper.insertUser(user);
User user1 = new User();
user1.setId("36");
user1.setUsername("12345");
user1.setPassword("132456");
userMapper.insertUser(user1);
User user2 = new User();
user2.setId("37");
user2.setUsername("12345");
user2.setPassword("132456");
userMapper.insertUser(user2);
User user3 = new User();
user3.setUsername("12345");
user3.setPassword("132456");
userMapper.insertUser(user3);
}
catch (Exception e){
throw new IllegalAccessException();
}
}
执行结果以及数据库表
可以看到上面的程序虽然出错了但是数据库表还是进行的新增操作,并没有回滚事务而是进行了提交
接下是不使用try…catch捕获异常,而是使用throw直接抛出异常
1.抛出IllegalAccessException 异常
@Override
@Transactional
public void addUser() throws IllegalAccessException {
User user = new User();
user.setId("38");
user.setUsername("12345");
user.setPassword("132456");
userMapper.insertUser(user);
User user1 = new User();
user1.setId("39");
user1.setUsername("12345");
user1.setPassword("132456");
userMapper.insertUser(user1);
User user2 = new User();
user2.setId("40");
user2.setUsername("12345");
user2.setPassword("132456");
userMapper.insertUser(user2);
User user3 = new User();
user3.setUsername("12345");
user3.setPassword("132456");
int i = userMapper.insertUser(user3);
if (i != 1) {
throw new IllegalAccessException();
}
}
在上面的执行结果中我让最后一个插入返回一下插入结果进行一下判断如果不满意抛出异常,学习过的都知道这个必然是错误的
执行结果和数据库截图如下
可以看到上面的数据库表中并没有因为出错而新增的数据,说明进行了事务的回滚
2.抛出运行时异常
@Override
@Transactional
public void addUser(){
User user = new User();
user.setId("38");
user.setUsername("12345");
user.setPassword("132456");
userMapper.insertUser(user);
User user1 = new User();
user1.setId("39");
user1.setUsername("12345");
user1.setPassword("132456");
userMapper.insertUser(user1);
User user2 = new User();
user2.setId("40");
user2.setUsername("12345");
user2.setPassword("132456");
userMapper.insertUser(user2);
User user3 = new User();
user3.setUsername("12345");
user3.setPassword("132456");
int i = userMapper.insertUser(user3);
if (i != 1) {
throw new RuntimeException();
}
}
执行结果和数据库结果如下
结论就是在事务中如果想要事务回滚,在使用try…catch时捕获时catch中抛出的异常必须时运行时异常,如果是不使用try…catch而是直接抛出异常的话,运行时异常和非运行时异常都会导致事务进行回滚
还有最重要的一点
注解修饰的方法被类内部方法调用
这种失效场景是我们日常开发中最常踩坑的地方;在类A里面有方法a 和方法b, 然后方法b上面用 @Transactional加了方法级别的事务,在方法a里面 调用了方法b, 方法b里面的事务不会生效。为什么会失效呢?:
其实原因很简单,Spring在扫描Bean的时候会自动为标注了@Transactional注解的类生成一个代理类(proxy),当有注解的方法被调用的时候,实际上是代理类调用的,代理类在调用之前会开启事务,执行事务的操作,但是同类中的方法互相调用,相当于this.B(),此时的B方法并非是代理类调用,而是直接通过原有的Bean直接调用,所以注解会失效。
@Service
public class ClassServiceImpl implements ClassService {
@Autowired
private ClassMapper classMapper;
public void insertClass(ClassDo classDo) throws CustomException {
insertClassByException(classDo);
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void insertClassByException(ClassDo classDo) throws CustomException {
classMapper.insertClass(classDo);
throw new RuntimeException();
}
}
//测试用例:
@Test
public void insertInnerExceptionTest() throws CustomException {
classDo.setClassId(2);
classDo.setClassName("java_2");
classDo.setClassNo("java_2");
classService.insertClass(classDo);
}
上面的这种情况是不会让事务生效的,具体原因在上卖弄查看
解决办法
public void insertClass(ClassDo classDo) throws CustomException {
// insertClassByException(classDo);
((ClassServiceImpl)AopContext.currentProxy()).insertClassByException(classDo);
}
//测试用例:
@Test
public void insertInnerExceptionTest() throws CustomException {
classDo.setClassId(3);
classDo.setClassName("java_3");
classDo.setClassNo("java_3");
classService.insertClass(classDo);
}