在EntityListener中使用带有PersistenceContext的EJB时,JPA尝试在数据库中插入同一实体两次
我想通过组合和一些JPA回调方法实现对我的一些JPA实体类的审计。我当前的方法(简称)如下所示:在EntityListener中使用带有PersistenceContext的EJB时,JPA尝试在数据库中插入同一实体两次,jpa,eclipselink,java-ee-7,jpa-2.1,entitylisteners,Jpa,Eclipselink,Java Ee 7,Jpa 2.1,Entitylisteners,我想通过组合和一些JPA回调方法实现对我的一些JPA实体类的审计。我当前的方法(简称)如下所示: public class AuditService { @Inject private SecurityService securityService; @PrePersist public void prePersist(Auditable auditable) { System.out.println("PrePersist m
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());
}
}
我要审核的每个实体都实现以下简单接口:
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());
}
}
}
现在它变得更加奇怪:如果我注释掉行//context.setwhowupdated(securityService.getCurrentUser())
在@postapdate
方法中,日志表明回调方法(仅)被调用一次,但在更新时保持null
如果有问题或任何信息丢失,请告诉我,我将更新问题
如果我根本不使用任何更新回调,我就无法发现任何问题
谁能解释实际问题和/或知道如何解决我的方法?披露:我对JPA有很好的理解,但我从未在EJB设置中使用过它
日志警告的标识符是嵌套的\u实体\u管理器\u刷新\u未执行\u预查询\u更改\u可能\u挂起的,如果查看代码RepeatableWriteUnitOfWork(第421行,EL 2.5.2),您可以看到许多代码被跳过。出于某种原因,UnitOfWork中的更改被写入了两次
需要记住的一点是,在事务EclipseLink刷新期间(默认刷新模式为自动),在执行select查询之前,此查询前刷新是为了确保数据库的一致性,否则最终可能会选择以前在事务中删除的实体。由于执行了select inside POSITUPDATE,这将导致刷新,可能这就是为什么会调用writeChanges()两次
另一件让我感到奇怪的事情是,你使用了PrePersist,但是PostUpdate-为什么不对称,不应该是PreUpdate
编辑:
您可以尝试更改查询的刷新模式,将刷新延迟到commit query.setFlushMode(),只要您不打算删除同一事务中的用户,就可以了。披露:我对JPA有很好的理解,但我从未在EJB设置中使用过它
日志警告的标识符是嵌套的\u实体\u管理器\u刷新\u未执行\u预查询\u更改\u可能\u挂起的,如果查看代码RepeatableWriteUnitOfWork(第421行,EL 2.5.2),您可以看到许多代码被跳过。出于某种原因,UnitOfWork中的更改被写入了两次
需要记住的一点是,在事务EclipseLink刷新期间(默认刷新模式为自动),在执行select查询之前,此查询前刷新是为了确保数据库的一致性,否则最终可能会选择以前在事务中删除的实体。由于执行了select inside POSITUPDATE,这将导致刷新,可能这就是为什么会调用writeChanges()两次
另一件让我感到奇怪的事情是,你使用了PrePersist,但是PostUpdate-为什么不对称,不应该是PreUpdate
编辑:
您可以尝试更改查询的刷新模式,将刷新延迟到commit query.setFlushMode(),只要您不打算删除同一事务中的用户,您就可以了。只需添加到您的答案中:update语句进入数据库后会发生POSUPDATE。对实体的更改不应与数据库同步,事件也不应根据JPA 2.1规范对实体进行关系更改:“一般来说,可移植应用程序的生命周期方法不应调用EntityManager或查询操作、访问其他实体实例或修改同一持久性上下文中的关系[46]。[47]生命周期回调方法可能会修改调用它的实体的非关系状态。“tyvm,谢谢你的回答1)我在尝试不同的回调方法。最初它是预先更新的。同样的结果。2) JPA FlushModeType似乎只是在事务提交时发生的提示刷新。提供商可以在其他时间刷新,但不需要
3)根据@Chris的评论,我的方法违反了JPA规范,所以我应该选择另一种解决方案,对吗?最后,我只想避免在每个服务方法中手动执行审计。当然,这只需要再调用一次方法,但我发现有人忘记调用它的风险很高。有什么想法/备选方案吗?只是想补充一下您的答案:在update语句进入数据库之后,会发生PostUpdate。对实体的更改不应与数据库同步,事件也不应根据JPA 2.1规范对实体进行关系更改:“一般来说,可移植应用程序的生命周期方法不应调用EntityManager或查询操作、访问其他实体实例或修改同一持久性上下文中的关系[46]。[47]生命周期回调方法可能会修改调用它的实体的非关系状态。“tyvm,谢谢你的回答1)我在尝试不同的回调方法。最初它是预先更新的。同样的结果。2) JPA FlushModeType似乎只是在事务提交时发生的提示刷新。提供商可以在其他时间刷新,但不需要
3)根据@Chris的评论,我的方法违反了JPA规范,所以我应该选择另一种解决方案,对吗?最后我只是