Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/jpa/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在EntityListener中使用带有PersistenceContext的EJB时,JPA尝试在数据库中插入同一实体两次_Jpa_Eclipselink_Java Ee 7_Jpa 2.1_Entitylisteners - Fatal编程技术网

在EntityListener中使用带有PersistenceContext的EJB时,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

我想通过组合和一些JPA回调方法实现对我的一些JPA实体类的审计。我当前的方法(简称)如下所示:

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规范,所以我应该选择另一种解决方案,对吗?最后我只是