Note that there are some explanatory texts on larger screens.

plurals
  1. POHow to change Entity type in JPA?
    primarykey
    data
    text
    <p>In my specific case, I am making use of a discriminator column strategy. This means that my JPA implementation (Hibernate) creates a <em>users</em> table with a special <em>DTYPE</em> column. This column contains the class name of the entity. For example, my <em>users</em> table can have subclasses of <strong>TrialUser</strong> and <strong>PayingUser</strong>. These class names would be in the <em>DTYPE</em> column so that when the EntityManager loads the entity from the database, it knows which type of class to instantiate. </p> <p>I've tried two ways of converting Entity types and both feel like dirty hacks:</p> <ol> <li>Use a native query to manually do an UPDATE on the column, changing its value. This works for entities whose property constraints are similar.</li> <li>Create a new entity of the target type, do a <em>BeanUtils.copyProperties()</em> call to move over the properties, save the new entity, then call a named query which manually replaces the new Id with the old Id so that all the foreign key constraints are maintained.</li> </ol> <p>The problem with #1 is that when you manually change this column, JPA doesn't know how to refresh/reattach this Entity to the Persistance Context. It expects a <strong>TrialUser</strong> with Id 1234, not a <strong>PayingUser</strong> with Id 1234. It fails out. Here I could probably do an EntityManager.clear() and detach all Entities/clear the Per. Context, but since this is a Service bean, it would wipe pending changes for all users of the system.</p> <p>The problem with #2 is that when you delete the <strong>TrialUser</strong> all of the properties you have set to Cascade=ALL will be deleted as well. This is bad because you're only trying to swap in a different User, not delete all the extended object graph.</p> <p><strong><em>Update 1</em></strong>: The problems of #2 have made it all but unusable for me, so I've given up on trying to get it to work. The more elegant of the hacks is definitely #1, and I have made some progress in this respect. The key is to first get a reference to the underlying Hibernate Session (if you're using Hibernate as your JPA implementation) and call the Session.evict(user) method to remove only that single object from your persistance context. Unfortunitely there is no pure JPA support for this. Here is some sample code:</p> <pre><code> // Make sure we save any pending changes user = saveUser(user); // Remove the User instance from the persistence context final Session session = (Session) entityManager.getDelegate(); session.evict(user); // Update the DTYPE final String sqlString = "update user set user.DTYPE = '" + targetClass.getSimpleName() + "' where user.id = :id"; final Query query = entityManager.createNativeQuery(sqlString); query.setParameter("id", user.getId()); query.executeUpdate(); entityManager.flush(); // *** PROBLEM HERE *** // Load the User with its new type return getUserById(userId); </code></pre> <p>Notice the manual <strong>flush()</strong> which throws this exception:</p> <pre><code>org.hibernate.PersistentObjectException: detached entity passed to persist: com.myapp.domain.Membership at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:102) at org.hibernate.impl.SessionImpl.firePersistOnFlush(SessionImpl.java:671) at org.hibernate.impl.SessionImpl.persistOnFlush(SessionImpl.java:663) at org.hibernate.engine.CascadingAction$9.cascade(CascadingAction.java:346) at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:291) at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:239) at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:192) at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:319) at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:265) at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:242) at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:192) at org.hibernate.engine.Cascade.cascade(Cascade.java:153) at org.hibernate.event.def.AbstractFlushingEventListener.cascadeOnFlush(AbstractFlushingEventListener.java:154) at org.hibernate.event.def.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:145) at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:88) at org.hibernate.event.def.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:58) at org.hibernate.impl.SessionImpl.autoFlushIfRequired(SessionImpl.java:996) at org.hibernate.impl.SessionImpl.executeNativeUpdate(SessionImpl.java:1185) at org.hibernate.impl.SQLQueryImpl.executeUpdate(SQLQueryImpl.java:357) at org.hibernate.ejb.QueryImpl.executeUpdate(QueryImpl.java:51) at com.myapp.repository.user.JpaUserRepository.convertUserType(JpaUserRepository.java:107) </code></pre> <p>You can see that the <strong>Membership</strong> entity, of which <strong>User</strong> has a OneToMany Set, is causing some problems. I don't know enough about what's going on behind the scenes to crack this nut.</p> <p><strong><em>Update 2</em></strong>: The only thing that works so far is to change DTYPE as shown in the above code, then call <strong>entityManager.clear()</strong></p> <p>I don't completely understand the ramifications of clearing the entire persistence context, and I would have liked to get <strong>Session.evict()</strong> working on the particular Entity being updated instead.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload