DiscriminatorColumn as part of primary key / id
Asked Answered
Q

3

14

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?

Qualification answered 15/6, 2012 at 11:17 Comment(0)
B
1

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.

Bluey answered 1/10, 2015 at 18:15 Comment(0)
E
1

If you are okay with provider specific extension, Hibernate provides annotation @DiscriminatorOptions.

It helped me to solve a problem where the discriminator column is part of composite primary key.

Elisavetpol answered 31/3, 2020 at 14:0 Comment(0)
E
0

If Container has a bidirectional OneToOne with SomeTypeOfContainerAssignment, which extends ContainerAssignment, then the container field should not be defined and mapped in ContainerAssignment, but in SomeTypeOfContainerAssignment:

public class Container {
    @Id
    private Long id;

    @OneToOne(mappedBy = "container")
    private SomeTypeOfContainerAssignment someTypeOfContainerAssignment;
}

public class ContainerAssignment {
    @Id
    private Long id;
}

public class SomeTypeOfContainerAssignment extends ContainerAssignment {
    @OneToOne
    private Container container;
}

If all the types of container assignments have such a OneToOne association with COntainer, you can define the Container as

public abstract class ContainerAssignment {
    @Id
    private Long id;

    public abstract Container getContainer();
    public abstract void setContainer(Container container);
}

To be honest, I don't know if you're allowed to use the same join column in the table to map the @OneToOne container fields of each subclass.

I think this is the best you can have. If you put the container field in the base class, then you must define the association as a OneToMany/ManyToOne association, since it's what it really is.

I don't think what you want to do is possible, and I wouldn't mess with composite PKs, as they're discouraged for good reasons, and a nightmare to use.

Exhilarative answered 10/12, 2012 at 21:48 Comment(2)
Thanks for your answer, but it doesn't really solve the question - is there a way to put the DiscriminatorColumn kinda inside the PK? I agree that an artificial PK is the best choice, but in many systems we can't control the schema and have to deal with composite PKs.Piroshki
Also notice that NEVER using composite primary keys is an anti-pattern, according to Bill Karwin book.Bluey

© 2022 - 2024 — McMap. All rights reserved.