我想通過組合和一些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在這裏誰可以解釋實際問題和/或知道如何解決我的方法?
只需添加到您的答案:更新語句進入數據庫後發生PostUpdate。對實體的更改不應與數據庫同步,事件也不應按照JPA 2.1規範對實體進行關係更改:「通常,便攜式應用程序的生命週期方法不應調用EntityManager 或查詢操作,訪問其他實體實例或修改相同持久性上下文[46]內的關係[47]。生命週期回調方法可能會修改調用它的實體的非關係狀態。 – Chris
Tyvm爲你的答案1)我正在嘗試不同的回調方法。最初它是PreUpdate。相同的結果。 2)JPA FlushModeType似乎只是一個提示'在事務提交時發生刷新。提供者可能會在其他時間刷新,但根據@Chris的評論,不需要'3),我的方法違反了JPA規範,所以我應該去尋求另一種解決方案,對吧?最後,我只想避免在每個服務方法中手動執行審計。當然,這只是一個方法調用,但我認爲有人忘記稱之爲高風險。任何想法/選擇? – stg