Spring Data JPA 之 @Entity 的常用注解

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

  1. 实体是直接进⾏数据库持久化操作的领域对象(即⼀个简单的 POJO,可以按照业务领域划分),必须通过 @Entity 注解进⾏标示。

  2. 实体必须有⼀个 public 或者 protected 的⽆参数构造⽅法。

  3. 持久化映射的注解可以标示在 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 上⾯。

  4. 只要是在 @Entity 的实体⾥⾯被注解标注的字段,都会被映射到数据库中,除了使⽤ @Transient 注解的字段之外。

  5. 实体⾥⾯必须要有⼀个主键,主键标示的字段可以是单个字段,也可以是复合主键字段。

以上我只挑选了最关键的⼏条进⾏了介绍,如果你有兴趣可以读⼀读 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 等。

  1. @Entity ⽤于定义对象将会成为被 JPA 管理的实体,必填,将字段映射到指定的数据库表中,使⽤起来很简单,直接⽤在实体类上⾯即可,通过源码表达的语法如下:

    @Target(TYPE) // 表示此注解只能⽤在 class 上⾯
    public @interface Entity {
        // 可选,默认是实体类的名字,整个应⽤⾥⾯全局唯⼀。
        String name() default ""; 
    }
    
  2. @Table ⽤于指定数据库的表名,表示此实体对应的数据库⾥⾯的表名,⾮必填,默认表名和 entity 名字⼀样。

    @Target(TYPE) // ⼀样只能⽤在类上⾯
    public @interface Table {
        // 表的名字,可选。如果不填写,系统认为和实体的名字⼀样为表名。
        String name() default "";
        // 此表所在 schema,可选
        String schema() default "";
        // 唯⼀性约束,在创建表的时候有⽤,表创建之后后⾯就不需要了。
        UniqueConstraint[] uniqueConstraints() default { };
        // 索引,在创建表的时候使⽤,表创建之后后⾯就不需要了。
        Index[] indexes() default {};
    }
    
  3. @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
    }
    
  4. @Id 定义属性为数据库的主键,⼀个实体⾥⾯必须有⼀个主键,但不⼀定是这个注解,可以和 @GeneratedValue 配合使⽤或成对出现。

  5. @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
    }
    
  6. @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。但是实际⼯作中,不建议⽤数字下标,因为枚举⾥⾯的属性值是会不断新增的,如果新增⼀个,位置变化了就惨了。

  7. @Basic 表示属性是到数据库表的字段的映射。如果实体的字段上没有任何注解,默认即为 @Basic。也就是说默认所有的字段肯定是和数据库进⾏映射的,并且默认为 Eager 类型。

    public @interface Basic {
        // 可选,EAGER(默认):⽴即加载;LAZY:延迟加载。(LAZY 主要应⽤在⼤字段上⾯)
        FetchType fetch() default EAGER;
        // 可选。这个字段是否可以为 null,默认是 true。
        boolean optional() default true; 
    }
    
  8. @Transient 表示该属性并⾮⼀个到数据库表的字段的映射,表示⾮持久化属性。JPA 映射数据库的时候忽略它,与 @Basic 有相反的作⽤。也就是每个字段上⾯ @Transient 和 @Basic 必须⼆选⼀,⽽什么都不指定的话,默认是 @Basic。

  9. @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;
    }
    
  10. @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.EmbeddedIdjavax.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 的区别是什么?有以下两个⽅⾯:

  1. 如上⾯测试⽤例,在使⽤的时候,Embedded ⽤的是对象,⽽ IdClass ⽤的是具体的某⼀个字段;
  2. ⼆者的 JPQL 也会不⼀样:
    • ⽤ @IdClass JPQL 的写法:SELECT u.name FROM UserInfo u
    • ⽤ @EmbeddedId 的 JPQL 的写法:select u.userInfoId.name FROM UserInfo u

联合主键还有需要注意的就是,它与唯⼀性索引约束的区别是写法不同,如上⾯所讲,唯⼀性索引的写法如下:

@Column(unique = true)
private String uniqueNumber;

到这⾥,联合主键我们讲完了,那么在遇到联合主键的时候,利⽤ @IdClass、@EmbeddedId,你就可以应对联合主键了。

此外,Java 是⾯向对象的,肯定会⽤到多态的使⽤场景,那么场景都有哪些?公共⽗类⼜该如何写?我们来学习⼀下。

6.4 如何实现实体之间的继承关系

在 Java ⾯向对象的语⾔环境中,@Entity 之间的关系多种多样,⽽根据 JPA 的规范,我们⼤致可以将其分为以下⼏种:

  1. 纯粹的继承,和表没关系,对象之间的字段共享。利⽤注解 @MappedSuperclass,协议规定⽗类不能是 @Entity。
  2. 单表多态问题,同⼀张 Table,表示了不同的对象,通过⼀个字段来进⾏区分。利⽤ @Inheritance(strategy = InheritanceType.SINGLE_TABLE) 注解完成,只有⽗类有 @Table。
  3. 多表多态,每⼀个⼦类⼀张表,⽗类的表拥有所有公⽤字段。通过 @Inheritance(strategy = InheritanceType.JOINED) 注解完成,⽗类和⼦类都是表,有公⽤的字段在⽗表⾥⾯。
  4. 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,这个将在后面详细介绍,到时候你可以体验⼀下它的不同之处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值