一次事務提交錯誤

在使用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,查看報錯緣由。
image.png
根據堆棧信息,發如今保存問卷過程當中執行了獲取當前登陸用戶的方法,該方法操做了數據庫,致使提交了事務,從而使得@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

相關文章
相關標籤/搜索