认证成功后调用Jpa Repository更新遇TransactionRequiredException异常
问题根源
你遇到的TransactionRequiredException是因为successfulAuthentication属于Spring Security的Filter类,而Filter由Servlet容器初始化,不在Spring容器的代理管理范围内。直接在Filter方法上添加@Transactional注解,Spring的AOP代理无法拦截该方法,导致事务不生效,执行更新/删除查询时就会抛出异常。
修复步骤
1. 抽取数据库操作到Spring管理的Service类
把端口相关的数据库逻辑抽成独立的Service,在Service方法上添加@Transactional注解(Spring会为Service创建代理,事务注解能正常生效):
@Service public class PortService { private final PortRepo portRepo; // 构造注入(推荐方式) public PortService(PortRepo portRepo) { this.portRepo = portRepo; } @Transactional public Port assignPortToUser(Long userId) { // 启用过期端口 portRepo.enableExpiredPorts(LocalDateTime.now()); // 检查用户是否已有端口 Port port = portRepo.checkIfUserHasPort(userId); if (port == null) { port = portRepo.getOpenPort(); } else { portRepo.extendDuration(port.getPort()); } portRepo.setInUse(userId, port.getPort()); return port; } }
2. 修正Repository中的SQL参数问题
你的enableExpiredPorts方法定义了@Param("localDateTime"),但SQL中直接用了datetime(),参数未被实际使用,需要修正SQL语句:
@Repository public interface PortRepo extends JpaRepository<Port, Integer> { // 修正SQL,使用传入的参数 @Modifying @Query(value = "UPDATE Port SET active=1 WHERE expiration < :localDateTime", nativeQuery = true) void enableExpiredPorts(@Param("localDateTime") LocalDateTime localDateTime); // 其他方法保持不变 @Query(value = "SELECT * FROM Port WHERE user_id=:userId", nativeQuery = true) Port checkIfUserHasPort(@Param("userId") Long userId); @Query(value = "SELECT * FROM Port WHERE active=1 LIMIT 1", nativeQuery = true) Port getOpenPort(); @Modifying @Query(value = "UPDATE Port SET active=0, user_id=:userId, expiration=datetime('now') WHERE port=:port", nativeQuery = true) void setInUse(@Param("userId") Long userId, @Param("port") int port); @Modifying @Query(value = "UPDATE Port SET expiration=datetime('now') WHERE port=:port", nativeQuery = true) void extendDuration(@Param("port") int port); }
3. 修改Filter中的方法,调用Service
在你的AuthenticationFilter中注入PortService,调用Service方法处理端口逻辑:
// 注入PortService和UserRepo private final PortService portService; private final UserRepo userRepo; // 构造注入 public YourAuthenticationFilter(PortService portService, UserRepo userRepo) { this.portService = portService; this.userRepo = userRepo; } @Override public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException { User user = (User) authentication.getPrincipal(); AppUser appUser = userRepo.findByUsername(user.getUsername()); // 调用Service处理端口分配 Port port = portService.assignPortToUser(appUser.getUserId()); // 后续无关逻辑 }
原理说明
Spring的事务管理依赖AOP代理,只有Spring容器管理的Bean(比如Service、Repository)的方法会被代理拦截,从而应用事务逻辑。Filter不属于Spring Bean的代理范围,直接在Filter方法上加@Transactional无效。将逻辑移到Service后,Service的方法被Spring代理,事务注解就能正常生效,执行更新/删除查询时会有事务上下文,不会再抛出TransactionRequiredException。
内容的提问来源于stack exchange,提问作者Grimalkin

