JPA 基础(4)—— 关联表映射

JPA 基础(1)—— 数据库持久化代码实战
JPA 基础(2)—— 分页操作
JPA 基础(3)—— Auditing
JPA 基础(4)—— 关联表映射

从JPA2.0开始,不仅支持实体集合的映射,还支持基本类型(如String,Integer等)集合以及值对象(Embeded)集合的映射。

代码实战

学生表——student

实体包含:
学号no(主键),姓名name,成绩关系映射,家庭关系映射,班级关系映射。

package com.sunseaiot.stu.entity;

import lombok.Data;

import javax.persistence.*;
import java.util.Map;
import java.util.Set;

/**
 * @author huangbaoling
 * @date 2019/5/29 9:39 AM
 */
@Data
@Entity
@Table(name = "student")
public class StudentEntity {

    @Id
    @Column(length = 36)
    private String no;

    @Column(length = 32, nullable = false)
    private String name;

    @ElementCollection(fetch = FetchType.EAGER) 
    @CollectionTable(name = "student_to_grade", joinColumns = @JoinColumn(name = "stu_no"))
    @Column(length = 36, nullable = false)
    private Map<String, Grade> grade;

    @ElementCollection(fetch = FetchType.LAZY)
    @CollectionTable(name = "student_to_family", joinColumns = @JoinColumn(name = "stu_no"))
    @Column(name = "family_name")
    private Set<String> familyName;

    @ManyToOne(fetch = FetchType.LAZY) 
    @JoinTable(name = "student_to_class",
            joinColumns = @JoinColumn(name = "stu_no"),
            inverseJoinColumns = @JoinColumn(name = "class_no"))
    private ClassEntity classNo;
}

下面解析student表涉及知识点:

  • @ElementCollection 主要用于映射非实体,@ManyToOne 主要用于映射实体。
  • fetch = FetchType.Lazy 懒加载,作用是用到该属性的时候,才会将该属性加载到内存。加载student对象时,并不会去立即加载familyName和classNo属性。如果不设置,缺省为eager,急加载。
  • @CollectionTable 注解可以指定连接表的详细信息,如表名,外键名等。
  • @JoinTable 注解属性如下,前3个属性常用。
属性 是否必须 说明
name 指定该连接表的表名
JoinColumns 该属性值可接受多个 @JoinColumn,用于配置连接表中外键列的信息,这些外键列参照当前实体对应表的主键列
inverseJoinColumns 该属性值可接受多个@JoinColumn,用于配置连接表中外键列的信息,这些外键列参照当前实体的关联实体对应表的主键列
targetEntity 该属性指定关联实体的类名。在默认情况下,Hibernate将通过反射来判断关联实体的类名
catalog 设置将该连接表放入指定的catalog中。如果没有指定该属性,连接表将放入默认的catalog
schema 设置将该连接表放入指定的schema中。如果没有指定该属性,连接表将放入默认的schema
uniqueConstraints 该属性用于为连接表增加唯一约束
indexes 该属性值为@Index注解数组,用于为该连接表定义多个索引
  • @CollectionTable@JoinTable 在一定程度上可以替换互用,比如只设置JoinColumns时,@CollectionTable 不具备 @JoinTable 特有的 inverseJoinColumns属性。
  • @Column 映射所持久化属性对应的表中的字段名。

三张关联表的设计:
1、学生-成绩关联表
学生有成绩,但成绩无法独立于学生而存在,所以不应该将成绩映射为一个实体entity,这时需要映射为组件(表述可能不正确,只是为了实现对非实体类的关联,如果场景需要也是可以将成绩独立成一个pojo类的)。成绩可以分为各个科目,所以我用Map存储,key对应科目, value对应期中、期末之类分时间段的成绩。
Grade结构:

package com.sunseaiot.stu.entity;

import lombok.Data;

import javax.persistence.Embeddable;

/**
 * @author huangbaoling
 * @date 2019/5/29 2:53 PM
 */
@Data
@Embeddable
public class Grade {

    private String time;

    private String score;
}

2、学生-家人关联表
同样对该连接表使用非实体的映射@ElementCollection,与上表实现相似。

3、学生-班级关联表
班级是一个独立的实体类:

package com.sunseaiot.stu.entity;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * @author huangbaoling
 * @date 2019/5/29 10:37 AM
 */
@Data
@Entity
@Table(name = "class")
public class ClassEntity {

    @Id
    private String no;

    private String name;
}

由于学生和班级是多对一的关系,所以使用注解 @ManyToOne ,@JoinTable(name = “student_to_class”, joinColumns = @JoinColumn(name = “stu_no”), inverseJoinColumns = @JoinColumn(name = “class_no”)) 指明了关联表名student_to_class,设置学生student表的主键为外键stu_no,设置班级class表的主键为外键class_no。

自动建表配置:

spring:
  jpa:
    hibernate:
      ddl-auto: update
  datasource:
    driver-class-name: "com.mysql.cj.jdbc.Driver"
    url: "jdbc:mysql://192.168.5.17:3306/hbl_test?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false"
    username: "root"
    password: "root"

目前Mysql只有InooDB类型的引擎支持外键约束。

启动类:

package com.sunseaiot.stu;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

/**
 * @author huangbaoling
 * @date 2019/5/27 5:19 PM
 */
@SpringBootApplication
@ComponentScan({"com.sunseaiot.stu.entity"})
public class StudentInstallApplication {

    public static void main(String[] args) {
        SpringApplication.run(StudentInstallApplication.class, args);
    }

}

当数据库没有表时,就会自动生成。

疑问:
1、发现生成的student_to_family没有主键,后来将family属性的@Column注解加上不为空后@Column(name = “family_name”, nullable = false),得到的表结构如下:

解答:后者的写法应该才是正确的,因为该关联表是一对多的关系,stu_no和family_name 必须是联合主键。(看到此处请去纠正前面的代码)

2、同样的我们可以看到student_to_class表的class_no也不是主键,但是在classEntity中no为主键,所以应该也是注解出了问题,查看建表ddl语句:

-- auto-generated definition
create table student_to_class
(
  class_no varchar(255) null,
  stu_no   varchar(36)  not null
    primary key,
  constraint FK84yvwttrfub9kbqhs1np9x488
    foreign key (class_no) references class (no),
  constraint FKj5smmktp1b5suue91k2i68apy
    foreign key (stu_no) references student (no)
);

果然class_no不是主键,这显然不是我们想要的。

解答:未解决,后续补充。

3、表 student_to_grade 映射的map值为Grade,生成的关联表会默认为Grade结构体生成一个grade_key,它和指定的stu_no组成联合主键。

解答:未解决,后续补充。

Tips

  • 外键是一种约束,与索引的概念不一样,只是大多数情况下,我们建立外键时,都会在外键列上建立对应的索引。外键的存在会在每一次数据插入、修改时进行约束检查,如果不满足外键约束,则禁止数据的插入或修改,这必然带来一个问题,就是在数据量特别大的情况下,每一次约束检查必然导致性能的下降。索引其实也有类似的问题,索引如果建多了,那么在插入删除修改数据时也要去维护对应的索引,所以索引的存在也会导致数据操作变慢。
  • 不过外键与索引的优点不同,外键只是保证数据的一致性,并不能给系统性能带来任何好处,所以由于外键导致的插入数据变慢会随着数据量的增长而越来越严重。而索引的目的是为了检索数据更快,维护数据时导致的索引数据的变更,对性能的影响不会像外键那样随着数据量增长而变得严重(当然大数量时的索引树维护会比小数据量的索引树维护更麻烦,但至少不是像外键那样)。
  • 出于性能的考虑,如果我们的系统完全由我们开发的程序使用,而不需要提供数据库给其他应用系统写入数据,而且对性能要求较高,那么我们可以考虑在生产环境中不使用外键,只需要建立能够提高性能的索引。由于整个数据库的操作都是由我们开发的程序来完成的,所以我们程序可以在开发过程中做好各方面的一致性检查,保证操作的数据是满足外键约束的,而不需要真正的存在这样一个外键约束。怎么做到这一点呢,首先,我们在建立数据库时有多个脚本,包括创建表、创建初始化数据、创建索引、创建外键等,我们在开发和测试环境中,都把这些脚本运行了,以使开发测试环境中的数据库是完整的,经过大量测试保证应用程序能够维护数据之间的约束的情况下,那么我们在生产时,并不需要运行创建外键这个脚本文件,只需要创建表、初始化数据、创建索引等即可。(引自https://www.cnblogs.com/my_life/articles/5950415.html)

你可能感兴趣的:(数据库)