12

情况

我有一个 Entity DiscriminatorColumn,配置为单表继承:

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

'ContainerAssignment' 引用了另一个实体:

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

一个容器可能有ContainerAssignment每个 TYPE 之一。这意味着表的主键由和ContainerAssignment定义。CONTAINER_IDTYPE

ContainerAssignment有一些子类,例如

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

SomeTypeOfContainerAssignment给定的CONTAINER_ID.

问题

如果我将 JPA 定义@Id为 ContainerAssignment 表上的 Container,我可以这样做entityManager.find(SomeTypeOfContainerAssignment.class, containerId),这很棒。这与SELECT * FROM CONTAINER_ASSIGNMENT WHERE CONTAINER_ID = 1 AND TYPE = 'SOME_TYPE';. 它知道它需要在这里检查类型,因为@DiscriminatorValue("SOME_TYPE")实体上的注释。

但是,这意味着从 Container 到 ContainerAssignment 的反向引用会中断,因为 Container 并不是真正的主键。例如,如果 Container 有一个@OneToOne(mappedBy=container) private SomeTypeOfContainerAssignment assignment;,当您在容器中读取时,它将通过类似 的方式读取分配SELECT * FROM CONTAINER_ASSIGNMENT WHERE CONTAINER_ID = 1;,而不进行类型检查。这给了它一个容器的所有分配,然后它看似随机地选择一个,可能是错误的类型,在这种情况下,它会抛出一个异常。

相反,如果我将@IdContainerAssignment 的 JPA 定义为使用容器和类型的复合 id,则对 ContainerAssignment 子类的引用可以正常工作。

但是,我不能这样做entityManager.find(SomeTypeOfContainerAssignment.class, containerId),因为 containerId 不是 id。我必须这样做entityManager.find(SomeTypeOfContainerAssignment.class, new MyPk(containerId, "SOME_TYPE")),这似乎是打败了点@DiscriminatorValue("SOME_TYPE")。如果无论如何我必须在 find 上指定类型,我还不如只使用一个 ContainerAssignment Entity。

问题

有没有一种方法可以对单个表继承实体的子类进行工作引用,其中表上的主键在鉴别器列上是复合的,同时也能够EntityManager.find仅通过主键的一部分来实现不是鉴别器?

4

3 回答 3

1

我将假设 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 的一部分意味着您可以拥有两个具有相同 id 的不同类型的 ContainerAssignment 实例。如果您不知道它的类型,您将需要一个查询来检索 ContainerAssignment。但是,如果您的 id 是从序列生成的,您可以编写另一个查找器方法来隐藏对实体框架的内部调用,返回结果集的第一个结果。

于 2015-10-01T18:15:17.100 回答
0

如果Container有一个双向 OneToOne with SomeTypeOfContainerAssignment, which extends 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;
}

如果所有类型的容器分配都与 C​​Ontainer 有这样的 OneToOne 关联,则可以将 Container 定义为

public abstract class ContainerAssignment {
    @Id
    private Long id;

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

老实说,我不知道你是否允许在表中使用相同的连接列来映射@OneToOne container每个子类的字段。

我认为这是你能拥有的最好的。如果将容器字段放在基类中,则必须将关联定义为 OneToMany/ManyToOne 关联,因为它确实是这样。

我不认为你想做的事情是可能的,我也不会搞乱复合 PK,因为有充分的理由不鼓励它们,而且使用起来是一场噩梦。

于 2012-12-10T21:48:43.383 回答
0

如果你对提供者特定的扩展没问题,Hibernate 提供注解@DiscriminatorOptions

它帮助我解决了鉴别器列是复合主键的一部分的问题。

于 2020-03-31T14:00:34.457 回答