关于Java:从JPA / EJB3持久性上下文分离实体

关于Java:从JPA / EJB3持久性上下文分离实体

Detach an entity from JPA/EJB3 persistence context

分离通过EntityManager获取的特定JPA实体Bean的最简单方法是什么。 或者,我是否可以首先通过查询返回分离的对象,使它们本质上充当"只读"对象?

之所以这样做,是因为我想修改Bean中的数据-仅在我的应用程序中,而从未将其持久化到数据库中。 在我的程序中,我最终不得不在EntityManager上调用flush(),该操作将保留从连接的实体到底层数据库的所有更改,但是我想排除特定的对象。


(可能为时已晚,但对其他人可能有用)

我现在正在使用JPA开发我的第一个系统。不幸的是,当这个系统几乎完成时,我面临着这个问题。

简单的说。使用Hibernate,或等待JPA 2.0。

在Hibernate中,您可以使用'session.evict(object)'从会话中删除一个对象。在JPA 2.0中,目前在草稿中,存在一种'EntityManager.detach(object)'方法,用于从持久性上下文中分离一个对象。


不管您使用哪种JPA实现,都只需使用entityManager.detach(object),它就已经在JPA 2.0和JEE6中。


如果需要从EntityManager分离对象,并且将Hibernate用作基础ORM层,则可以访问Hibernate Session对象,并使用上面Mauricio Kanada提到的Session.evict(Object)方法。

1
2
3
4
public void detach(Object entity) {
    org.hibernate.Session session = (Session) entityManager.getDelegate();
    session.evict(entity);
}

当然,如果您切换到另一个ORM提供程序,则可能会中断,但是我认为这最好是尝试制作深层副本。


不幸的是,在当前的JPA实现AFAIR中,无法从实体管理器断开一个对象的连接。

EntityManager.clear()将断开所有JPA对象的连接,因此,如果您确实计划保持其他连接,那么在所有情况下这都不是一个合适的解决方案。

因此,最好的选择是克隆对象,并将克隆传递给更改对象的代码。由于默认克隆机制会以适当的方式处理原始和不可变的对象字段,因此您无需编写大量的管道代码(除了深克隆可能具有的任何聚合结构之外)。


如果使用EclipseLink,您还可以选择,

使用查询提示eclipselink.maintain-cache"="false-所有返回的对象将被分离。

使用EclipseLink JpaEntityManager copy() API将对象复制到所需深度。


据我所知,唯一的直接方法是:

  • 提交TXN-可能不是一个合理的选择
  • 清除持久性上下文-EntityManager.clear()-这很残酷,但是会清除掉
  • 复制对象-大多数情况下,您的JPA对象是可序列化的,因此这应该很容易(如果不是特别有效)。

  • 要处理类似的情况,我创建了一个DTO对象,该对象扩展了持久实体对象,如下所示:

    1
    2
    3
    4
    5
    class MyEntity
    {
       public static class MyEntityDO extends MyEntity {}

    }

    最后,标量查询将检索所需的非托管属性:

    1
    2
    (Hibernate) select p.id, p.name from MyEntity P
    (JPA)       select new MyEntity(p.id, p.name) from myEntity P

    在JPA 1.0(使用EclipseLink测试)中,您可以在事务外部检索实体。例如,使用容器管理的事务,您可以执行以下操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public MyEntity myMethod(long id) {
        final MyEntity myEntity = retrieve(id);
        // myEntity is detached here
    }

    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    public MyEntity retrieve(long id) {
        return entityManager.find(MyEntity.class, id);
    }

    由于我正在使用SEAM和JPA 1.0,并且我的系统具有需要记录所有字段更改的功能,因此,如果需要记录实体的相同字段,则创建了一个值对象或数据传输对象。新pojo的构造函数为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
        public DocumentoAntigoDTO(Documento documentoAtual) {
        Method[] metodosDocumento = Documento.class.getMethods();
        for(Method metodo:metodosDocumento){
            if(metodo.getName().contains("get")){
                try {
                    Object resultadoInvoke = metodo.invoke(documentoAtual,null);
                    Method[] metodosDocumentoAntigo = DocumentoAntigoDTO.class.getMethods();
                    for(Method metodoAntigo : metodosDocumentoAntigo){
                        String metodSetName ="set" + metodo.getName().substring(3);
                        if(metodoAntigo.getName().equals(metodSetName)){
                            metodoAntigo.invoke(this, resultadoInvoke);
                        }
                    }
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    这很麻烦,但是您也可以序列化和反序列化对象。


    如果bean中没有太多属性,则可以创建一个新实例,然后从持久化bean中手动设置其所有属性。

    可以将其实现为副本构造函数,例如:

    1
    2
    3
    4
    public Thing(Thing oldBean) {
      this.setPropertyOne(oldBean.getPropertyOne());
      // and so on
    }

    然后:

    1
    Thing newBean = new Thing(oldBean);

    我认为有一种方法可以通过以下方式从EntityManager中逐出单个实体

    1
    2
    EntityManagerFactory emf;
    emf.getCache().evict(Entity);

    这将从缓存中删除特定实体。


    如果到达这里是因为您实际上想跨远程边界传递实体,则只需输入一些代码来愚弄hibernazi。

    1
    2
    for(RssItem i : result.getChannel().getItem()){
    }

    可克隆将无法正常工作,因为它实际上复制了PersistantBag。

    并无需使用可序列化和字节数组流以及管道流。创建线程以避免死锁会杀死整个概念。


    我认为如果实体的主键没有更改,您也可以使用EntityManager.refresh(Object o)方法。此方法将恢复实体的原始状态。


    推荐阅读