8

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

@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,鉴别器也是 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 表的 DISCRIMINATOR 列,但总是创建配置中声明的第一个子类型。如何解决这个问题?注释有问题吗?

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

4

3 回答 3

5

使用 Hibernate 5.0.2.Final,我能够使用@ManyToOne(..., targetEntity = A.class). 我也换成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.classHibernate,甚至在急切地从 中获取行时甚至都不会查询表的列,DISCRIMINATOR就像它已经决定了 的实际类型一样。 AABA
  • 当我添加targetEntity = A.class时,A.DISCRIMINATOR出现在查询中,并且对象是使用 class 的正确子类创建的A
于 2015-10-04T05:12:36.860 回答
3

A_ID在两个B1子类中使用相同的连接列 ( ) B2

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

@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 在内部使用列名来唯一标识同一表中的某些映射元素。这就是为什么它可能会忽略 in 中的多对一映射的定义,B1并为它使用一个 from B2(因为B2之前B1在 中定义persistence.xml)。

于 2015-10-02T15:39:43.790 回答
1

迟到这一点,但只是补充一点,当您在与它们有关系的类中命名具有相同名称的子类字段时,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 时,当从数据库中读取 B 类的实例时,上面会抛出一个错误。更新至:

@Entity
public class A {

    @ManyToOne
    private LargeBox largeBox;

}

@Entity
public class B {

    @ManyToOne
    private SmallBox smallBox;
}

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

于 2019-06-14T11:41:57.423 回答