Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DiscriminatorColumn as part of primary key / id

Situation

I have an Entity with a DiscriminatorColumn, configured for single table inheritance:

@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="TYPE")
public class ContainerAssignment{
...
}

'ContainerAssignment' has a reference to another Entity:

@JoinColumn(name="CONTAINER_ID")
private Container container;

A container may have one ContainerAssignment of each TYPE. This means that the primary key of the ContainerAssignment table is defined by the CONTAINER_ID and the TYPE.

ContainerAssignment has some subclasses e.g.

@Entity
@DiscriminatorValue("SOME_TYPE")
public class SomeTypeOfContainerAssignment extends ContainerAssignment{
...
}

There will only be a single SomeTypeOfContainerAssignment instance for a given CONTAINER_ID.

Problem

If I define the JPA @Id as just the Container on the ContainerAssignment table, I can do entityManager.find(SomeTypeOfContainerAssignment.class, containerId), which is great. This runs something along the lines of SELECT * FROM CONTAINER_ASSIGNMENT WHERE CONTAINER_ID = 1 AND TYPE = 'SOME_TYPE';. It knows it needs the TYPE check in here, because of the @DiscriminatorValue("SOME_TYPE") annotation on the Entity.

However, this means that the back references from Container to ContainerAssignment breaks as Container is not really the primary key. For example, if Container has a @OneToOne(mappedBy=container) private SomeTypeOfContainerAssignment assignment;, when you read in a container, it will read in the assignment by something like SELECT * FROM CONTAINER_ASSIGNMENT WHERE CONTAINER_ID = 1;, without the type checking. This gives it all assignments for a container, and then it picks one seemingly at random, potentially of the wrong type, in which case, it throws an exception.

If instead, I define the JPA @Id of ContainerAssignment as a composite id using container and type, references to the sub-classes of ContainerAssignment work fine.

However, I cannot do entityManager.find(SomeTypeOfContainerAssignment.class, containerId), because containerId is not the id. I have to do entityManager.find(SomeTypeOfContainerAssignment.class, new MyPk(containerId, "SOME_TYPE")), which seems to defeate the point of @DiscriminatorValue("SOME_TYPE"). I might as well just use a single ContainerAssignment Entity if I have to specify type on find anyway.

Question

Is there a way to have working references to sub-classes of a single table inheritance Entity where the primary key on the table is composite on the discriminator column, whilst also being able to EntityManager.find by just the part(s) of the primary key which are not the discriminator?

like image 687
Spycho Avatar asked Jun 15 '12 11:06

Spycho


1 Answers

I´m going to assume that the composite primary key of ContainerAssignment is working fine (I really think it may be JPA implementation dependent!), and all that still bothers you is the annoying call to the entityManager.find and PK instantiation.

My solution is to define finder methods independent of the JPA API. Don´t lock yourself to JPA. The simplest way is to just define a static finder at your domain class (or, define another class with just finders, if you want to keep domain uncoupled do JPA. Dig at IoC to know how to do that).

At ContainerAssignment (or your finder class):

public static <T extends ContainerAssignment> T findByPK(EntityManager manager,Class<T> type,long id) {
    DiscriminatorValue val = type.getAnnotation(DiscriminatorValue.class); // this is not optimal...can be cached...
    return (T) manager.find(type, new MyPk(containerId, val.getValue()));
}

At your code:

SomeTypeOfContainerAssignment ca = ContainerAssignment.findByPK(entityManager,SomeTypeOfContainerAssignment.class,containerId);

Notice that making the type part of the PK means that you can have two ContainerAssignment instances of distinct types with the same id. You going to need a Query to retrieve ContainerAssignment if you don´t know its type. If, however, your id is generated from a sequence, you can just write another finder method that hides the inner calls to entity framework, returning the first result of the resultset.

like image 108
atorres Avatar answered Oct 10 '22 20:10

atorres