如何从 EntityManager 中分离实体

How to detach an entity from an EntityManager

我的环境

Java7/JPA 2/休眠 5.1。

我的场景

我正在构建存储库模式实现。所有代码都已编写,并且在没有错误情况发生的情况下一切正常。

但是,假设将三个实体实例添加到存储库中。第一和第三都可以,但第二个缺少强制(非空)列的值。保存仓库时会返回DB错误

遇到这种情况时,批处理应该只在某处写入日志消息,跳过无效对象并继续处理其他对象。为了做到这一点,这个无效的实体应该只从存储库中删除,这意味着将它从这个存储库使用的底层 EntityManager 中分离出来。

我的问题

repository.exclude(entity) 方法调用(在内部将实体与 EntityManager 分离)似乎不起作用,第二次尝试保存存储库再次失败。

我的(部分)AbstractRepository class

public abstract class AbstractRepository<T> {
    private static Map<String,EntityManagerFactory> entityManagerFactories = new HashMap<String,EntityManagerFactory>();
    private static Map<EntityManagerFactory,EntityManager> entityManagers = new HashMap<EntityManagerFactory,EntityManager>();
    private EntityManager entityManager;
    private List<T> addedEntities = new ArrayList<T>();
    private List<T> deletedEntities = new ArrayList<T>();
    private List<T> updatedEntities = new ArrayList<T>();

    protected Class<T> entityClass = getEntityClass();

    // Many other declarations

    protected EntityManager createEntityManager() throws Exception {
        String persistenceUnitName = getPersistenceUnitName(); // using Reflection
        EntityManagerFactory entityManagerFactory = getEntityManagerFactory(persistenceUnitName);
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        return entityManager;
    }

    public T add(T entity) {
        addedEntities.add(entity);
        return entity;
    }

    public void save() throws Exception {
        EntityManager entityManager = getEntityManager();
        EntityTransaction transaction = entityManager.getTransaction(); 
        transaction.begin();
        try {
            for(T entity : addedEntities)
                entityManager.persist(entity);
            for(T entity : updatedEntities)
                entityManager.merge(entity);
            for(T entity : deletedEntities)
                entityManager.remove(entity);
            transaction.commit();
        } catch(Exception e) {
            if(transaction.isActive())
                transaction.rollback();
            throw e;
        }
        addedEntities.clear();
        updatedEntities.clear();
        deletedEntities.clear();
    }

    public T exclude(T entity) throws Exception {
        if(entity == null)
            return null;
        addedEntities.remove(entity);
        deletedEntities.remove(entity);
        updatedEntities.remove(entity);
        getEntityManager().detach(entity);
        return entity;
    }

    public EntityManager getEntityManager() throws Exception {
        if(entityManager == null)
            entityManager = createEntityManager();
        return entityManager;
    }
}

我的存储库声明

@PersistenceUnit(unitName = "my-ds")
public class MestreRepository extends AbstractRepository<Mestre, Long> {
    public List<Mestre> all() throws Exception {
        List<Mestre> result = getEntityManager().createQuery("from Mestre", Mestre.class).getResultList();
        return result;
    }
}

我的测试代码

public class Main {
    public static void main(String[] args) {
        MestreRepository allMestres = new MestreRepository();

        Mestre mestre1 = new Mestre();
        mestre1.setNome("Mestre 1");
        Mestre mestre2 = new Mestre(); // This one lacks Nome and will fail to be saved
        Mestre mestre3 = new Mestre();
        mestre3.setNome("Mestre 3");

        allMestres.add(mestre1);
        allMestres.add(mestre2);
        allMestres.add(mestre3);

        System.out.println("Saving 3 mestres");
        try {
            allMestres.save();
            System.out.println("All 3 mestres saved"); // never happens!
        } catch(Exception e) {
            System.out.println("Error when salving 3 mestres");
            try {
                System.out.println("Excluding mestre 2");
                allMestres.exclude(mestre2);
                System.out.println("Salving other 2 mestres");
                allMestres.save();
                System.out.println("All 2 mestres salved"); // never happens!
            } catch(Exception e2) {
                System.out.println("Still having errors");
            }
        }

        allMestres.close();
    }
}

我的persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
    xmlns="http://xmlns.jcp.org/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
    <persistence-unit name="my-ds" transaction-type="RESOURCE_LOCAL">
        <class>domain.Mestre</class>
        <class>domain.Detalhe</class>

        <exclude-unlisted-classes>true</exclude-unlisted-classes>
        <properties>
            <!-- Hibernate properties -->
            <property name="hibernate.connection.driver_class" value="oracle.jdbc.OracleDriver"/>
            <property name="hibernate.connection.url" value="xxx"/>
            <property name="hibernate.connection.username" value="yyy"/>
            <property name="hibernate.connection.password" value="***"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect"/>
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.use_sql_comments" value="true"/>
        </properties>
    </persistence-unit>
 </persistence>

更新了 save() 方法

这是 save() 方法的新版本,可以让一切正常进行。对于那些没有造成任何问题的实体,需要在 commit() 之前调用 flush(),而不再需要调用 persist(),但是 merge() 它们,因为它们已经有一个 ID。

public void save() throws Exception {
    List<T> processedEntities = new ArrayList<T>();
    EntityManager entityManager = getEntityManager();
    EntityTransaction transaction = entityManager.getTransaction(); 
    transaction.begin();
    try {
        for(T entity : addedEntities) {
            entityManager.persist(entity);
            processedEntities.add(entity);
        }
        for(T entity : updatedEntities)
            entityManager.merge(entity);
        for(T entity : deletedEntities)
            entityManager.merge(entity);
        entityManager.flush();
        transaction.commit();
    } catch(Exception e) {
        updatedEntities.addAll(processedEntities);
        addedEntities.removeAll(processedEntities);

        if(transaction.isActive())
            transaction.rollback();
        throw e;
    }
    addedEntities.clear();
    updatedEntities.clear();
    deletedEntities.clear();
}

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

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

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

将评论转换为答案:

根据 this guide,您需要 flush() 在分离实体之前

从 OP 更新代码:

public void save() throws Exception {
    List<T> processedEntities = new ArrayList<T>();
    EntityManager entityManager = getEntityManager();
    EntityTransaction transaction = entityManager.getTransaction(); 
    transaction.begin();
    try {
        for(T entity : addedEntities) {
            entityManager.persist(entity);
            processedEntities.add(entity);
        }
        for(T entity : updatedEntities)
            entityManager.merge(entity);
        for(T entity : deletedEntities)
            entityManager.merge(entity);
        entityManager.flush();
        transaction.commit();
    } catch(Exception e) {
        updatedEntities.addAll(processedEntities);
        addedEntities.removeAll(processedEntities);

        if(transaction.isActive())
            transaction.rollback();
        throw e;
    }
    addedEntities.clear();
    updatedEntities.clear();
    deletedEntities.clear();
}
  • 注意

显然 merge 单独用于更新是不够的,即

for(T entity : updatedEntities)
        entityManager.merge(entity);

合并会将分离的实体状态(源)复制到托管实体实例(目标),因此要从数据源更新实体,您需要首先将它们带到托管状态,然后分离并静音 de attache 实体如您所愿,然后调用 merge 使其再次进入托管状态以与 flushcommit

同步

因此正确的更新代码应该是这样的

    public <T> BehindCacheDirector<R, V> update(Class<?> type, T t, Long v) {
            entityManager.detach(entityManager.find(type, v));
            entityManager.merge(t);
    ...        
    }

所有大多数相同的故事都适用于删除,您需要将要删除的实体置于托管状态,然后将其状态更改为删除状态,然后调用同步命令,即

    public BehindCacheBuilder<R, V> remove(Class<?> type, Object object) {
        entityManager.remove(entityManager.find(type, object));
    ...
    }

here 中查找更多信息。