Hibernate 在关系中创建错误的实体子类型

Hibernate creating wrong entity subtype in relationship

我有一个奇怪的问题,即 hibernate 没有在多对一关系中创建预期的实体类型。我们有以下具有子类层次结构的实体(简化):

@Entity
@Table(name = "A")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DISCRIMINATOR", discriminatorType = DiscriminatorType.STRING, length = 1)
public abstract class A {

    @Id
    ...
    public Long getId() { ... }
    ...
}

@Entity
@DiscriminatorValue("1")
public class A1 extends A {
    ...
}

@Entity
@DiscriminatorValue("2")
public class A2 extends A {
    ...
}


@Entity
@Table(name = "B")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DISCRIMINATOR", discriminatorType = DiscriminatorType.STRING, length = 1)
public abstract class B<AClass extends A> {

    protected AClass a;

    @Id
    ...
    public Long getId() { ... }
    ...

    public abstract AClass getA();
    public void setA(AClass a) { ... }
}

@Entity
@DiscriminatorValue("1")
public class B1 extends B<A1> {
    ...

    @Override
    @ManyToOne(fetch = EAGER)
    @JoinColumn(name = "A_ID")
    public A1 getA() { ... }
}

@Entity
@DiscriminatorValue("2")
public class B2 extends B<A2> {
    ...

    @Override
    @ManyToOne(fetch = EAGER)
    @JoinColumn(name = "A_ID")
    public A2 getA() { ... }
}

persistence.xml中,两个实体都按顺序声明

A2
A1
B2
B1

现在我在数据库中创建 A1 和 B1 的实例:

A1 a1 = new A1();
entityManager.persist(a1);
B1 b1 = new B1();
b1.setA(a1);
entityManager.persist(b1);

我可以看到实例正确保存到数据库中,每个实例都有 ID 1,DISCRIMINATOR 也是 1,B 中的 A_ID 也是 1。

当我现在尝试获取 B 时(在另一个休眠会话中):

B b = entityManager.find(B.class, 1L);

我得到异常:

org.hibernate.PropertyAccessException: Exception occurred inside getter of B
Caused by: java.lang.ClassCastException: A2 cannot be cast to A1
at B1.getA(B1.java:61)
... 108 more 

通过调试,我发现 hibernate 正在创建正确的 B1 类型实体,并为与 A 的关系创建错误的 A2 类型实体。如果 persistence.xml 中的顺序,则会创建正确的类型 A1被改变了。在这种情况下,hibernate 似乎没有考虑 A table 的 DISCRIMINATOR 列,但总是创建配置中声明的第一个子类型。这怎么能解决?注释有问题吗?

(我最初也有方法getA()的具体实现,它的注解在超类型B中,但这会导致类似的问题。)

您在 B1B2 子类中使用相同的连接列 (A_ID)。

在每个子类中使用不同的一个:

@Entity
@DiscriminatorValue("1")
public class B1 extends B<A1> {
    @Override
    @ManyToOne(fetch = EAGER)
    @JoinColumn(name = "A1_ID")
    public A1 getA() { ... }
}

@Entity
@DiscriminatorValue("2")
public class B2 extends B<A2> {
    @Override
    @ManyToOne(fetch = EAGER)
    @JoinColumn(name = "A2_ID")
    public A2 getA() { ... }
}

尽管重用该列可能有意义(对于不同的列,根据子类,每个记录无论如何都会 null),Hibernate 似乎在内部使用列名来唯一标识某些映射元素在同一个 table 内。这就是为什么它可能会忽略 B1 中多对一映射的定义,并使用 B2 中的映射(因为 B2 是在 B1 之前定义的在 persistence.xml).

使用 Hibernate 5.0.2.Final 我能够使用 @ManyToOne(..., targetEntity = A.class) 让您的示例工作。我还用普通的 getter.

替换了 public abstract AClass getA();
@Entity
@Table(name = "B")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DISCRIMINATOR", discriminatorType = DiscriminatorType.STRING, length = 1)
public abstract class B<AClass extends A> {
    private Long id;
    private AClass a;

    @Id
    @GeneratedValue
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @ManyToOne(fetch = FetchType.EAGER, targetEntity = A.class)
    @JoinColumn(name = "A_ID")
    public AClass getA() {
        return a;
    }

    public void setA(AClass a) {
        this.a = a;
    }
}
@Entity
@DiscriminatorValue("1")
public class B1 extends B<A1> {
    // no need to override getA()
}
@Entity
@DiscriminatorValue("2")
public class B2 extends B<A2> {
    // no need to override getA()
}

我在文档中没有找到关于此行为的任何信息。所以我只有我的观察:

  • 没有 targetEntity = A.class Hibernate 甚至没有查询 table ADISCRIMINATOR 列,当急切地从 A 和 [=19] 中获取行时=],就像它已经决定了 A 的实际类型一样。
  • 当我添加 targetEntity = A.class 时,A.DISCRIMINATOR 出现在查询中,并且对象是使用 class A 的右子 class 创建的.

晚了,只是补充一点,当您在具有关系的 classes 中命名具有相同名称的 subclass 字段时,Hibernate 会抛出相同的错误(返回错误的子类型)与他们一起

@Entity
public abstract class Box {
    ...
}

@Entity
public class LargeBox extends Box {
    ...
}

@Entity
public class SmallBox extends Box {
    ...
}

@Entity
public class A {

    @ManyToOne
    private LargeBox box;

}

@Entity
public class B {

    @ManyToOne
    private SmallBox box;
}

当 box 转换为 LargeBox 时,当从数据库中读取 class B 的实例时,上面会抛出一个错误。更新到:

@Entity
public class A {

    @ManyToOne
    private LargeBox largeBox;

}

@Entity
public class B {

    @ManyToOne
    private SmallBox smallBox;
}

...帮我修好了。注意:示例很简短,您也需要相应地更新 getter 签名。