在使用JPA审计功能后,项目保存问卷过程当中出现了以下错误,根据错误信息可知,是提交jpa事务时发生错误。java
2021-03-20 14:52:04.570 ERROR 21225 --- [io-8002-exec-10] c.y.q.e.GlobalExceptionHandler : 程序运行异常: 主机 127.0.0.1 调用地址 http://localhost/admin/questionnaire 错误信息 Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction
当时在保存问卷过程当中存在spring
@CreatedBy private User createUser; /** * 在保存以前设置创造时间. */ @PrePersist public void initCreateTime() { this.createTime = new Timestamp(System.currentTimeMillis()); } /** * 在保存以后设置token. */ @PostPersist public void initToken() { ... }
自动设置user,保存前设置创造时间,保存后设置token操做。怀疑是设置user后提交了事务,致使设置token操做提交时报错。进而得出@CreatedBy注解与@PostPersist注解没法一块儿使用的结论数据库
尝试在save方法上添加事务注解。结果仍是报错。
尝试在则须要将 @PrePersist
@PostPersist
注解的方法移动到实体监听器中,结果仍是报错。
将设置token方法放到m层中,监听器调用m层设置token方法,结果监听器没法注入m层。
切面完成token设置,何尝试。
对m层save方法进行集成测试,排除其余的影响,发现能正确执行,排除了@CreatedBy注解与@PostPersist注解的冲突问题,问题出在别处。
将日志等级变动为debug,查看报错缘由。
根据堆栈信息,发如今保存问卷过程当中执行了获取当前登陆用户的方法,该方法操做了数据库,致使提交了事务,从而使得@CreateBy注解在获取当前登陆用户后,再次更新数据时无事务可用。缓存
/** * 审计 获取当前登陆用户实现. */ private class SpringSecurityAuditorAware implements AuditorAware<User> { @Autowired private UserService userService; @Override public Optional<User> getCurrentAuditor() { User user = this.userService.getCurrentLoginUser(); if (user == null) { throw new RuntimeException("未获取到当前登陆用户"); } return Optional.of(user); } }
启用审计功能在对数据库执行操做时老是会获取当前登陆用户。,咱们以前的方法就是从数据库中查询。
问题找到了,剩下的就是使得此方法不操做数据库,最简单的思路就是像前台同样设置缓存。
在用户登陆的时候设置user,当要获取当前登陆用户时再也不进行数据库操做,而是直接返回user。ide
/** * 自定义认证用户,以此将由数据库中查询出的用户传给认证相关方法使用,好比@CreateBy. */ public static class AuthUser extends org.springframework.security.core.userdetails.User { private User user; public AuthUser(User user, Collection<? extends GrantedAuthority> authorities) { super(user.getUsername(), user.getPassword(), authorities); this.user = user; } public AuthUser(User user, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) { super(user.getUsername(), user.getPassword(), enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); this.user = user; } public User getUser() { return user; } protected void setUser(User user) { this.user = user; } }
自定义AuthUser类函数
logger.debug("构造用户"); return new AuthUser( user, true, true, true, user.isNonLocked(), authorities);
登陆时执行AuthUser类构造函数,赋予user值测试
UserServiceAuth.AuthUser authUser = (UserServiceAuth.AuthUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); user = authUser.getUser();
获取当前登陆用户时调用getUser()方法,从而避免操做数据库,对事务进行提交。this
解决bug应该熟悉出错的流程,知道到底哪里错了,而后再想解决方案。spa