2015-09-28 67 views
7

我有一个奇怪的问题,即hibernate没有在多对一的关系中创建预期的实体类型。我们有子类层次结构下面的实体(简化):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 

现在我在DB创建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(在另一个Hibernate的Session):

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()的,但是这会导致类似的问题。)

回答

3

随着休眠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.class休眠甚至没有查询表ADISCRIMINATOR列时急切地从AB一起提取行,就像它已经做了一个关于实际类型的A决定。
  • 当我添加targetEntity = A.class时,A.DISCRIMINATOR出现在查询中,而对象是用类A的正确子类创建的。
+0

谢谢你的回答。昨天我有机会测试这个,它在问题的场景中工作:) – Gandalf

3

您在两个B1使用相同的连接列(A_ID)和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使用列名内部唯一标识同一个表内的一些映射元素。这就是为什么它可能会忽略B1中多对一映射的定义,并使用B2中的一个作为它的原因(因为B2B1之前在persistence.xml之前定义)。

+0

Thanks Dragan for your answer! :)我还没有时间去测试它是否有效,因为其他答案有效并且具有一些优势:1.代码少。 2.减少数据库列3.我们可以在数据库列上保留非空和外键约束。 – Gandalf

相关问题