SpringJPA中关于Entity继承的问题(上)

coding

参考资料: https://developer.aliyun.com/article/312873

org.hibernate.WrongClassException:Object with [id=11] was not of the specified subclass: xxxx.entity(loaded object was of wrong class ...

这个错误其实是说,在构建当前的Entity1列表(LIST)的时候,发现该Entity所共用对象的另一个Entity2中,已经存在了id=11的对象,并且Entity1和Entity2的id=11的对象在结构上是不一样的,无法覆盖。 看到这个错误是不是有点蒙,像是在听天书的感觉?其实,这个Exception的本质是和JPA中hibernate的继承映射有关。

当前笔者的项目中,定义了BaseEntity,其中包含了code(编码),name(名称),elementcode(要素的tag)这些通用属性。然后,分别定义了Entity1和Entity2,且都集成自BaseEntity。Entity1和Entity2的的elementcode返回的内容不一样。举例子来描述的话,就相当于两组不同的字典要素,一个是桌子(Entity1,elementcode=桌子),一个是椅子(Entity2,elementcode=椅子),他们都属于家具(BaseEntity)。这是一种比较常见的继承的实践。

在基于sringboot-jpa的架构上,用代码描述如下

`

// 家具

@Entity

@Data

public abstract class BaseEntity{

@Id

@GeneratedValue

private int id;

private String code;

private String name;

private String elementcode;

}

// 桌子

@Entity

@Data

public class Entity1 extends BaseEntity implements java.io.Serializable{

public String getElementcode(){ return "桌子"; }

}

// 椅子

@Entity

@Data

public class Entity2 extends BaseEntity implements java.io.Serializable{

int venderId;

String venderCode;

String venderName;

public String getElementcode(){ return "椅子"; }

}

`

此时,如果我在一个方法内调用了dao来获取Entity1和Entity2,就会出最开始的那个WrongClass的异常,而原因就是hibernate认为两个entity是同一个entity,先通过dao获得entity1后,再获取entity2的时候, 由于Entity2的内部属性和Entity1不一样,且两个entity都有一个id=11的对象,所以就报了WrongClassException。

一开始,再网上找了一下,结果发现很多内容都是就事论事,让修改id值,避免id重复就好了。这个明显不是我所期望的结果。于是我换了一个思路,开始寻找hibernate关于继承及映射的相关说明,还就真的找到了根本原因。

简而言之,EJB3支持三种类型的继承映射:

1、每个类一张表(TABLE_PER_CLASS) 这个策略的效果,类似于你查询多个表的结果然后通过union组合在一起而得到的查询结果。所以在此策略下,@Id 将无法使用自增这个策略的效果,类似于你查询多个表的结果然后通过union组合在一起而得到的查询结果。所以在此策略下,@Id 将无法使用自生成及自增模式(AUTO生成器和IDENTITY生成器)。道理也很简单,通过union方式获取的结果对象,其各自的Entity都是独立的,所以@Id肯定是不能在当前的这个组合而来的Entity内共用。

回到之前的案例,我的目标是大家都继承自一个父类Entity,而不是要通过并集的方式获得一个新的Entity,所以这个策略不符合期望。

2、每个类层次一张表(SINGLE_TABLE) 这种呢,相当于所有的子Entity都是来自于一个Entity中,然后每个子Entity是通过父Entity中的一个标识字段内不同value进行区分。就相当于有个很大的要素表<Table家具>,这个表里有一个字段elementcode,当elementcode=桌子,将得到对应的Entity——桌子,而当elementcode=椅子,则得到对应的Entity——椅子。

再继续看案例,貌似很相似啊,但其实不然,案例中的子类Entity是来自不同的表,这个策略则是要来自同一个表,所以是不行的。当然,我也试了这个策略,结果就是提示我elementcode有问题,所以就是说还是不行。

3、每个类一张表(JOINED) 这个策略,其实类似于基于父类的不同的属性的继承。网上的资料使用这个代码进行解释:

`

@Entity

@Table(name = "T_ANIMAL")

@Inheritance(strategy=InheritanceType.JOINED)

public class Animal {

@Id

@Column(name = "ID")

@GeneratedValue(strategy = GenerationType.AUTO)

private Integer id;

@Column(name = "NAME")

private String name;

@Column(name = "COLOR")

private String color;

}

@Entity

@Table(name = "T_BIRD")

@PrimaryKeyJoinColumn(name = "BIRD_ID")

public class Bird extends Animal {

@Column(name = "SPEED")

private String speed;

}

@Entity

@Table(name = "T_DOG")

@PrimaryKeyJoinColumn(name = "DOG_ID")

public class Dog extends Animal {

@Column(name = "LEGS")

private Integer legs;

}

` 实际表结构如下: T_ANIMAL ID,COLOR,NAME T_BIRD SPEED,BIRD(既是外键,也是主键) T_DOG LEGS,DOG_ID(既是外键,也是主键)

在这个策略中,所有Entity也都是需要有对应的数据库实体的,并且所有的子类相互可以认为没有啥强的共同属性,因此这个策略也不适用于先前的案例需求。

好吧,上述三种都不符合需求,那么这个时候该怎么办?很多时候,我们经常会通过一个父类来实现共享一些公共属性,并且这个父类并不一定有对应的映射实体,即该实体不一定有对应的表。 这个时候@MappedSuperclass登场了。 还是先前的案例,现在改写如下

`

// 家具 

@MappedSuperclass

@Data

public abstract class BaseEntity{

@Id

@GeneratedValue

private int id;

private String code;

private String name;

private String elementcode;

}

// 桌子

@Entity

@Data

public class Entity1 extends BaseEntity implements java.io.Serializable{

public String getElementcode(){ return "桌子"; }

}

// 椅子

@Entity

@Data

public class Entity2 extends BaseEntity implements java.io.Serializable{

int venderId;

String venderCode;

String venderName;

public String getElementcode(){ return "椅子"; }

}

`

此时,父类家具中的属性映射将复制到桌子和椅子两个子类实体中,父类是没有对应的数据库实体表的,而两个子类则是有对应的实体表,但他们都同时具有父类中的所有属性。所以案例中的需求在此得到完美实现。 P.S:如果不使用@MappedSuperclass注解父类的话,父类中的属性将被忽略。

以上是 SpringJPA中关于Entity继承的问题(上) 的全部内容, 来源链接: utcz.com/z/510246.html

回到顶部