6 Spring Data JPA 之 @Entity 的常用注解
前⼏课时介绍了 Repository 的⽤法,其中经常会提到“实体类”(即前⾯的User 类),它是对我们数据库中表的 Metadata 映射,那么具体如何映射呢?这⼀课时我们来讲解。
我们先看⼀下 Java Persistence API ⾥⾯都有哪些重要规定;再通过讲解基本注解,重点介绍⼀下联合主键和实体之间的继承关系,然后你就会知道 JPA 的实体⾥⾯常⻅的注解有哪些。先来看⼀下 Entity 的相关规定。
6.1 JPA 协议中关于实体的相关规定
我们先看⼀下 JPA 协议⾥⾯关于实体做了哪些规定。(这⾥推荐⼀个查看 JPA 协议的官⽅地址: https://download.oracle.com/otn-pub/jcp/persistence-2_2-mrel-spec/JavaPersistence.pdf
)
-
实体是直接进⾏数据库持久化操作的领域对象(即⼀个简单的 POJO,可以按照业务领域划分),必须通过 @Entity 注解进⾏标示。
-
实体必须有⼀个 public 或者 protected 的⽆参数构造⽅法。
-
持久化映射的注解可以标示在 Entity 的字段 field 上,如下所示:
@Column(length = 20, nullable = false) private String userName;
除此之外,也可以将持久化注解运⽤在 Entity ⾥⾯的 get/set ⽅法上,通常我们是放在 get ⽅法中,如下所示:
@Column(length = 20, nullable = false) public String getUserName(){ return userName; }
概括起来,就是 Entity ⾥⾯的注解⽣效只有两种⽅式:将注解写在字段上或者将注解写在⽅法上(JPA ⾥⾯称 Property)。
但是需要注意的是,在同⼀个 Entity ⾥⾯只能有⼀种⽅式⽣效,也就是说,注解要么全部写在 field 上⾯,要么就全部写在 Property 上⾯。
-
只要是在 @Entity 的实体⾥⾯被注解标注的字段,都会被映射到数据库中,除了使⽤ @Transient 注解的字段之外。
-
实体⾥⾯必须要有⼀个主键,主键标示的字段可以是单个字段,也可以是复合主键字段。
以上我只挑选了最关键的⼏条进⾏了介绍,如果你有兴趣可以读⼀读 Java Persistence API 协议,这样我们在做 JPA 开发的时候就会顺⼿很多,可以理解很多 Hibernate ⾥⾯实现⽅法。
这也为你提供了⼀条解决疑难杂症的思路,也就是当我们遇到解决不了的问题时,就去看协议、阅读官⽅⽂档,深⼊挖掘⼀下,可能就会找到答案。那么接下来我们看看实例⾥⾯常⽤的注解有哪些。
6.2 实体里面常见的注解
我们先通过源码看看 JPA ⾥⾯⽀持的注解有哪些。
⾸先,我们利⽤ IEDA ⼯具,打开 @Entity 所在的包,就可以看到 JPA ⾥⾯⽀持的注解有哪些。如下所示:
我们可以看到,在 jakarta.persistence-api
的包路径下⾯⼤概有⼀百多个注解,你在没事的时候可以到这⾥⾯⼀个⼀个地看,也可以到 JPA 的协议⾥⾯对照查看⽂档。我在这⾥只提及⼀些最常⻅的,包括 @Entity、@Table、@Access、@Id、@GeneratedValue、@Enumerated、@Basic、@Column、@Transient、@Lob、@Temporal 等。
-
@Entity ⽤于定义对象将会成为被 JPA 管理的实体,必填,将字段映射到指定的数据库表中,使⽤起来很简单,直接⽤在实体类上⾯即可,通过源码表达的语法如下:
@Target(TYPE) // 表示此注解只能⽤在 class 上⾯ public @interface Entity { // 可选,默认是实体类的名字,整个应⽤⾥⾯全局唯⼀。 String name() default ""; }
-
@Table ⽤于指定数据库的表名,表示此实体对应的数据库⾥⾯的表名,⾮必填,默认表名和 entity 名字⼀样。
@Target(TYPE) // ⼀样只能⽤在类上⾯ public @interface Table { // 表的名字,可选。如果不填写,系统认为和实体的名字⼀样为表名。 String name() default ""; // 此表所在 schema,可选 String schema() default ""; // 唯⼀性约束,在创建表的时候有⽤,表创建之后后⾯就不需要了。 UniqueConstraint[] uniqueConstraints() default { }; // 索引,在创建表的时候使⽤,表创建之后后⾯就不需要了。 Index[] indexes() default {}; }
-
@Access ⽤于指定 entity ⾥⾯的注解是写在字段上⾯,还是 get/set ⽅法上⾯⽣效,⾮必填。在默认不填写的情况下,当实体⾥⾯的第⼀个注解出现在字段上或者 get/set ⽅法上⾯,就以第⼀次出现的⽅式为准;也就是说,⼀个实体⾥⾯的注解既有⽤在 field 上⾯,⼜有⽤在 properties 上⾯的时候,看下⾯的代码你就会明⽩。
@Id private Long id; @Column(length = 20, nullable = false) public String getUserName(){ return userName; }
那么由于 @Id 是实体⾥⾯第⼀个出现的注解,并且作⽤在字段上⾯,所以所有写在 get/set ⽅法上⾯的注解就会失效。⽽ @Access 可以⼲预默认值,指定是在 fileds 上⾯⽣效还是在properties 上⾯⽣效。我们通过源码看下语法:
// 表示此注解可以运⽤在 class 上(那么这个时候就可以指定此实体的默认注解⽣效策略了),也可以⽤在⽅法上或者字段上(表示可以独⽴设置某⼀个字段或者⽅法的⽣效策略); @Target( { TYPE, METHOD, FIELD }) @Retention(RUNTIME) public @interface Access { // 指定是字段上⾯⽣效还是⽅法上⾯⽣效 AccessType value(); } public enum AccessType { FIELD, PROPERTY }
-
@Id 定义属性为数据库的主键,⼀个实体⾥⾯必须有⼀个主键,但不⼀定是这个注解,可以和 @GeneratedValue 配合使⽤或成对出现。
-
@GeneratedValue 主键⽣成策略,如下所示:
public @interface GeneratedValue { // Id 的⽣成策略 GenerationType strategy() default AUTO; // 通过 Sequences ⽣成 Id,常⻅的是 Orcale 数据库 ID ⽣成规则,这个时候需要配合 @SequenceGenerator 使⽤ String generator() default ""; }
其中,GenerationType ⼀共有以下四个值:
public enum GenerationType { // 通过表产⽣主键,框架借由表模拟序列产⽣主键,使⽤该策略可以使应⽤更易于数据库移植。 TABLE, // 通过序列产⽣主键,通过 @SequenceGenerator 注解指定序列名, MySql 不⽀持这种⽅式; SEQUENCE, // 采⽤数据库ID⾃增⻓, ⼀般⽤于mysql数据库 IDENTITY, // JPA ⾃动选择合适的策略,是默认选项; AUTO }
-
@Enumerated 这个注解很好⽤,因为它对 enum 提供了下标和 name 两种⽅式,⽤法直接映射在 enum 枚举类型的字段上。请看下⾯源码。
@Target({METHOD, FIELD}) // 作⽤在⽅法和字段上 public @interface Enumerated { // 枚举映射的类型,默认是 ORDINAL(即枚举字段的下标)。 EnumType value() default ORDINAL; } public enum EnumType { // 映射枚举字段的下标 ORDINAL, // 映射枚举的 Name STRING }
再来看⼀个 User ⾥⾯关于性别枚举的例⼦,你就会知道 @Enumerated 在这⾥没什么作⽤了,如下所示:
// 有⼀个枚举类,⽤户的性别 public enum Gender { MAIL("男性"), FMAIL("⼥性"); private String value; private Gender(String value) { this.value = value; } } //实体类@Enumerated的写法如下 @Entity @Table(name = "tb_user") public class User implements Serializable { @Enumerated(EnumType.STRING) @Column(name = "user_gender") private Gender gender; ....................... }
这时候插⼊两条数据,数据库⾥⾯的值会变成 MAIL/FMAIL,⽽不是 男性/⼥性。
经验分享: 如果我们⽤ @Enumerated(EnumType.ORDINAL),这时候数据库⾥⾯的值是 0、1。但是实际⼯作中,不建议⽤数字下标,因为枚举⾥⾯的属性值是会不断新增的,如果新增⼀个,位置变化了就惨了。
-
@Basic 表示属性是到数据库表的字段的映射。如果实体的字段上没有任何注解,默认即为 @Basic。也就是说默认所有的字段肯定是和数据库进⾏映射的,并且默认为 Eager 类型。
public @interface Basic { // 可选,EAGER(默认):⽴即加载;LAZY:延迟加载。(LAZY 主要应⽤在⼤字段上⾯) FetchType fetch() default EAGER; // 可选。这个字段是否可以为 null,默认是 true。 boolean optional() default true; }
-
@Transient 表示该属性并⾮⼀个到数据库表的字段的映射,表示⾮持久化属性。JPA 映射数据库的时候忽略它,与 @Basic 有相反的作⽤。也就是每个字段上⾯ @Transient 和 @Basic 必须⼆选⼀,⽽什么都不指定的话,默认是 @Basic。
-
@Column 定义该属性对应数据库中的列名。
public @interface Column { // 数据库中的表的列名;可选,如果不填写认为字段名和实体属性名⼀样。 String name() default ""; // 是否唯⼀。默认 flase,可选。 boolean unique() default false; // 数据字段是否允许空。可选,默认 true。 boolean nullable() default true; // 执⾏ insert 操作的时候是否包含此字段,默认,true,可选。 boolean insertable() default true; // 执⾏ update 的时候是否包含此字段,默认,true,可选。 boolean updatable() default true; // 表示该字段在数据库中的实际类型。 String columnDefinition() default ""; // 数据库字段的⻓度,可选,默认 255 int length() default 255; }
-
@Temporal ⽤来设置 Date 类型的属性映射到对应精度的字段,存在以下三种情况:
- @Temporal(TemporalType.DATE) :映射为⽇期 // date (只有⽇期)
- @Temporal(TemporalType.TIME) :映射为⽇期 // time (只有时间)
- @Temporal(TemporalType.TIMESTAMP) :映射为⽇期 // date time (⽇期+时间)
我们看⼀个完整的例⼦,感受⼀下上⾯提到的注解的完整⽤法,如下:
@Entity
@Table(name = "user_topic")
@Access(AccessType.FIELD)
@Data
public class UserTopic {
@Id
@Column(name = "id", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "title", nullable = true, length = 200)
private String title;
@Basic
@Column(name = "create_user_id", nullable = true)
private Integer createUserId;
@Basic(fetch = FetchType.LAZY)
@Column(name = "content", nullable = true, length = -1)
@Lob
private String content;
@Basic(fetch = FetchType.LAZY)
@Column(name = "image", nullable = true)
@Lob
private byte[] image;
@Basic
@Column(name = "create_time", nullable = true)
@Temporal(TemporalType.TIMESTAMP)
private Date createTime;
@Basic
@Column(name = "create_date", nullable = true)
@Temporal(TemporalType.DATE)
private Date createDate;
@Enumerated(EnumType.STRING)
@Column(name = "topic_type")
private Type type;
@Transient
private String transientSimple;
//⾮数据库映射字段,业务类型的字段
public String getTransientSimple() {
return title + "auto:jack" + type;
}
//有⼀个枚举类,主题的类型
public enum Type {
EN("英⽂"), CN("中⽂");
private final String des;
Type(String des) {
this.des = des;
}
}
}
其实这⾥⾯的很多注解都可以省略,直接使⽤默认的就可以。如 @Basic、@Column 名字有⼀定的映射策略,所以可以省略。
此外,@Access 也可以省略,我们只要在这些类⾥⾯保持⼀致就可以了。
6.3 联合主键
在实际的⼯作中,我们会经常遇到联合主键的情况。所以在这⾥我们详细讲解⼀下,可以通过 javax.persistence.EmbeddedId
和 javax.persistence.IdClass
两个注解实现联合主键的效果。
6.3.1 如何通过 @IdClass 实现联合主键
第⼀步:新建⼀个 UserInfoID 类⾥⾯是联合主键。
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserInfoID implements Serializable {
private String name;
private String telephone;
}
第⼆步:再新建⼀个 UserInfo 的实体,采⽤ @IdClass 引⽤联合主键类。
@Entity
@Data
@Builder
@IdClass(UserInfoID.class)
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo {
private Integer ages;
@Id
private String name;
@Id
private String telephone;
}
第三步:新增⼀个 UserInfoRepository 类来做 CRUD 操作。
public interface UserInfoRepository extendsJpaRepository<UserInfo,UserInfoID> {}
第四步:写⼀个测试⽤例,测试⼀下。
@DataJpaTest
public class UserInfoRepositoryTest {
@Autowired
private UserInfoRepository userInfoRepository;
@Test
public void testIdClass() {
userInfoRepository.save(UserInfo.builder().ages(1).name("jack").telephone("123456789").build());
Optional<UserInfo> userInfo =userInfoRepository.findById(UserInfoID.builder().name("jack").telephone("123456789").build());
System.out.println(userInfo.get());
}
}
日志打印如下:
Hibernate: create table user_info (name varchar(255) not null, telephone varchar(255) not null, ages integer, primary key (name, telephone))
Hibernate: select userinfo0_.name as name1_3_0_, userinfo0_.telephone as telephon2_3_0_, userinfo0_.ages as ages3_3_0_ from user_info userinfo0_ where userinfo0_.name=? and userinfo0_.telephone=?
UserInfo(ages=1, name=jack, telephone=123456789)
通过上⾯的例⼦我们可以发现,我们的表的主键是 primary key (name, telephone),⽽ Entity ⾥⾯不再是⼀个 @Id 字段了。那么我来介绍另外⼀个注解 @Embeddable,也能做到这⼀点。
6.3.2 @Embeddable 与 @EmbeddedId 注解使⽤
第⼀步:在我们上⾯例⼦中的 UserInfoID ⾥⾯添加 @Embeddable 注解。
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Embeddable
public class UserInfoID implements Serializable {
private String name;
private String telephone;
}
第⼆步:改⼀下我们刚才的 User 对象,删除 @IdClass,添加 @EmbeddedId 注解,如下:
@Entity
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo {
private Integer ages;
@EmbeddedId
private UserInfoID userInfoID;
@Column(unique = true)
private String uniqueNumber;
}
第三步:UserInfoRepository 不变,我们直接修改⼀下测试⽤例。
@Test
public void testIdClass() {
userInfoRepository.save(UserInfo.builder().ages(1).userInfoID(UserInfoID.builder().name("jack").telephone("123456789").build()).build());
Optional<UserInfo> userInfo =userInfoRepository.findById(UserInfoID.builder().name("jack").telephone("123456789").build());
System.out.println(userInfo.get());
}
运⾏完之后,你可以得到相同的结果。那么 @IdClass 和 @EmbeddedId 的区别是什么?有以下两个⽅⾯:
- 如上⾯测试⽤例,在使⽤的时候,Embedded ⽤的是对象,⽽ IdClass ⽤的是具体的某⼀个字段;
- ⼆者的 JPQL 也会不⼀样:
- ⽤ @IdClass JPQL 的写法:
SELECT u.name FROM UserInfo u
- ⽤ @EmbeddedId 的 JPQL 的写法:
select u.userInfoId.name FROM UserInfo u
- ⽤ @IdClass JPQL 的写法:
联合主键还有需要注意的就是,它与唯⼀性索引约束的区别是写法不同,如上⾯所讲,唯⼀性索引的写法如下:
@Column(unique = true)
private String uniqueNumber;
到这⾥,联合主键我们讲完了,那么在遇到联合主键的时候,利⽤ @IdClass、@EmbeddedId,你就可以应对联合主键了。
此外,Java 是⾯向对象的,肯定会⽤到多态的使⽤场景,那么场景都有哪些?公共⽗类⼜该如何写?我们来学习⼀下。
6.4 如何实现实体之间的继承关系
在 Java ⾯向对象的语⾔环境中,@Entity 之间的关系多种多样,⽽根据 JPA 的规范,我们⼤致可以将其分为以下⼏种:
- 纯粹的继承,和表没关系,对象之间的字段共享。利⽤注解 @MappedSuperclass,协议规定⽗类不能是 @Entity。
- 单表多态问题,同⼀张 Table,表示了不同的对象,通过⼀个字段来进⾏区分。利⽤ @Inheritance(strategy = InheritanceType.SINGLE_TABLE) 注解完成,只有⽗类有 @Table。
- 多表多态,每⼀个⼦类⼀张表,⽗类的表拥有所有公⽤字段。通过 @Inheritance(strategy = InheritanceType.JOINED) 注解完成,⽗类和⼦类都是表,有公⽤的字段在⽗表⾥⾯。
- Object 的继承,数据库⾥⾯每⼀张表是分开的,相互独⽴不受影响。通过 @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) 注解完成,⽗类(可以是⼀张表,也可以不是)和⼦类都是表,相互之间没有关系。
其中,第⼀种 @MappedSuperclass,暂时不多介绍,我们先看⼀下第⼆种 SINGLE_TABLE 。
6.4.1 @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
⽗类实体对象与各个⼦实体对象共⽤⼀张表,通过⼀个字段的不同值代表不同的对象,我们看⼀个例⼦。
我们抽象⼀个 Book 对象,如下所示:
@Entity(name = "book")
@Data
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "color", discriminatorType = DiscriminatorType.STRING)
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
}
再新建⼀个 BlueBook 对象,作为 Book 的⼦对象。
@Entity
@Data
@EqualsAndHashCode(callSuper = false)
@DiscriminatorValue("blue")
public class BlueBook extends Book {
private String blueMark;
}
再新建⼀个 RedBook 对象,作为 Book 的另⼀⼦对象。
@Entity
@DiscriminatorValue("red")
@Data
@EqualsAndHashCode(callSuper=false)
public class RedBook extends Book {
private String redMark;
}
这时,我们⼀共新建了三个 Entity 对象,其实都是指 book 这⼀张表,通过 book 表⾥⾯的color 字段来区分红书还是绿书。我们继续做⼀下测试看看结果。
我们再新建⼀个 RedBookRepository 类:
public interface RedBookRepository extends JpaRepository<RedBook, Long> {
}
然后再新建⼀个测试⽤例。
@DataJpaTest
public class RedBookRepositoryTest {
@Autowired
private RedBookRepository redBookRepository;
@Test
public void testRedBook() {
RedBook redBook = new RedBook();
redBook.setTitle("redbook");
redBook.setRedMark("redmark");
redBook.setId(1L);
redBookRepository.saveAndFlush(redBook);
RedBook r = redBookRepository.findById(1L).get();
System.out.println(r.getId() + ":" + r.getTitle() + ":" + r.getRedMark());
}
}
最后看⼀下执⾏结果。
Hibernate: create table book (color varchar(31) not null, id bigint not null, title varchar(255), blue_mark varchar(255), red_mark varchar(255), primary key (id))
Hibernate: insert into book (title, red_mark, color, id) values (?, ?, 'red', ?)
你会发现,我们只创建了⼀张表,insert 了⼀条数据,但是我们发现 color 字段默认给的是 red。
那么再看⼀下打印结果。
1:redbook:redmark
结果完全和预期的⼀样,这说明了 RedBook、BlueBook、Book,都是⼀张表,通过字段color 的值不⼀样,来区分不同的实体。
那么接下来我们看⼀下 InheritanceType.JOINED,它的每个实体都是独⽴的表。
6.4.2 @Inheritance(strategy = InheritanceType.JOINED)
在这种映射策略⾥⾯,继承结构中的每⼀个实体(entity)类都会映射到数据库⾥⼀个单独的表中。也就是说,每个实体(entity)都会被映射到数据库中,⼀个实体(entity)类对应数据库中的⼀个表。
其中根实体(root entity)对应的表中定义了主键(primary key),所有的⼦类对应的数据库表都要共同使⽤ Book ⾥⾯的 @ID 这个主键。
⾸先,我们我们 Book ⽗类、改变 Inheritance 策略、删除 DiscriminatorColumn。,改动如下:
@Entity(name="book")
@Data
@Inheritance(strategy = InheritanceType.JOINED)
public class Book {
@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
private String title;
}
然后,BlueBook 和 RedBook 也删除 DiscriminatorColumn,新增 @PrimaryKeyJoinColumn(name = "book_id", referencedColumnName = "id")
,和 book ⽗类共⽤⼀个主键值。
@Entity
@Data
@EqualsAndHashCode(callSuper=false)
@PrimaryKeyJoinColumn(name = "book_id", referencedColumnName = "id")
public class BlueBook extends Book {
private String blueMark;
}
@Entity
@PrimaryKeyJoinColumn(name = "book_id", referencedColumnName = "id")
@Data
@EqualsAndHashCode(callSuper=false)
public class RedBook extends Book {
private String redMark;
}
RedBookRepository 和测试⽤例不变,我们执⾏看⼀下结果。
Hibernate: create table blue_book (blue_mark varchar(255), book_id bigint not null, primary key (book_id))
Hibernate: create table book (id bigint not null, title varchar(255), primary key (id))
Hibernate: create table red_book (red_mark varchar(255), book_id bigint not null, primary key (book_id))
Hibernate: alter table blue_book add constraint FK9uuwgq7a924vtnys1rgiyrlk7 foreign key (book_id) references book
Hibernate: alter table red_book add constraint FKk8rvl61bjy9lgsr9nhxn5soq5 foreign key (book_id) references book
上述代码可以看到,我们⼀共创建了三张表,并且新增了两个外键约束;⽽我们 save 的时候也⽣成了两个 insert 语句,如下:
Hibernate: insert into book (title, id) values (?, ?)
Hibernate: insert into red_book (red_mark, book_id) values (?, ?)
⽽打印结果依然不变。
1:redbook:redmark
这就是 InheritanceType.JOINED 的例⼦,这个⽅法和上⾯的 InheritanceType.SINGLE_TABLE 区别在于表的数量和关系不⼀样,这是表设计的另⼀种⽅式。
6.4.3 @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
我们在使⽤ @MappedSuperClass 主键的时候,如果不指定 @Inhertance,默认就是此种 TABLE_PER_CLASS 模式。当然了,我们也显示指定,要求继承基类的都是⼀张表,⽽⽗类不是表,是 java 对象的抽象类。我们看⼀个例⼦。
⾸先,还是改⼀下上⾯的三个实体。
@Entity(name="book")
@Data
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Book {
@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
private String title;
}
其次,Book 表采⽤ TABLE_PER_CLASS 策略,其⼦实体类都代表各⾃的表,实体代码如下:
@Entity
@Data
@EqualsAndHashCode(callSuper=false)
public class RedBook extends Book {
private String redMark;
}
@Entity
@Data
@EqualsAndHashCode(callSuper=false)
public class BlueBook extends Book{
private String blueMark;
}
这时,从 RedBook 和 BlueBook ⾥⾯去掉 PrimaryKeyJoinColumn,⽽ RedBookRepository 和测试⽤例不变,我们执⾏看⼀下结果。
Hibernate: create table blue_book (id bigint not null, title varchar(255), blue_mark varchar(255), primary key (id))
Hibernate: create table book (id bigint not null, title varchar(255), primary key (id))
Hibernate: create table red_book (id bigint not null, title varchar(255), red_mark varchar(255), primary key (id))
这⾥可以看到,我们还是创建了三张表,但三张表什么关系也没有。⽽ insert 语句也只有⼀条,如下:
Hibernate: insert into red_book (title, red_mark, id) values (?, ?, ?)
打印结果还是不变。
1:redbook:redmark
这个⽅法与上⾯两个相⽐较,语义更加清晰,是⽐较常⽤的⼀种做法。
以上就是实体之间继承关系的实现⽅法,可以在涉及 java 多态的时候加以应⽤,不过要注意区分三种⽅式所表达的表的意思,再加以运⽤。
6.5 本章小结
从我的个⼈经验来看,@Inheritance 的这种使⽤⽅式会逐渐被淘汰,因为这样的表的设计很复杂,本应该在业务层⾯做的事情(多态),⽽在 datasoure 的表级别做了。所以在JPA 中使⽤这个的时候你就会想:“这么复杂的东⻄,我直接⽤ Mybatis 算了。”我想告诉你,其实它们是⼀样的,只是我们使⽤的思路不对。
那么为什么⾏业内都不建议使⽤了,还要介绍这么详细呢?因为,如果你遇到的是⽼⼀点的项⽬,如果不是⽤ Java 语⾔写的,不⼀定有⾯向对象的思想。这个时候如果让你迁移成Java 怎么办?如果你可以想到这种⽤法,就不⾄于束⼿⽆措。
此外,在互联⽹项⽬中,⼀旦有关表的业务对象过多了之后,就可以拆表拆库了,这个时候我们要想到我们的 @Table 注解指定表名和 schema。
关于上⾯提到的⽅法中,最常⽤的是第⼀种 @MappedSuperclass,这个将在后面详细介绍,到时候你可以体验⼀下它的不同之处。