DiscriminatorColumn как часть первичного ключа/идентификатора

Ситуация

У меня есть сущность с DiscriminatorColumn, настроенная для наследования одной таблицы:

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

«ContainerAssignment» имеет ссылку на другой объект:

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

Контейнер может иметь по одному ContainerAssignment каждого ТИПА. Это означает, что первичный ключ таблицы ContainerAssignment определяется CONTAINER_ID и TYPE.

ContainerAssignment имеет несколько подклассов, например.

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

Для данного CONTAINER_ID будет только один экземпляр SomeTypeOfContainerAssignment.

Проблема

Если я определяю JPA @Id просто как Container в таблице ContainerAssignment, я могу сделать entityManager.find(SomeTypeOfContainerAssignment.class, containerId), и это здорово. Это работает что-то вроде SELECT * FROM CONTAINER_ASSIGNMENT WHERE CONTAINER_ID = 1 AND TYPE = 'SOME_TYPE';. Он знает, что здесь нужна проверка TYPE из-за аннотации @DiscriminatorValue("SOME_TYPE") на Entity.

Однако это означает, что обратные ссылки из Container в ContainerAssignment прерываются, поскольку Container на самом деле не является первичным ключом. Например, если у контейнера есть @OneToOne(mappedBy=container) private SomeTypeOfContainerAssignment assignment;, когда вы читаете в контейнере, он будет читать в присваивании что-то вроде SELECT * FROM CONTAINER_ASSIGNMENT WHERE CONTAINER_ID = 1; без проверки типа. Это дает ему все назначения для контейнера, а затем он выбирает одно, казалось бы, случайное, потенциально неправильного типа, и в этом случае генерирует исключение.

Если вместо этого я определяю JPA @Id ContainerAssignment как составной идентификатор, используя контейнер и тип, ссылки на подклассы ContainerAssignment работают нормально.

Однако я не могу сделать entityManager.find(SomeTypeOfContainerAssignment.class, containerId), потому что containerId не является идентификатором. Я должен сделать entityManager.find(SomeTypeOfContainerAssignment.class, new MyPk(containerId, "SOME_TYPE")), что, кажется, противоречит пункту @DiscriminatorValue("SOME_TYPE"). С тем же успехом я мог бы просто использовать одну сущность ContainerAssignment, если мне все равно нужно указать тип при поиске.

Вопрос

Есть ли способ иметь рабочие ссылки на подклассы объекта наследования одной таблицы, где первичный ключ в таблице является составным в столбце дискриминатора, а также иметь возможность EntityManager.find только частью (частями) первичного ключа, который не дискриминатор?


person Spycho    schedule 15.06.2012    source источник


Ответы (3)


Я собираюсь предположить, что составной первичный ключ ContainerAssignment работает нормально (я действительно думаю, что это может зависеть от реализации JPA!), и все, что вас все еще беспокоит, — это надоедливый вызов entityManager.find и создание экземпляра PK.

Мое решение состоит в том, чтобы определить методы поиска, независимые от JPA API. Не привязывайтесь к JPA. Самый простой способ - просто определить статический искатель в классе вашего домена (или определить другой класс только с искателями, если вы хотите, чтобы домен не был привязан к JPA. Копайте в IoC, чтобы узнать, как это сделать).

В ContainerAssignment (или в вашем классе поиска):

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()));
}

В вашем коде:

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

Обратите внимание, что включение типа в PK означает, что вы можете иметь два экземпляра ContainerAssignment разных типов с одним и тем же идентификатором. Вам понадобится запрос для получения ContainerAssignment, если вы не знаете его тип. Однако, если ваш идентификатор генерируется из последовательности, вы можете просто написать другой метод поиска, который скрывает внутренние вызовы структуры сущностей, возвращая первый результат набора результатов.

person atorres    schedule 01.10.2015

Если Container имеет двунаправленный OneToOne с SomeTypeOfContainerAssignment, который расширяет ContainerAssignment, то поле контейнера должно определяться и сопоставляться не в ContainerAssignment, а в 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;
}

Если все типы назначений контейнера имеют такую ​​ассоциацию OneToOne с CONtainer, вы можете определить контейнер как

public abstract class ContainerAssignment {
    @Id
    private Long id;

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

Честно говоря, я не знаю, разрешено ли вам использовать один и тот же столбец соединения в таблице для отображения @OneToOne container полей каждого подкласса.

Я думаю, это лучшее, что у вас может быть. Если вы поместите поле контейнера в базовый класс, то вы должны определить ассоциацию как ассоциацию OneToMany/ManyToOne, поскольку это то, чем она является на самом деле.

Я не думаю, что то, что вы хотите сделать, возможно, и я бы не стал связываться с составными ПК, поскольку они не рекомендуются по уважительным причинам и являются кошмаром для использования.

person JB Nizet    schedule 10.12.2012
comment
Спасибо за ваш ответ, но на самом деле он не решает вопрос - есть ли способ поместить DiscriminatorColumn внутри ПК? Я согласен с тем, что искусственный ПК — лучший выбор, но во многих системах мы не можем контролировать схему и вынуждены иметь дело с составными ПК. - person Bruno Kim; 14.12.2012
comment
Также обратите внимание, что, согласно книге Билла Карвина, НИКОГДА не используйте составные первичные ключи. - person atorres; 01.10.2015

Если вас устраивает расширение для конкретного провайдера, Hibernate предоставляет аннотацию @DiscriminatorOptions.

Это помогло мне решить проблему, когда столбец дискриминатора является частью составного первичного ключа.

person zeratul021    schedule 31.03.2020