package com.softwaremill.common.cdi.persistence; import org.hibernate.proxy.HibernateProxy; import com.softwaremill.common.util.persistance.Identifiable; import javax.inject.Inject; import javax.persistence.EntityManager; /** * @author Adam Warski (adam at warski dot org) * @author Tomasz Szymanski (szimano at szimano dot org) * @author Pawel Stawicki (pawelstawicki at gmail dot com) */ public class EntityWriter { @Inject @ReadOnly private EntityManager readOnlyEm; @Inject @Writeable private EntityManager writeableEm; @SuppressWarnings({"unchecked"}) /** * Stores changes made to an entity. All entites must have {@code DETACH} cascade enabled on all associations! * @param entity The entity to be written. * @return The written entity. */ public <T extends Identifiable<?>> T write(T entity) { // First detaching the entity from the RO context if (readOnlyEm.contains(entity)) { readOnlyEm.detach(entity); } else if (entity.getId() != null) { // If the entity is not in the RO EM, and is persistent, it is possible that the RO EM contains a different // copy of the entity. It must also be detached, hence first looking it up. It is possible that the find() // loads the entity into the EM, but it's not possible to check if an entity is loaded into an EM simply // by id. Class<T> entityTargetClass = (Class<T>) getTargetClassIfProxied(entity.getClass()); readOnlyEm.detach(readOnlyEm.find(entityTargetClass, entity.getId())); } // Writing the changes T writtenEntity = writeableEm.merge(entity); writeableEm.flush(); // Now looking up a fresh copy of the entity. We won't get a stale one because we removed it from the context // in the beginning return (T) readOnlyEm.find(getTargetClassIfProxied(writtenEntity.getClass()), writtenEntity.getId()); } /** * Executes an update query, created with the given creator, using the writeable entity manager. * The read only entity manager is cleared, so all entities with lazy values will have to be re-read. * @param queryCreator A creator to create the update query. * @return The number of affected rows. */ public int executeUpdate(QueryCreator queryCreator) { int result = queryCreator.createQuery(writeableEm).executeUpdate(); // Now we need to clear the read only entity manager, so that it doesn't contain stale entities. // This could result in some lazy initialization exception. readOnlyEm.clear(); return result; } /** * Deletes an entity and detaches it from the read only manager. * @param entity Entity to delete */ @SuppressWarnings({"unchecked"}) public <T extends Identifiable<?>> void delete(T entity) { // First detaching the entity from the RO context if (readOnlyEm.contains(entity)) { readOnlyEm.detach(entity); } else if (entity.getId() != null) { // If the entity is not in the RO EM, and is persistent, it is possible that the RO EM contains a different // copy of the entity. It must also be detached, hence first looking it up. It is possible that the find() // loads the entity into the EM, but it's not possible to check if an entity is loaded into an EM simply // by id. Class<T> entityTargetClass = getTargetClassIfProxied((Class<T>) entity.getClass()); readOnlyEm.detach(readOnlyEm.find(entityTargetClass, entity.getId())); } // attach the entity entity = (T) writeableEm.find(entity.getClass(), entity.getId()); // Then delete the entity writeableEm.remove(entity); writeableEm.flush(); } private <T> Class<T> getTargetClassIfProxied(Class<T> clazz) { if (clazz == null) { return null; } else if (HibernateProxy.class.isAssignableFrom(clazz)) { // Get the source class of Javassist proxy instance. return (Class<T>) clazz.getSuperclass(); } return clazz; } }