Id 策略

@GeneratedValue:主键的产生策略,通过strategy属性指定 主键产生策略通过GenerationType来指定。GenerationType是一个枚举,它定义了主键产生策略的类型。

1、AUTO 自动选择一个最适合底层数据库的主键生成策略。如MySQL会自动对应auto increment。这个是默认选项,即如果只写@GeneratedValue,等价于@GeneratedValue(strategy=GenerationType.AUTO)。

2、IDENTITY 表自增长字段,Oracle不支持这种方式。

3、SEQUENCE 通过序列产生主键,MySQL不支持这种方式。

4、TABLE 通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植。不同的JPA实现商生成的表名是不同的,如 OpenJPA生成openjpa_sequence_table表,Hibernate生成一个hibernate_sequences表,而TopLink则生成sequence表。这些表都具有一个序列名和对应值两个字段,如SEQ_NAME和SEQ_COUNT。 如果使用Hibernate对JPA的实现,可以使用Hibernate对主键生成策略的扩展,通过Hibernate的@GenericGenerator实现。

@GenericGenerator(name = “system-uuid”, strategy = “uuid”) 声明一个策略通用生成器,name为”system-uuid”,策略strategy为”uuid”。

@GeneratedValue(generator = “system-uuid”) 用generator属性指定要使用的策略生成器。

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.GenericGenerator;
 
@Entity
@Table(name="csq_test")
public class Test {
    private Long id;
    @Id
    @GenericGenerator(name="idGenerator", strategy="uuid") //这个是hibernate的注解/生成32位UUID
    @GeneratedValue(generator="idGenerator")
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
}

 

映射

举例说明,在公司权限中会存在,公司,部门,员工,其中一个部门中有多个员工,公司有多个部门,类似于这种。 这些映射中都可以使用下面的参数

fetch=FetchType.LAZY为默认的数据延迟加载,fetch=FetchType.EAGER为急加载

cascade={CascadeType.PERSIST,CascadeType.MERGE, 一般采用CascadeType.MERGE:级联合并(级联更新)即可。默认值是均不进行关联。

增加默认的查询条件可以在Set上增加@Where(clause="status = 1")

OneToOne

在一对一的关系中,只需在主控方内注明@OneToOne,而被控方只是作为外键,不需任何特殊标记

@OneToOne  
@JoinColumn(name="ACCOUNT_ID")  
private AccountEntity account;

 对应的员工表上,存储账户表的外键就可以了 

 在另一端如果想使用的话可以

@OneToOne(mappedBy="account")  
private EmployeeEntity employee;

 这样维护的工作就都由employee来完成了。即保存employee时,会同时保存account

OneToMany && ManyToOne

公司(组织)表相对于部门表是被控方,只需在被控方写mappedBy,其值为主控方中引用的外键对象的名称

@Entity
@Table(name = "costume_organization")
public class Organization extends AbstractEntity {
    private static final long serialVersionUID = 1L;

    @Column(nullable = false, length = 50)
    private String name; // 组织名称

    @OneToMany(mappedBy = "organization")
    private Set<Department> departmentSet; // 部门集合
}

 部门表相对于公司(组织)表是主控方,在主控方只需写@ManyToOne即可,其对象名为被控表中mappedBy中的值。如果需要将公司表对象或者部门表对象转为JSON数据的时候,为了防止出现无限循环包含对方,需要在部门表(多的一方)的引用公司表(少的一方)对象的set方法上写上注解@JsonBackReference(或者,在对应的一方的bean上,写上@JsonIgnore)

@Entity
@Table(name = "costume_department")
public class Department extends AbstractEntity {
    private static final long serialVersionUID = 1L;

    @Column(nullable = false, length = 50)
    private String name; // 部门名称

    
    private Organization organization; // 组织外键
@ManyToOne(optional = false)//这个放get上,放字段上有时候不好用
    getOrganization(){
}
    @ManyToMany
    private Set<Member> memberSet; // 用户表外键

    public Organization getOrganization() {
        return organization;
    }

    @JsonBackReference
    public void setOrganization(Organization organization) {
        this.organization = organization;
    }
}

 

ManyToMany

@ManyToMany 注释:表示此类是多对多关系的一边,mappedBy 属性定义了此类为双向关系的维护端,注意:mappedBy 属性的值为此关系的另一端的属性名。 被控方:

@ManyToMany(fetch = FetchType.LAZY, mappedBy = "students")
public Set<Teacher> getTeachers() {
return teachers;
}

 那么这里的“students”就是Teachers的一个属性,通常应该是这样的:<Student> 主控方:

@ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
@JoinTable(name = "Teacher_Student",
joinColumns = {@JoinColumn(name = "Teacher_ID", referencedColumnName = "teacherid")},
inverseJoinColumns = {@JoinColumn(name = "Student_ID", referencedColumnName ="studentid")})
public Set<Student> getStudents() {
return students;
}

 @ManyToMany 注释表示Teacher 是多对多关系的一端。@JoinTable 描述了多对多关系的数据表关系。name 属性指定中间表名称,joinColumns 定义中间表与Teacher 表的外键关系。上面的代码中,中间表Teacher_Student的Teacher_ID 列是Teacher 表的主键列对应的外键列,inverseJoinColumns 属性定义了中间表与另外一端(Student)的外键关系。

可以通过上面的定义看到有三个表学生表--老师表--老师学生中间表 正确的写法应该是两边都用主控方的写法:只是joinColumns和inverseJoinColumns属性的地方互换就可以了。

@ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
@JoinTable(name = "Teacher_Student",
joinColumns = {@JoinColumn(name = "Student_ID", referencedColumnName = "studentid")},
inverseJoinColumns = {@JoinColumn(name = "Teacher_ID", referencedColumnName ="teacherid")})
public Set<Teacher> getTeachers() {
  return teachers;
}

@ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
@JoinTable(name = "Teacher_Student",
joinColumns = {@JoinColumn(name = "Teacher_ID", referencedColumnName = "teacherid")},
inverseJoinColumns = {@JoinColumn(name = "Student_ID", referencedColumnName ="studentid")})
public Set<Student> getStudents() {
  return students;
}

 

注意

这个可以不使用mappyBy,但是一定是一方是fetch = FetchType.LAZY,如果两方都是 fetch = FetchType.EAGER 系统不能启动,会报错,特别说明一下。

Jpa对象的继承映射策略

在企业级开发中,经常会碰到的一种对象是这样的,实体公司、部门、人员,都会有ID、code、name等公共属性,本着面向对象的处理方法,在java世界里,一般都会抽象出一层来,比如称为 BaseBean,或者 BaseEntity,这样第一可以通过base来统一处理通用的CURD,第二,通过 extends BaseEntity 可以少写很多代码。 这个问题从头说,一般来说实体和表的映射关系分为3种

  • Single-table :这是继承映射中的缺省策略,在不特别指明的情况下,系统默认就是采用这种映射策略进行映射的。这个策略的映射原则就是父类包括子类中新添加的属性全部映射到一张数据库表中,数据库表中有一个自动生成的字段用来存储区分不同的子类的信息
  • Joined-subclass:这种映射策略中,继承关系中的每一个实体类,无论是具体类 (concrete entity) 或者抽象类 (abstract entity),数据库中都有一个单独的表与他对应。子实体对应的表中不含有从根实体继承而来的属性,它们之间通过共享主键的方式进行关联。
  • Table-per-concrete-class :这个策略就是将继承关系中的每一个实体映射到数据库中的一个单独的表中,与“Joined”策略不同的是,子实体对应的表中含有从根实体继承而来的属性。

下文举例种会应用的例子的UML图

 

Java中的主键生成策略 springboot主键生成策略_子类

image.png

Single Table

单表(Single-Table)映射是继承映射中的缺省映射策略,在不加说明的情况下,也就是不在根实体 (root entity) 中指定映射策略的时候默认就是使用的这种映射策略。在本例中根实体 (root entity) 指的是实体 Item 类。 Item 实体类没有用 @Inheritance 这个注解 (annotation) 进行注释,说明在这个继承关系中使用的是单表映射策略。同时 Item 实体类是 Phone 实体类和 Magazine 实体类的根实体 (root entity),它们继承了 Item 的属性并且拥有自己的独有的属性。

@Entity 
public class Item implements Serializable { 
   private static final long serialVersionUID = 1L; 
   @Id 
   @GeneratedValue(strategy = GenerationType.AUTO) 
   private Long id; 
   private String title; 
   private Float price; 
   private String decription; 
  // Getters and Setters 
}
@Entity 
public class Phone extends Item { 
   private String factory; 
   private Float DurationTime; 
    // Getters and Setters 
}
@Entity 
public class Magazine extends Item { 
   private String isbn; 
   private String publisher; 
    // Getters and Setters 
}

 对应的ER图

Java中的主键生成策略 springboot主键生成策略_Java中的主键生成策略_02

image.png

 对应的数据

Java中的主键生成策略 springboot主键生成策略_主键_03

image.png

单表映射中除了将所有子类中的属性和父类集中在同一个表中之外,还多添加了一个 DTYPE 的列,这个列主要是为了区别子类的,他的缺省属性是 String 类型,缺省值就是子实体 (entity) 类的类名。数据库中记录的一个片断,DTYPE 列的值默认就是类的名字,它是用来区分本行的记录属于继承关系的中的哪个类的。DTYPE 的值是 Item 的行,就是由根实体持久化而来的,以此类推。 用来区分子类的字段也是可以自定义的,如下:

@Entity 
@DiscriminatorColumn(name="DISC",discriminatorType=DiscriminatorType.CHAR) 
@DiscriminatorValue("I") 
public class Item implements Serializable { 
 
   private static final long serialVersionUID = 1L; 
   @Id 
   @GeneratedValue(strategy = GenerationType.AUTO) 
   private Long id; 
   private String title; 
   private Float price; 
   private String decription; 
 
    // Getters and Setters 
 
}
@Entity 
@DiscriminatorValue("M") 
public class Magazine extends Item { 
 
   private String isbn; 
   private String publisher; 
 
   // Getters and Setters 
 
}
@Entity 
@DiscriminatorValue("P") 
public class Phone extends Item { 
 
   private String factory; 
   private Float DurationTime; 
 
    // Getters and Setters 
 
}

 这样定义后,数据库实际如下

Java中的主键生成策略 springboot主键生成策略_主键_04

image.png

这种策略对实体之间的多种关联关系能提供很好的支持,同时在查询方面也有很好的效率。但是这种映射策略在数据库表中会有很多的空字段的存在。这样势必会造成数据库资源的大量浪费,同时这个映射策略也要求子类中的所有属性也必须是可空 (null able) 的。

Joined-subclass

这种映射策略,继承结构中的每一个实体 (entity) 类都会映射到数据库中一个单独的表中,也就是说每个实体 (entity) 都会被映射到数据库中,一个实体 (entity) 类对应数据库中的一个表。其中根实体 (root entity) 对应的表中定义了主键 (primary key),所有的子类对应的数据库表都要共同使用这个主键,同时这个表中和单表映射策略一样还定义了区分列 (DTYPE)。

@Entity 
@Inheritance(strategy=InheritanceType.JOINED) 
@Table(name="Item_Joined") 
public class Item implements Serializable { 
   private static final long serialVersionUID = 1L; 
   @Id 
   @GeneratedValue(strategy = GenerationType.AUTO) 
   private Long id; 
   private String title; 
   private Float price; 
   private String decription; 
    // Getters and Setters 
}
@Entity 
public class Magazine extends Item { 
   private String isbn; 
   private String publisher; 
    // Getters and Setters 
}
@Entity 
public class Phone extends Item { 
   private String factory; 
   private Float DurationTime; 
    // Getters and Setters 
}

 对应ER图

Java中的主键生成策略 springboot主键生成策略_子类_05

image.png 

这种映射策略和缺省的单表映射策略唯一的区别就是在根实体中使用 @Inheritance 注解 (annotation) 并指定使用 Joined 映射策略。在继承比较多时,查寻起来效率就会差一些,因为在查询的过程中需要多表的连接,连接的表数越多,查询效率越低下。DType关键字同单表策略

Table-per-concrete-class

这种映射策略和连接映射策略很类似,不同的是子类对应的表中要继承根实体 (root entity) 中的属性,根实体 (root entity) 对应的表中也不需要区分子类的列,表之间没有共享的表,也没有共享的列

@Entity 
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) 
@Table(name="Item_TPC") 
public class Item implements Serializable { 
   private static final long serialVersionUID = 1L; 
   @Id 
   @GeneratedValue(strategy = GenerationType.AUTO) 
   private Long id; 
   private String title; 
   private Float price; 
   private String decription; 
    // Getters and Setters 
}
@Entity 
public class Magazine extends Item { 
   private String isbn; 
   private String publisher; 
    // Getters and Setters 
}
@Entity 
public class Phone extends Item { 
   private String factory; 
   private Float DurationTime; 
    // Getters and Setters 
 
}

 对应的ER图

Java中的主键生成策略 springboot主键生成策略_子类_06

image.png

按上面配置的话,每个子表对应的根属性都是一样的,如果要修改数据库中的字段名称可以进行重写,如下例子

@Entity 
@AttributeOverrides({ 
   @AttributeOverride(name="id", 
                     column=@Column(name="Phone_id")), 
   @AttributeOverride(name="title", 
                     column=@Column(name="Phone_title")), 
   @AttributeOverride(name="description", 
                     column=@Column(name="Phone_desc")), 
}) 
public class Phone extends Item { 
 
   private String factory; 
   private Float DurationTime; 
 
    // Getters and Setters 
 
}

 

抽象实体类 (Abstract entity)

上面的例子中所使用的 Item 是个具体类 (concrete class),并且使用 @Entity 注解 (annotation) 进行了注释。那么当 Item 类不是具体类 (concrete class),而是一个抽象类 (abstract class) 的时候,也就是当 Item 类的声明中使用了 abstract 关键字的时候,是如何影射的呢?事实上根实体是否是抽象实体类,在数据库中映射成的表没有任何区别。也就是说上面的例子中如果根实体类 Item 是个抽象实体类,使用了 abstract 关键字的话,在数据库中生成的表和上面的例子是相同的。唯一的区别就是,如果根实体是抽象实体类的话,就不能使用 new 关键字来生成这个实体类的对象了。他们的区别只是在 Java 语言语法上的区别,在持久化上没有任何区别。

非实体类 (Nonentity)

非实体类 (Nonentity) 也叫瞬态类 (transient class),就是普通的 POJO 类,没有使用 @Entity 注解 (annotation) 注释,这种类在持久化的时候不会被映射到数据库中,因为根据之前介绍过的持久化的原则,一个类如果想被持久化到数据库中,必须使用 @Entity 注解。

个人感觉,这种好像实际用途不大。

Mapped SupperClass

JPA 有一种特殊的类叫做 Mapped Supper class,这种类不是实体类,他与实体类的区别就是用 @MappedSuperclass 注解来替代 @Entity 注解,其他方面没有变化。

@MappedSuperclass 
@Inheritance(strategy=InheritanceType.JOINED) 
public class Employee { 
   @Id 
   @GeneratedValue 
   private Long id; 
   private String name; 
   private String depart; 
 
   // Getters and Setters 
 
}

 这样在数据库中不用存在Employee对应的表。