如题,最近碰到了一个问题,在public方法上添加@Transaction没有生效,事务没有回滚。
我自己模拟了一个功能,向数据库表User里面插入用户数据。说一下代码背景,
数据库MySQL,持久化层Mybatis,项目使用SpringBoot。
数据表User如下,已有一条数据:
下面是代码
- Application启动类
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication(scanBasePackages = {"com.example.demo","com.example.demo.dao"})
//启动事务
@EnableTransactionManagement
//mapper接口扫描
@MapperScan("com.example.demo.dao")
@RestController
public class DemoApplication {
@GetMapping("/")
String home() {
return "Spring is here!";
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
- 实体类User,对应数据表user表
@Data
@Table(name = "`user`")
public class User {
@Id
@Column(name = "`id`")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Column(name = "`name`")
private String name;
@Column(name = "`created_time`")
private Date createdTime;
}
- Mapper类
package com.example.demo.dao;
import com.example.demo.domain.po.User;
import org.springframework.stereotype.Repository;
import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.common.MySqlMapper;
/**
* TOrderMapper
*
* @author zhouxy@133.cn
* @date 2022/8/15
**/
@Repository
public interface UserMapper extends Mapper<User>, MySqlMapper<User> {
}
- Service接口,处理业务逻辑,添加@Transaction
package com.example.demo.service;
import com.example.demo.domain.po.User;
import org.springframework.stereotype.Service;
/**
* @Description: TODO
* @Author: zhouxy
* @CreateTime: 2023-10-17
*/
public interface IUserService {
Integer addUser(User user);
}
- service实现类
package com.example.demo.service.impl;
import com.example.demo.dao.UserMapper;
import com.example.demo.domain.po.User;
import com.example.demo.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @Description: TODO
* @Author: zhouxy
* @CreateTime: 2023-10-17
*/
@Service
public class UserService implements IUserService {
@Autowired
private UserMapper userMapper;
@Override
@Transactional
public Integer addUser(User user) {
Integer result = userMapper.insertSelective(user);
}
}
- AOP类
package com.example.demo.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @Description: TODO
* @Author: zhouxy
* @CreateTime: 2023-05-17
*/
@Aspect
@Component
@Slf4j
public class LogAop {
@Pointcut(value = "execution(* com.example.demo.service.*.*(..))")
public void controller() {
log.info("223432");
}
@Around("controller()")
public Object around(ProceedingJoinPoint joinPoint) {
log.info("before================");
try {
Object res = joinPoint.proceed();
return res;
} catch (Throwable throwable) {
log.error("", throwable);
}finally {
log.info("after================");
}
return null;
}
}
- 测试类
import com.example.demo.DemoApplication;
import com.example.demo.dao.UserMapper;
import com.example.demo.domain.po.User;
import com.example.demo.service.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* MybatisTest
*
* @author zhouxy@133.cn
* @date 2021/7/20
**/
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class)
public class MybatisTest {
@Autowired
private IUserService userService;
@Test
public void test() {
User user = new User();
user.setName("LISI222");
userService.addUser(user);
}
}
当正常执行的时候,会在数据库中插入数据。
打印日志:
此时在实现类中加入一行异常,看一下出现异常之后,事务是否会回滚,数据能否成功插入到数据表中。
package com.example.demo.service.impl;
import com.example.demo.dao.UserMapper;
import com.example.demo.domain.po.User;
import com.example.demo.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @Description: TODO
* @Author: zhouxy
* @CreateTime: 2023-10-17
*/
@Service
public class UserService implements IUserService {
@Autowired
private UserMapper userMapper;
@Override
@Transactional
public Integer addUser(User user) {
Integer result = userMapper.insertSelective(user);
//添加一行异常,查看事务是否会因为抛出异常回滚刚才的插入操作
throw new RuntimeException();
}
}
我们看执行完之后,数据库的变化,我刚才插入的是用户:LISI222
显示的是插入成功,看后台日志显示,事务并没有做回滚操作,说明当前事务并没有捕获到异常信息。仔细分析日志头尾可知,日志打印操作包裹了service实现类的addUser方法,应该是aop操作在@Transaction之前执行,导致异常被aop处理了,aop最终也没有抛出异常,所以@Transaction方法没有捕获到异常信息。
2023-11-24 10:53:24.418 INFO [,,,] 29600 --- [ main] com.example.demo.aop.LogAop : before================
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c3884f5]
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5542418c] will be managed by Spring
==> Preparing: INSERT INTO `user` ( `id`,`name` ) VALUES( ?,? )
==> Parameters: 0(Integer), LISI222(String)
<== Updates: 1
==> Executing: SELECT LAST_INSERT_ID()
<== Columns: LAST_INSERT_ID()
<== Row: 11
<== Total: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c3884f5]
2023-11-24 10:53:24.566 ERROR [,,,] 29600 --- [ main] com.example.demo.aop.LogAop :
java.lang.RuntimeException: null
at com.example.demo.service.impl.UserService.addUser(UserService.java:24) ~[classes/:na]
at com.example.demo.service.impl.UserService$$FastClassBySpringCGLIB$$9c38b50f.invoke(<generated>) ~[classes/:na]
····异常信息打印,忽略
2023-11-24 10:53:24.566 INFO [,,,] 29600 --- [ main] com.example.demo.aop.LogAop : after================
这里仍然正常提交事务,说明没有捕获到异常信息
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c3884f5]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c3884f5]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c3884f5]
现在是因为aop中没有将异常抛出,做了友好化处理,将异常处理成了返回了错误码。所以需要在aop处理返回结果之前,事务就要捕获到当前事务中抛出的异常并进行回滚或者不提交事务。
我在网上查找资料的时候查找到@Order注解,@Order的作用是定义Spring IOC容器中Bean的执行顺序的优先级(这里的顺序也可以理解为存放到容器中的先后顺序),所以我给LogAop类加了一个@Order注解,让LogAOP在最后执行,这样@Transaction类就可以提前执行,捕获异常,回滚数据。@Order默认为最低优先级,因此可以直接设置@Order即可
@Aspect
@Component
@Slf4j
@Order(10) //定义LogAOP的顺序最后执行,值越高,优先级越低
public class LogAop {
·····
}
这次再执行一遍,查看是否成功回滚事务。
再看下日志,事务在aop之前执行完成。
2023-11-24 10:59:04.879 INFO [,,,] 28412 --- [ main] com.example.demo.aop.LogAop : before================
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@786e2c5e]
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@78854721] will be managed by Spring
==> Preparing: INSERT INTO `user` ( `id`,`name` ) VALUES( ?,? )
==> Parameters: 0(Integer), LISI333(String)
<== Updates: 1
==> Executing: SELECT LAST_INSERT_ID()
<== Columns: LAST_INSERT_ID()
<== Row: 12
<== Total: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@786e2c5e]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@786e2c5e]
这里关闭了事务,并且没有提交操作。
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@786e2c5e]
2023-11-24 10:59:05.271 ERROR [,,,] 28412 --- [ main] com.example.demo.aop.LogAop :
java.lang.RuntimeException: null
at com.example.demo.service.impl.UserService.addUser(UserService.java:24) ~[classes/:na]
at com.example.demo.service.impl.UserService$$FastClassBySpringCGLIB$$9c38b50f.invoke(<generated>) ~[classes/:na]
2023-11-24 10:59:05.272 INFO [,,,] 28412 --- [ main] com.example.demo.aop.LogAop : after================
使用@Order注解可以调整运行级别,但是需注意一个问题,aop中不要做一些业务操作,因为transaction不会捕获到aop中抛出的异常。可以使用try…catch捕获aop中的数据库操作,并进行回滚。