Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPA. How do I subclass existing entity and keep its ID?

Assume I have two classic non-abstract JPA classes: Person and Student.

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Person {
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private String id;
  // ...
}

@Entity
public class Student extends Person {
  // ...
}

Now person having some id enters the university and becomes a student. How do I process that fact in JPA and keep person's id?

student = new Student();
student.setPersonData(person.getPersonData());
student.setId(person.getId());
entityManager.persist(student);

Code above produces 'Detached entity passed to persist' Exception, whereas using entityManager.merge(student) skips assigned id and creates two new entities of Person and Student with new id. Any ideas how can I keep original Id?

like image 224
andbi Avatar asked Jun 21 '11 06:06

andbi


1 Answers

The JPA specification prohibits an application from changing the identity of an entity (Section 2.4):

The application must not change the value of the primary key[10]. The behavior is undefined if this occurs.[11]

Also, in the case of tables where inheritance across entities is performed using the joined inheritance strategy, the identity is defined in the root class alone. All child classes only store the local attribute of the class.

By invoking student.setId(person.getId()); you are attempting to change the identity of the not-yet-persisted entity to that of an existing one. This in itself doesn't make sense, especially since you are generating values for the identities using the sequence generation strategy of AUTO (which is usually a TABLE).

If we were to ignore the previous points, and if you wish to convert the Person to a Student, without losing the identity, well that is more or less impossible (at least in a clean manner, as @axtavt has pointed out). The simple reason is that you cannot downcast successfully from a Person to a Student at runtime, for that is the natural object-oriented operation that you are attempting to perform in real life. Even if you downcast successfully in a hypothetical manner, the original entity has a discriminator column value that needs modification; without knowing how the JPA provider uses and caches this value, any attempt to make changes in the data, using native SQL might result in more trouble than it is worth.

If you are not averse to losing the generated IDs (after all one usually generates them, so that you could use natural keys or you didn't have to share these generated IDs publicly), you ought to create a copy of the Person object and recreate it as a Student. This will ensure that the JPA provider will also populate the discriminator column correctly. Also, you'll need to delete the original Person entity.

All of the above, is considering that you will not modify the current object model. If you can modify the object model, you do might a chance of retaining the original ID. This would require that you drop the inheritance hierarchy, for it does not naturally suit your domain in the first place. The attempt to downcast from a Person to a Student indicates that inheritance is not a natural fit. Following @axtavt's advice is more appropriate, for it actually means favoring composition over inheritance, if you read it carefully (at least I read it that way).

The JPA Wikibook discusses this situation in the section on Object Reincarnation. Note the specific advice provided on having a type attribute in your entity instead of using inheritance to change the type of the object.

Reincarnation

Normally an object that has been removed, stays removed, but in some cases you may need to bring the object back to life. This normally occurs with natural ids, not generated ones, where a new object would always get an new id. Generally the desire to reincarnate an object occurs from a bad object model design, normally the desire to change the class type of an object (which cannot be done in Java, so a new object must be created). Normally the best solution is to change your object model to have your object hold a type object which defines its type, instead of using inheritance. But sometimes reincarnation is desirable.

like image 191
Vineet Reynolds Avatar answered Oct 15 '22 06:10

Vineet Reynolds