我的环境
Java 7/JPA 2/Hibernate 5.1。
我的情景
我正在构建一个存储库模式实现。所有的代码都是编写的,当没有发生错误情况时,所有代码都可以正常工作。
但是,假设将三个实体实例添加到存储库中。第一列和第三列都可以,但第二列缺少强制(非空)列的值。保存存储库时将返回DB错误。
当面对这种情况时,批处理过程应该只写一个日志消息,跳过无效的对象,然后继续到其他的对象。为了做到这一点,这个无效的实体只应该从存储库中删除,从存储库使用的底层EntityManager中分离它意味着什么。
我的问题
repository.exclude(entity)
方法调用(在内部将实体与EntityManager分离)似乎不起作用,第二次保存存储库的尝试再次失败。
我的(部分) AbstractRepository类
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()
方法的一个新版本,它可以使事情正常工作。它需要在flush()
之前调用commit()
,对于那些没有造成任何问题的实体,不需要调用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();
}
发布于 2018-08-15 02:53:21
将评论转换为回答:
根据本指南,在分离实体之前需要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();
}
发布于 2022-04-11 11:12:51
显然,单独使用merge
进行更新是不够的。
for(T entity : updatedEntities)
entityManager.merge(entity);
合并将将分离的实体状态(源)复制到托管实体实例(目标),因此,要从数据源更新实体,首先需要将它们带到托管状态,然后按您的意愿分离和静音detach实体,然后调用merge
使其再次进入托管状态以与flush
或commit
同步。
因此,用于更新的适当代码如下所示
public <T> BehindCacheDirector<R, V> update(Class<?> type, T t, Long v) {
entityManager.detach(entityManager.find(type, v));
entityManager.merge(t);
...
}
对于delete,您需要将要删除的实体更改为托管状态,然后将其状态更改为删除状态,然后调用同步命令,即
public BehindCacheBuilder<R, V> remove(Class<?> type, Object object) {
entityManager.remove(entityManager.find(type, object));
...
}
在这里中可以找到更多。
发布于 2018-08-14 05:12:18
不幸的是,在当前的JPA实现AFAIR中,无法将一个对象与实体管理器断开连接。
EntityManager.clear()将断开所有JPA对象的连接,因此在所有情况下,如果您计划保持连接的其他对象,这可能不是一个合适的解决方案。
所以,最好的选择是克隆对象,并将克隆传递给更改对象的代码。由于原始和不可变的对象字段是由默认的克隆机制以适当的方式处理的,所以您不必编写大量的管道代码(除了深入克隆任何聚合的结构之外)。
https://stackoverflow.com/questions/51842232
复制