My environment
Java 7/JPA 2/Hibernate 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. 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.
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.
My (partial) 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 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
Here is a new version of the save()
method that makes things work. 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.
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(); }
Advertisement
Answer
Converting an comment to an answer:
According to this guide, you need to flush()
before detaching the entity
Update code from 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(); }