简体   繁体   English

如何从 EntityManager 中分离实体

[英]How to detach an entity from an EntityManager

My environment我的环境

Java 7/JPA 2/Hibernate 5.1. Java 7/JPA 2/休眠 5.1。

My Scenario我的场景

I'm building a Repository pattern implementation.我正在构建一个存储库模式实现。 All code is written and it all works fine when no error condition happens.所有代码都已编写,并且在没有错误情况发生时一切正常。

However, let's say that three entity instances are added to the repository.但是,假设将三个实体实例添加到存储库中。 First and third are ok, but second lacks value for a mandatory (not null) column.第一和第三都可以,但第二个缺少强制(非空)列的值。 A DB error will be returned when the repository is saved.保存存储库时将返回数据库错误。

When facing this condition a batch process should just write a log message somewhere, skip the invalid object and continue to the others.当遇到这种情况时,批处理应该只在某处写一条日志消息,跳过无效的 object 并继续其他的。 In order to do that, this invalid entity should only be removed from the repository, what means to detach it from the underlying EntityManager this repository uses.为了做到这一点,这个无效的实体应该只从存储库中删除,这意味着将它从这个存储库使用的底层 EntityManager 中分离出来。

My problem我的问题

The repository.exclude(entity) method call (that internally detaches the entity from the EntityManager) seems to not be working and a second attempt to save the repository fails again. repository.exclude(entity)方法调用(在内部将实体与 EntityManager 分离)似乎不起作用,第二次尝试保存存储库再次失败。

My (partial) AbstractRepository class我的(部分)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;
    }
}

My Repository declaration我的存储库声明

@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;
    }
}

My test code我的测试代码

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();
    }
}

My persistence.xml我的坚持.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>

The save() method updated更新了 save() 方法

Here is a new version of the save() method that makes things work.这是使一切正常的save()方法的新版本。 It was needed to call flush() before commit() and no more persist() for those entities that did not create any problem, but merge() them, since they already have an Id.需要在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();
}

Converting an comment to an answer: 将评论转换为答案:

According to this guide , you need to flush() before detaching the entity 根据本指南 ,在分离实体之前,需要flush()

Update code from OP: 从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();
}
  • CAUTION警告

Definitely the usage of merge as stand alone for updating is not suffice ie绝对使用merge作为独立的更新是不够的,即

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

The merge is going to copy the detached entity state (source) to a managed entity instance (destination) thus for updating entities from datasource you need to first bring them to managed state first then detach and mute the de attache entity as you wish then call the merge to bring it again to managed state to synchronization with flush or commit合并会将分离的实体 state(源)复制到托管实体实例(目标),因此要从数据源更新实体,您需要首先将它们带到托管 state,然后根据需要分离并静音 de attache 实体,然后调用merge将其再次带到托管 state 以与flushcommit同步

在此处输入图像描述

Thus the proper code for updating will be like this因此,正确的更新代码将是这样的

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

All most Same story holds for delete, you need to bring the entity that you wish to delete to managed state then change it state to remove state then calling syntonization commands, ie所有大多数相同的故事适用于删除,您需要将要删除的实体带到托管 state 然后将其更改为 state 以删除 state 然后调用同步命令,即

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

Find more in here .这里找到更多。

Unfortunately, there's no way to disconnect one object from the entity manager in the current JPA implementation, AFAIR. 不幸的是,在当前的JPA实现AFAIR中,无法从实体管理器断开一个对象的连接。

EntityManager.clear() will disconnect all the JPA objects, so that might not be an appropriate solution in all the cases, if you have other objects you do plan to keep connected. EntityManager.clear()将断开所有JPA对象的连接,因此,如果您确实计划保持其他连接,则在所有情况下这都不是一个合适的解决方案。

So your best bet would be to clone the objects and pass the clones to the code that changes the objects. 因此,最好的选择是克隆对象,并将克隆传递给更改对象的代码。 Since primitive and immutable object fields are taken care of by the default cloning mechanism in a proper way, you won't have to write a lot of plumbing code (apart from deep cloning any aggregated structures you might have). 由于默认克隆机制会以适当的方式处理原始和不可变的对象字段,因此您无需编写大量的管道代码(除了深度克隆您可能拥有的任何聚合结构之外)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM