SpringJPA中关于Entity继承的问题(上)
参考资料: 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