While using entities in an inheritance hierarchy, you may sometimes need to change the entity from superclass type to subclass type or from one subclass type to another.
For example, a Customer entity needs to be changed to its subclass PrivilegedCustomer
or a PartTimeEmployee
entity needs to be changed to FullTimeEmployee
where both PartTimeEmployee
and FullTimeEmployee
are subclasses of class Employee.
In Java, we cannot change the object type from one class to another. Similarly, there is no way to change the entity from one class type to another in Java Persistence API (JPA). In order to change the entity to another class type in JPA, you will have to remove the existing entity, create a new entity of the required type, copy state of existing entity to the new one and persist the new entity. This approach has a drawback when auto-generated @Id
is used. While creating entity of another class type, we cannot use the same id of the existing entity for the new one. One way to resolve this problem is not to use @GeneratedValue
annotation along with @Id
, instead another class can generate the id value and assign it to the entity.
We may not need to use the same id while converting an entity from one subclass type to another. However, we might have to maintain the same id when we convert entity to the subclass type.
Another approach to modify the entity class type is to execute the native SQL queries. In single table inheritance strategy, an update SQL query to update the discriminator column can be executed to modify the entity type. In joined table and table per class inheritance strategies, insert-update-delete SQL queries may need to be executed to change the entity type. This approach is not recommend as we lose the benefits the JPA. Also, the native query won’t update the state of managed entities.
The native queries are always executed in a separate transaction. Versioning needs to be implemented on the entities which are modified by native queries as the managed and detached entities may become outdated.
Using native query to change the entity type is the optimum solution when the following conditions are true:
- Single table inheritance strategy is used
- You want to change the entity from superclass to subclass type
- Versioning is taken care in the entity
- Entity uses auto-generated Id and you want the same Id to be used after changing the entity type (optional)
The Listing 1 shows two classes - Customer
and PrivilegedCustomer
– using SINGLE_TABLE inheritance strategy.
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name= “type”,
discriminatorType=DiscriminatorType.INTEGER)
@DiscriminatorValue(“0”)
public class Customer {
@Id @GeneratedValue private Long customerId;
@Version private int version;
...
}
@Entity
@DiscriminatorValue(“1”)
public class PrivilegedCustomer extends Customer {
...
}
Listing 1.Two classes using SINGLE_TABLE inheritance strategy
When you want to modify a customer record from Customer
to PrivilegedCustomer
type, the best way to do it is to execute a native SQL UPDATE query to modify the discriminator value. The query can be executed dynamically using EntityManager
as shown in Listing 2 or it can be declared by using @NamedNativeQuery
annotation and executed as shown in Listing 3.
The native SQL queries should always be executed in a new transaction. Hence the transaction attribute REQUIRES_NEW is used.
@PersistenceContext EntityManager em;
...
em.createNativeQuery(“UPDATE CUSTOMER SET TYPE = ?, ”
“ VERSION = VERSION + 1 WHERE CUSTOMERID = ?”)
.setParameter(1, 1)
.setParameter(2,customerId)
.executeUpdate();
Listing 2. Executing native SQL query dynamically to modify the entity to PrivilegedCustomer type
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name= “type”,
discriminatorType=DiscriminatorType.INTEGER)
@DiscriminatorValue(“0”)
@NamedNativeQuery(name=“Customer.changeCustomerType”,
query=“UPDATE CUSTOMER SET TYPE = ?, ” +
“VERSION = VERSION + 1 WHERE CUSTOMERID = ?”)
public class Customer {
@Id private Long customerId;
...
}
@Stateless
public class Admin {
@PersistenceContext EntityManager em;
@TransactionAttribute(
TransactionAttributeType.REQUIRES_NEW)
public void changeCustomerTypeToPrivileged(
long customerId) {
em.createNamedQuery(“Customer.changeCustomerType”)
.setParameter(1,1).setParameter(1,customerId)
.executeUpdate();
}
}
Listing 3. Executing native SQL query defined using @NamedNativeQuery
Versioning & Locking
When a customer type is changed to PrivilegedCustomer, to avoid changing the type back to Customer by managed or detached entities, versioning and locking should be used. The native SQL query should explicitly increment the version.