Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing the type of an entity preserving its ID

I am using hibernate as a persistence layer. There are 2 entities that live in the same table extending one superclass with single table inheritance strategy.

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class A {
    @Id
    @GeneratedValue
    protected Long id;

    // some common fields for B and C
}


@Entity
public class B extends A {
    // B-specific fields
}

@Entity
public class C extends A {
    // C-specific fields
}

I have an instance of B with id=4. How do I change the type of this instance to C preserving it's ID (4)?

B b = em.find(B.class, 4L);
C c = convertToC(b);
c.setId(b.getId());
em.remove(b);
em.persist(c);

The code above fails with

org.hibernate.PersistentObjectException: detached entity passed to persist: C

Is it possible at all?

like image 346
artemb Avatar asked Jul 13 '09 11:07

artemb


2 Answers

Hibernate attempts to make persistence as transparent as it possibly can - which means it tries to follow the same principles as normal Java objects. Now, rephrasing your question in Java, you'd get:

How can I convert an instance of B class into an instance of (incompatible) C class?

And you know the answer to that - you can't. You can create a new instance of C and copy necessary attributes, but B will always be B, never C. Thus the answer to your original question is - it cannot be done via JPA or Hibernate API.

However, unlike plain Java, with Hibernate you can cheat :-) InheritanceType.SINGLE_TABLE is mapped using @DiscriminatorColumn and in order to convert B into C you need to update its value from whatever's specified for B into whatever's specified for C. The trick is - you cannot do it using Hibernate API; you need to do it via plain SQL. You can, however, map this update statement as named SQL query and execute it using Hibernate facilities.

The algorithm, therefore, is:

  1. Evict B from session if it's there (this is important)
  2. Execute your named query.
  3. Load what-is-now-known-as-C using former B's id.
  4. Update / set attributes as needed.
  5. Persist C
like image 177
ChssPly76 Avatar answered Oct 21 '22 22:10

ChssPly76


In this case, "c" is an object which the hibernate session knows nothing about, but it has an ID, so it assumes that the object has already been persisted. In that context, persist() makes no sense, and so it fails.

The javadoc for Hibernate Session.persist() (I know you're not using the Hibernate API, but the semantics are the same, and the hibernate docs are better) says "Make a transient instance persistent". If your object already has an ID, it's not transient. Instead, it thinks it's a detached instance (i.e. an instance that has been persisted, but is not associated with the current session).

I suggest you try merge() instead of persist().

like image 28
skaffman Avatar answered Oct 21 '22 20:10

skaffman