2017-01-03 21 views
0

我想通過組合和一些JPA回調方法實現對某些JPA實體類的審計。我目前的做法(縮短)看起來是這樣的:JPA嘗試在EntityListener中使用具有PersistenceContext的EJB時在數據庫中插入兩次相同的實體

我要審覈每個實體實現以下簡單的接口:

public interface Auditable { 
    MetaContext getAuditContext(); 
    void setAuditContext(MetaContext context); 
} 

元方面是保持審計信息的另一個JPA實體類:

@Entity 
public class MetaContext implements Serializable { 
    @Id private Long id; 

    private Date whenCreated; 
    private Date whenUpdated; 
    @ManyToOne private User whoCreated;  
    @ManyToOne private User whoUpdated; 
} 

這是通過組合物在我EntityClass使用

@Entity 
@EntityListeners(AuditListener.class) 
public class MyEntity implements Auditable { 
    // ... 
    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) 
    private MetaContext auditContext; 
} 

的AuditListener看起來是這樣的:

public class AuditService {  
    @Inject private SecurityService securityService;  

    @PrePersist 
    public void prePersist(Auditable auditable) {  
     System.out.println("PrePersist method called"); 
     MetaContext context = new MetaContext(); 
     context.setWhenCreated(new Date());  
     context.setWhoCreated(securityService.getCurrentUser());   
     auditable.setAuditContext(context);     
    } 

    @PostUpdate 
    public void postUpdate(Auditable auditable) {  
     System.out.println("PostUpdate method called");  
     MetaContext context = auditable.getAuditContext(); 
     context.setWhenUpdated(new Date()); 
     context.setWhoUpdated(securityService.getCurrentUser());   
    } 
} 

哪裏SecurityService是另一個EJB裏面我是用檢索屬於誰執行操作的實際用戶的User實例。

@Stateless 
public class SecurityService { 

    @Resource private SessionContext sctx; 
    @PersistenceContext private EntityManager em; 

    public String getCurrentUserName() { 
     Principal principal = sctx == null ? null : sctx.getCallerPrincipal(); 
     return principal == null ? null : principal.getName(); 
    } 

    public User getCurrentUser() { 
     String username = getCurrentUserName(); 
     if (username == null) { 
      return null; 
     } else { 
      String jpqlQuery = "SELECT u FROM User u WHERE u.globalId = :name"; 
      TypedQuery<User> query = em.createQuery(jpqlQuery, User.class); 
      query.setParameter("name", username); 
      return EJBUtil.getUniqueResult(query.getResultList()); 
     } 
    } 
} 

在每個實體@TableGenerator@GeneratedValue用於創建的ID。


行爲: 當調用一個新的實例myEntity所EntityManager.persist,首先我EntityListener的PrePersist方法被調用,符合市場預期。日期和用戶設置正確。之後,調用@PostUpdate方法。 這似乎是與特定的EclipseLink結合生成ID的。我注意到之前在我使用不同方法的其他項目中。但是,現在在@PostUpdate方法中,再次使用查找當前用戶的服務,這似乎導致JPA想要將MetaContext插入數據庫幾次,這導致違反了此表上的Primaray Key約束。如果我刪除PK約束,操作將成功完成,但錯誤結果爲:whenUpdated設置爲當前日期,但whoUpdated仍爲null。另外還有一個警告被記錄下來,我不太明白。上面的代碼生成以下日誌:

Info: PrePersist method called 
Info: PostUpdate method called 
Info: PostUpdate method called 
Warning: The class RepeatableWriteUnitOfWork is already flushing. The query will be 
    executed without further changes being written to the database. If the query is 
    conditional upon changed data the changes may not be reflected in the results. 
    Users should issue a flush() call upon completion of the dependent changes and 
    prior to this flush() to ensure correct results. 

現在,它會變得更加奇怪:如果我註釋掉// context.setWhoUpdated(securityService.getCurrentUser());@PostUpdate方法日誌表明,回調方法被調用(只)一次,但whenUpdated遺體null

如果有任何疑問或缺少任何信息,請告訴我,我會更新問題。

如果我根本沒有使用任何更新回調,我找不到任何問題。

Soemone在這裏誰可以解釋實際問題和/或知道如何解決我的方法?

回答

3

披露:我對JPA有很好的理解,但是我從未在EJB設置中使用它。

記錄警告的標識符是nested_entity_manager_flush_not_executed_pre_query_changes_may_be_pending,如果您查看代碼RepeatableWriteUnitOfWork(第421行,EL 2.5.2),您可以看到很多代碼被跳過。出於某種原因,UnitOfWork中的更改會寫入兩次。

有一點需要記住的是,在執行EclipseLink刷新(默認flush-mode爲auto)事務期間,它執行select查詢之前,此預查詢刷新是爲了確保數據庫一致,否則可能會結束選擇您之前在交易中刪除的實體。由於您在PostUpdate內部執行select操作,因此會導致刷新,也許這就是writeChanges()被調用兩次的原因。

這令我奇怪的一件事是,你使用堅持,但更新 - 爲什麼不對稱,豈不是更新前

編輯: 你可以試着改變刷新模式爲您查詢,延遲沖洗,直到提交query.setFlushMode(),只要您不打算刪除的用戶在同一事務中你應該沒事。

+1

只需添加到您的答案:更新語句進入數據庫後發生PostUpdate。對實體的更改不應與數據庫同步,事件也不應按照JPA 2.1規範對實體進行關係更改:「通常,便攜式應用程序的生命週期方法不應調用EntityManager 或查詢操作,訪問其他實體實例或修改相同持久性上下文[46]內的關係[47]。生命週期回調方法可能會修改調用它的實體的非關係狀態。 – Chris

+0

Tyvm爲你的答案1)我正在嘗試不同的回調方法。最初它是PreUpdate。相同的結果。 2)JPA FlushModeType似乎只是一個提示'在事務提交時發生刷新。提供者可能會在其他時間刷新,但根據@Chris的評論,不需要'3),我的方法違反了JPA規範,所以我應該去尋求另一種解決方案,對吧?最後,我只想避免在每個服務方法中手動執行審計。當然,這只是一個方法調用,但我認爲有人忘記稱之爲高風險。任何想法/選擇? – stg

相關問題