如何从 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
使其再次进入托管状态以与 flush
或 commit
同步
因此正确的更新代码应该是这样的
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 中查找更多信息。
我的环境
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
使其再次进入托管状态以与 flush
或 commit
因此正确的更新代码应该是这样的
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 中查找更多信息。