SpringData JPA 数据表映射

SpringData JPA 数据表映射

1、映射注解说明

1.配置多表联系注解介绍
@OneToOne                           一对一映射
    targetEntityClass:             指定另一方类的字节码
    cascade:                       级联操作
        CascadeType.MERGE           级联更新
        CascadeType.PERSIST         级联保存
        CascadeType.REFRESH         级联刷新
        CascadeType.REMOVE          级联删除
        CascadeType.ALL             级联上述4种操作
    fetch:                         抓取策略
      FetchType.LAZY:              延迟加载(默认)
      FetchType.EAGER:             迫切查询(多表连接查询)
    mappedBy:                      放弃外键维护
    orphanRemoval:                 是否使用孤儿删除

@OneToMany(一对多)
    targetEntityClass:             指定多方类的字节码
    cascade:                       指定要使用的级联操作
    fetch:                         指定是否采用延迟加载
    mappedBy:                      指定从表实体类中引用主表对象的名称
    orphanRemoval:                 是否使用孤儿删除

@ManyToOne(多对一)
    targetEntityClass:             指定一的一方实体类字节码
    cascade:                       指定要使用的级联操作
    fetch:                         指定是否采用延迟加载
    optional:                      关联是否可选。如果设置为false,则必须始终存在非空关系

@ManyToMany(多对多)
    targetEntityClass:             指定另一方类的字节码
    cascade:                       配置级联操作
    fetch:                         配置延迟加载和立即加载
    mappedBy:                      放弃外键维护
    
    
2:配置外键关系的注解
@JoinColumn:用于定义主键字段和外键字段的对应关系。
    name:                          指定外键字段的名称
    referencedColumnName:          指定引用主表的主键字段名称
    unique:                        是否唯一。默认值不唯一
    nullable:                      是否允许为空。默认值允许
    insertable:                    是否允许插入。默认值允许
    updatable:                     是否允许更新。默认值允许
    columnDefinition:              列的定义信息
    table
    foreignKey

@JoinTable(针对中间表的设置)
    name:                          配置中间表的名称
    joinColumns:                   中间表的外键字段关联当前实体类所对应表的主键字段
        @JoinColumn:
            name:                  本类的外键
            referencedColumnName:  本类与外键(表)对应的主键
    inverseJoinColumn:             中间表的外键字段关联对方表的主键字段
        @JoinColumn:               
            name:                  对方类的外键
            referencedColumnName:  对方类与外键(表)对应的主键
            
            
3:首先确定多表之间的关系:
    一对一和一对多:
        一的一方称为主表,而多的一方称为从表,外键就要建立在从表上,它们的取值的来源主要来自主键
    多对多:
        这个时候需要建立一个中间表,中间表中最少由2个字段组成,这2个字段作为外键指向2张表的主键又组成了联合主键

4、mappedBy 属性
mappedBy是OneToOne、OneToMany和ManyToMany这三种关联关系的属性。
用来标注拥有这种关系的字段。 除非关系是单向的,否则是必需的。
那么什么叫拥有关联关系呢,假设是双向一对一的话,那么拥有关系的这一方有建立、解除和更新与另一方关系的能力。而另一方没有,只能被动管理。
由于JoinTable和JoinColumn一般定义在拥有关系的这一端,而mappedBy一定是定义在关系的被拥有方(the owned side),也就是跟定义JoinTable和JoinColumn互斥的一方,它的值指向拥有方中关于被拥有方的字段,可能是一个对象(OneToMany),也可能是一个对象集合(ManyToMany)

2、一对一关系映射

2.1、准备一对一

1、表结构和需求

-- 需求:学生表与学生信息表的一对一的关联关系
-- 学生表(主键表):一方
-- 学生信息表(外键表):一方

-- 学生表结构
+--------------+--------------+------+-----+---------+----------------+
| Field        | Type         | Null | Key | Default | Extra          |
+--------------+--------------+------+-----+---------+----------------+
| student_id   | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| student_name | varchar(255) | YES  |     | NULL    |                |
| student_pwd  | varchar(255) | YES  |     | NULL    |                |
+--------------+--------------+------+-----+---------+----------------+

-- 学生信息表结构
+----------------+--------------+------+-----+---------+----------------+
| Field          | Type         | Null | Key | Default | Extra          |
+----------------+--------------+------+-----+---------+----------------+
| information_id | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| student_age    | int(11)      | YES  |     | NULL    |                |
| student_number | varchar(255) | YES  |     | NULL    |                |
| student_id     | bigint(20)   | YES  | MUL | NULL    |                |
+----------------+--------------+------+-----+---------+----------------+

2、创建2个实体类对应的DAO接口

/**
 * 学生表repository接口
 */
public interface StudentRepository extends JpaRepository<Student,Long> {
}
/**
 * 学生信息表repository接口
 */
public interface StudentInformationRepository extends JpaRepository<StudentInformation,Long> {
}

2.2、单向一对一

1、创建:学生信息实体类(拥有方,外键表)

@Data
@Entity
@Table(name = "tb_student_information")
public class StudentInformation implements Serializable {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name = "information_id")
    private Long informationId;
    @Column(name = "student_number")
    private String studentNumber;
    @Column(name = "student_age")
    private Integer studentAge;

    // 单向的话配置可以只配置拥有方(外键方)
    @OneToOne
    @JoinColumn(name = "student_id")
    private Student student;
}

2、创建:学生实体类(被拥有方,主键表)

@Data
@Entity
@Table(name = "tb_student")
public class Student implements Serializable {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name = "student_id")
    private Long studentId;
    @Column(name = "student_name")
    private String studentName;
    @Column(name = "student_pwd")
    private String studentPwd;
}

3、测试代码:

@SpringBootTest
public class AssociationTableTests {
    @Autowired
    StudentRepository studentRepository;
    @Autowired
    StudentInformationRepository informationRepository;

    /**
     * 单向一对一
     */
    @Test
    public void testOneToOneSave(){
        // 首先给学生表(主键表)增加一条数据
        Student student = new Student();
        student.setStudentName("小明");
        student.setStudentPwd("123456");
        studentRepository.saveAndFlush(student);

        // 然后再给学生信息表(外键表)增加一条数据
        StudentInformation information = new StudentInformation();
        information.setStudentNumber("001");
        information.setStudentAge(13);
        // 设置这个字段相当于给外键设置值,如果没有设置那么保存后外键字段将为null
        information.setStudent(student);
        informationRepository.saveAndFlush(information);

        // 查询信息
        System.out.println(teacherRepository.findById(student.getStudentId()));
        System.out.println(informationRepository.findById(information.getInformationId()));
    }
}

查看日志:

Hibernate: 
    insert 
    into
        tb_student
        (student_name, student_pwd) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        tb_student_information
        (student_id, student_age, student_number) 
    values
        (?, ?, ?)
Hibernate: 
    select
        teacher0_.teacher_id as teacher_1_5_0_,
        teacher0_.teacher_name as teacher_2_5_0_,
        teacher0_.teacher_pwd as teacher_3_5_0_ 
    from
        tb_teacher teacher0_ 
    where
        teacher0_.teacher_id=?
Optional.empty
Hibernate: 
    select
        studentinf0_.information_id as informat1_3_0_,
        studentinf0_.student_id as student_4_3_0_,
        studentinf0_.student_age as student_2_3_0_,
        studentinf0_.student_number as student_3_3_0_,
        student1_.student_id as student_1_2_1_,
        student1_.student_name as student_2_2_1_,
        student1_.student_pwd as student_3_2_1_ 
    from
        tb_student_information studentinf0_ 
    left outer join
        tb_student student1_ 
            on studentinf0_.student_id=student1_.student_id 
    where
        studentinf0_.information_id=?
Optional[StudentInformation(informationId=8, studentNumber=001, studentAge=13, student=Student(studentId=8, studentName=小明, studentPwd=123456))]

2.3、双向一对一

1、学生信息实体类(拥有方,外键表)不变

@Data
@Entity
@Table(name = "tb_student_information")
public class StudentInformation implements Serializable {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name = "information_id")
    private Long informationId;
    @Column(name = "student_number")
    private String studentNumber;
    @Column(name = "student_age")
    private Integer studentAge;

    // 单向的话配置可以只配置拥有方(外键方)
    @OneToOne
    @JoinColumn(name = "student_id")
    private Student student;
}

2、学生实体类(主键表)需要增加属性与注解

@Data
@Entity
@Table(name = "tb_student")
public class Student implements Serializable {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name = "student_id")
    private Long studentId;
    @Column(name = "student_name")
    private String studentName;
    @Column(name = "student_pwd")
    private String studentPwd;
    // 上面的属性与单向一对一是一样的,就增加了如下信息
    /**
     * mappedBy:放弃维护外键,值为另一方引用本类的属性名
     */
    @OneToOne(mappedBy = "student")
    private StudentInformation studentInformation;
}

3、测试代码与单向一对一也是一样的

@SpringBootTest
public class AssociationTableTests {
    @Autowired
    StudentRepository studentRepository;
    @Autowired
    StudentInformationRepository informationRepository;

    /**
     * 双向一对一
     */
    @Test
    public void testOneToOneSave(){
        // 首先给学生表(主键表)增加一条数据
        Student student = new Student();
        student.setStudentName("小明");
        student.setStudentPwd("123456");
        studentRepository.saveAndFlush(student);

        // 然后再给学生信息表(外键表)增加一条数据
        StudentInformation information = new StudentInformation();
        information.setStudentNumber("001");
        information.setStudentAge(13);
        // 设置这个字段相当于给外键设置值,如果没有设置那么保存后外键字段将为null
        information.setStudent(student);
        informationRepository.saveAndFlush(information);

        // 查询信息
        System.out.println(teacherRepository.findById(student.getStudentId()));
        System.out.println(informationRepository.findById(information.getInformationId()));
    }
}

4、查看日志:

// 插入语句与查询语句
Hibernate: 
    insert 
    into
        tb_student
        (student_name, student_pwd) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        tb_student_information
        (student_id, student_age, student_number) 
    values
        (?, ?, ?)
Hibernate: 
    select
        teacher0_.teacher_id as teacher_1_5_0_,
        teacher0_.teacher_name as teacher_2_5_0_,
        teacher0_.teacher_pwd as teacher_3_5_0_ 
    from
        tb_teacher teacher0_ 
    where
        teacher0_.teacher_id=?
Optional.empty
Hibernate: 
    select
        studentinf0_.information_id as informat1_3_0_,
        studentinf0_.student_id as student_4_3_0_,
        studentinf0_.student_age as student_2_3_0_,
        studentinf0_.student_number as student_3_3_0_,
        student1_.student_id as student_1_2_1_,
        student1_.student_name as student_2_2_1_,
        student1_.student_pwd as student_3_2_1_ 
    from
        tb_student_information studentinf0_ 
    left outer join
        tb_student student1_ 
            on studentinf0_.student_id=student1_.student_id 
    where
        studentinf0_.information_id=?
// 查询后开始报错
java.lang.StackOverflowError
	at java.lang.StringBuilder.append(StringBuilder.java:136)
	at com.example.jpa.entity.Student.toString(Student.java:13)
	at java.lang.String.valueOf(String.java:2994)

可以发现会报如上栈溢出错误。原因:双向查询后使用(System.out)输出时,toString() 方式时各自对象相互引用导致死循环。

解决方案:

  1. 去除@Data注解,替换成@Getter和@Setter注解。使用@ToString(exclude = “引用类型字段”)
  2. 也可以重写toString()方法,把引用类型的输出删掉即可
  3. 查询后可以不要输出打印该对象,这样就不会死循环输出对象中的对象了(治标不治本, 不推荐)
  4. 在实体类上增加@JsonIgnoreProperties(value = "对方类引用本类的属性名")(暂时我试了时无效的,打印对象还是会报错)

3、一对多关系映射

3.1、准备一对多

1、表结构和需求

-- 需求:学生表与学生称即表的一对多的关联关系
-- 学生表(主键表):一方
-- 学生成绩表(外键表):多方

-- 学生表结构
+--------------+--------------+------+-----+---------+----------------+
| Field        | Type         | Null | Key | Default | Extra          |
+--------------+--------------+------+-----+---------+----------------+
| student_id   | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| student_name | varchar(255) | YES  |     | NULL    |                |
| student_pwd  | varchar(255) | YES  |     | NULL    |                |
+--------------+--------------+------+-----+---------+----------------+

-- 学生成绩表结构
+----------------+--------------+------+-----+---------+----------------+
| Field          | Type         | Null | Key | Default | Extra          |
+----------------+--------------+------+-----+---------+----------------+
| result_id      | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| result_score   | float        | YES  |     | NULL    |                |
| result_subject | varchar(255) | YES  |     | NULL    |                |
| student_id     | bigint(20)   | YES  | MUL | NULL    |                |
+----------------+--------------+------+-----+---------+----------------+

2、创建2个实体类对应的DAO接口

/**
 * 学生表repository接口
 */
public interface StudentRepository extends JpaRepository<Student,Long> {
}
/**
 * 学生成绩表repository接口
 */
public interface StudentResultRepository extends JpaRepository<StudentResult,Long> {
}

3.2、单向一对多

单向一对多的话,那么只能是:一方(主键表)为拥有方,多方(外键表)为被拥有端。(只配置一方即可)

1、创建:学生实体类(拥有方,主键表)只配置一方(主键方)即可

@Data
@Entity
@Table(name = "tb_student")
public class Student implements Serializable {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name = "student_id")
    private Long studentId;
    @Column(name = "student_name")
    private String studentName;
    @Column(name = "student_pwd")
    private String studentPwd;

    /**
     * 单向一对多:只需要配置一方(主键表)即可
     * 一般配置@OneToMany + @JoinColumn
     * 也可以只配置@OneToMany,@JoinColumn会按照默认规则生成外键
     * @OneToMany:
     *     targetEntity:指定对方类的字节码(可选)
     *     fetch:指定是懒加载还是立即加载(@OneToMany中默认是懒加载)(最好必填)
     * @JoinColumn:
     *     name:外键字段名称(必填,不然会默认生成外键字段名)
     *     referencedColumnName:指定引用主表的主键字段名称(注意是数据库字段不是实体类属性名)
     * 注意!注意!注意!
     * 一对多时一方默认时开启懒加载的,需要关闭,非则查询会报错
     */
    @OneToMany(targetEntity = StudentResult.class,fetch = FetchType.EAGER)
    @JoinColumn(name="student_id",referencedColumnName = "student_id")
    private List<StudentResult> studentResults = new ArrayList<>();
}

2、创建:学生成绩实体类(被拥有方,外键表),多方(外键方)无需配置

@Data
@Entity
@Table(name = "tb_student_result")
public class StudentResult {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name = "result_id")
    private Long resultId;
    @Column(name = "result_subject")
    private String resultSubject;
    @Column(name = "result_score")
    private float resultScore;
}

3、测试代码:

    /**
     * 单向 一对多
     * 数据插入的步骤为:
     * 先插入外键表数据,此时外键表中的外键为null,
     * 然后在插入主键表数据,最后更新外键表中的外键
     * 可以查看日志输入的顺序
     */
    @Test
    public void testOneToManySave(){
        // 首先给学生成绩表插入两条数据
        StudentResult studentResultA = new StudentResult();
        studentResultA.setResultSubject("语文");
        studentResultA.setResultScore(89.5f);
        resultRepository.saveAndFlush(studentResultA);
        StudentResult studentResultB = new StudentResult();
        studentResultB.setResultSubject("数学");
        studentResultB.setResultScore(92f);
        resultRepository.saveAndFlush(studentResultB);

        // 然后给学生表(主键表)增加一条数据
        Student student = new Student();
        student.setStudentName("小明");
        student.setStudentPwd("123456");
        student.setStudentResults(Arrays.asList(studentResultA, studentResultB));
        studentRepository.saveAndFlush(student);

        // 查询学生(主)表数据
        System.out.println(studentRepository.findById(student.getStudentId()));
        // 查询成绩(从)表数据
        System.out.println(resultRepository.findById(studentResultA.getResultId()));
        System.out.println(resultRepository.findById(studentResultB.getResultId()));
    }

4、查看日志

// 初始化表的语句
Hibernate: 
    create table tb_student (
       student_id bigint not null auto_increment,
        student_name varchar(255),
        student_pwd varchar(255),
        primary key (student_id)
    ) engine=InnoDB
Hibernate: 
    create table tb_student_result (
       result_id bigint not null auto_increment,
        result_score float,
        result_subject varchar(255),
        student_id bigint,
        primary key (result_id)
    ) engine=InnoDB
Hibernate: 
    alter table tb_student_result 
       add constraint FK5101rgeygkbcenssvt342o3kg 
       foreign key (student_id) 
       references tb_student (student_id)
Hibernate: // 插入数据语句
    insert 
    into
        tb_student_result
        (result_score, result_subject) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        tb_student_result
        (result_score, result_subject) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        tb_student
        (student_name, student_pwd) 
    values
        (?, ?)
Hibernate: 
    update
        tb_student_result 
    set
        student_id=? 
    where
        result_id=?
Hibernate: 
    update
        tb_student_result 
    set
        student_id=? 
    where
        result_id=?
Hibernate: // 查询语句
    select
        student0_.student_id as student_1_2_0_,
        student0_.student_name as student_2_2_0_,
        student0_.student_pwd as student_3_2_0_,
        studentres1_.student_id as student_4_4_1_,
        studentres1_.result_id as result_i1_4_1_,
        studentres1_.result_id as result_i1_4_2_,
        studentres1_.result_score as result_s2_4_2_,
        studentres1_.result_subject as result_s3_4_2_ 
    from
        tb_student student0_ 
    left outer join
        tb_student_result studentres1_ 
            on student0_.student_id=studentres1_.student_id 
    where
        student0_.student_id=?
// 输出查询结果
Optional[Student(studentId=1, studentName=小明, studentPwd=123456, studentResults=[StudentResult(resultId=1, resultSubject=语文, resultScore=89.5), StudentResult(resultId=2, resultSubject=数学, resultScore=92.0)])]
Hibernate: // 查询语句
    select
        studentres0_.result_id as result_i1_4_0_,
        studentres0_.result_score as result_s2_4_0_,
        studentres0_.result_subject as result_s3_4_0_ 
    from
        tb_student_result studentres0_ 
    where
        studentres0_.result_id=?
Optional[StudentResult(resultId=1, resultSubject=语文, resultScore=89.5)]
Hibernate: 
    select
        studentres0_.result_id as result_i1_4_0_,
        studentres0_.result_score as result_s2_4_0_,
        studentres0_.result_subject as result_s3_4_0_ 
    from
        tb_student_result studentres0_ 
    where
        studentres0_.result_id=?
Optional[StudentResult(resultId=2, resultSubject=数学, resultScore=92.0)]

PS:可能会出现如下报错信息:

org.hibernate.LazyInitializationException: failed to lazily initialize XXXXX could not initialize proxy - no Session

问题解决:这个问题是由于实体中一对多或者多对多关联关系的加载方式配置不当引起的。一对多或者多对多关联关系的加载策略使用了懒加载,结果在加载子实体时就会报:org.hibernate.LazyInitializationException: failed to lazily initialize XXXXX could not initialize proxy - no Session错误,只需要将懒加载改为急加载即可

方式一:修改实体类

// 修改学生实体类(主表),从一方获取多方的时候默认时懒加载.改成立即加载
@OneToMany(fetch = FetchType.EAGER)

方式二:配置文件修改

# 开启延迟加载
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

两者方式选择一种即可。

3.3、单向多对一

单向多对一的话:多方(外键表)为拥有方,一方(主键表)为被拥有端。(只配置多方即可)

1、创建:学生成绩实体类(拥有方,外键表)(只配置多方(外键方)即可)

@Data
@Entity
@Table(name = "tb_student_result")
public class StudentResult {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name = "result_id")
    private Long resultId;
    @Column(name = "result_subject")
    private String resultSubject;
    @Column(name = "result_score")
    private float resultScore;

    /**
     * 单向多对一:只配置多方(外键方)即可。
     * @ManyToOne
     *     name:指定对方类的字节码(可选)
     * @JoinColumn
     *     name:外键字段名称(必填,不然会默认生成外键字段名)
     *     referencedColumnName:指定引用主表的主键字段名称(注意是数据库字段不是实体类属性名)
     */
    @ManyToOne(targetEntity = Student.class)
    @JoinColumn(name = "student_id", referencedColumnName = "student_id")
    private Student student;
}

2、创建:学生实体类(被拥有方,主键表)(一方(主键方)无需配置)

@Data
@Entity
@Table(name = "tb_student")
public class Student implements Serializable {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name = "student_id")
    private Long studentId;
    @Column(name = "student_name")
    private String studentName;
    @Column(name = "student_pwd")
    private String studentPwd;
}

3、测试代码

    /**
     * 单向 多对一
     * 数据插入的步骤为:先插入插入主键表数据,然后在外键表数据
     */
    @Test
    public void testManyToOneSave(){

        // 首先给学生表(主键表)增加一条数据
        Student student = new Student();
        student.setStudentName("小明");
        student.setStudentPwd("123456");
        studentRepository.saveAndFlush(student);

        /**
         * 然后给学生成绩表插入两条数据
         * 与单向一对多不同的是:setStudent(student),这个步骤就是给外键字段设置值
         */
        StudentResult studentResultA = new StudentResult();
        studentResultA.setResultSubject("语文");
        studentResultA.setResultScore(89.5f);
        studentResultA.setStudent(student);
        resultRepository.saveAndFlush(studentResultA);
        StudentResult studentResultB = new StudentResult();
        studentResultB.setResultSubject("数学");
        studentResultB.setResultScore(92f);
        studentResultB.setStudent(student);
        resultRepository.saveAndFlush(studentResultB);

        // 查询学生(主)表数据
        System.out.println(studentRepository.findById(student.getStudentId()));
        // 查询成绩(从)表数据
        System.out.println(resultRepository.findById(studentResultA.getResultId()));
        System.out.println(resultRepository.findById(studentResultB.getResultId()));
    }

4、查看日志

// 初始化表的语句
Hibernate: 
    create table tb_student (
       student_id bigint not null auto_increment,
        student_name varchar(255),
        student_pwd varchar(255),
        primary key (student_id)
    ) engine=InnoDB
Hibernate: 
    create table tb_student_result (
       result_id bigint not null auto_increment,
        result_score float,
        result_subject varchar(255),
        student_id bigint,
        primary key (result_id)
    ) engine=InnoDB
Hibernate: 
    alter table tb_student_result 
       add constraint FK5101rgeygkbcenssvt342o3kg 
       foreign key (student_id) 
       references tb_student (student_id)
Hibernate: // 插入语句
    insert 
    into
        tb_student
        (student_name, student_pwd) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        tb_student_result
        (result_score, result_subject, student_id) 
    values
        (?, ?, ?)
Hibernate: 
    insert 
    into
        tb_student_result
        (result_score, result_subject, student_id) 
    values
        (?, ?, ?)
Hibernate: // 查询学生表语句
    select
        student0_.student_id as student_1_2_0_,
        student0_.student_name as student_2_2_0_,
        student0_.student_pwd as student_3_2_0_ 
    from
        tb_student student0_ 
    where
        student0_.student_id=?
Optional[Student(studentId=1, studentName=小明, studentPwd=123456)]
Hibernate: // 关联查询语句
    select
        studentres0_.result_id as result_i1_4_0_,
        studentres0_.result_score as result_s2_4_0_,
        studentres0_.result_subject as result_s3_4_0_,
        studentres0_.student_id as student_4_4_0_,
        student1_.student_id as student_1_2_1_,
        student1_.student_name as student_2_2_1_,
        student1_.student_pwd as student_3_2_1_ 
    from
        tb_student_result studentres0_ 
    left outer join
        tb_student student1_ 
            on studentres0_.student_id=student1_.student_id 
    where
        studentres0_.result_id=?
Optional[StudentResult(resultId=1, resultSubject=语文, resultScore=89.5, student=Student(studentId=1, studentName=小明, studentPwd=123456))]
Hibernate: 
    select
        studentres0_.result_id as result_i1_4_0_,
        studentres0_.result_score as result_s2_4_0_,
        studentres0_.result_subject as result_s3_4_0_,
        studentres0_.student_id as student_4_4_0_,
        student1_.student_id as student_1_2_1_,
        student1_.student_name as student_2_2_1_,
        student1_.student_pwd as student_3_2_1_ 
    from
        tb_student_result studentres0_ 
    left outer join
        tb_student student1_ 
            on studentres0_.student_id=student1_.student_id 
    where
        studentres0_.result_id=?
Optional[StudentResult(resultId=2, resultSubject=数学, resultScore=92.0, student=Student(studentId=1, studentName=小明, studentPwd=123456))]

3.4、双向一对多

双向一对多的话:多方(外键表)为拥有方,一方(主键表)为被拥有端。

1、创建:学生成绩实体类(拥有方,多方、外键表)(多方 / 外键方 配置:@ManyToOne + @JoinColumn)
PS:实际跟单向多对一代码一样,多了一个重写toString方法,为了避免输出死循环

@Data
@ToString(exclude = "student") // 为了避免输出死循环重写了toString
@Entity
@Table(name = "tb_student_result")
public class StudentResult {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name = "result_id")
    private Long resultId;
    @Column(name = "result_subject")
    private String resultSubject;
    @Column(name = "result_score")
    private float resultScore;

    /**
     * 单向多对一:只配置多方(外键方)即可。
     * @ManyToOne
     *     name:指定对方类的字节码(可选)
     * @JoinColumn
     *     name:外键字段名称(必填,不然会默认生成外键字段名)
     *     referencedColumnName:指定引用主表的主键字段名称(注意是数据库字段不是实体类属性名)
     */
    @ManyToOne(targetEntity = Student.class)
    @JoinColumn(name = "student_id", referencedColumnName = "student_id")
    private Student student;
}

2、创建:学生实体类(被拥有方,一方、主键表)(一方 / 主键方 配置:@OneToMany)
PS:实际上跟单向多对一相比:加了一个属性加上了@OneToMany,并且重写toString方法避免死循环

@Data
@ToString(exclude = "studentResults") // 为了避免输出死循环重写了toString
@Entity
@Table(name = "tb_student")
public class Student implements Serializable {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name = "student_id")
    private Long studentId;
    @Column(name = "student_name")
    private String studentName;
    @Column(name = "student_pwd")
    private String studentPwd;

    /**
     * 双向一对多:(实际就比单向多对一多了一个@OneToMany)
     * 必须在一方配置:@OneToMany:mappedBy + fetch 属性
     * @OneToMany
     *     mappedBy:放弃维护外键,值为另一方(拥有方 / 外键方 / 多方)引用本类的属性名
     *     fetch:指定是懒加载还是立即加载(@OneToMany中默认是懒加载)(最好必填)
     */
    @OneToMany(mappedBy = "student",fetch = FetchType.EAGER)
    private List<StudentResult> studentResults = new ArrayList<>();
}

3、测试代码

    /**
     * 双向 一对多
     * 数据插入的步骤为:先插入插入主键表数据,然后在外键表数据
     */
    @Test
    public void testOneToManyTwoWaySave2(){

        // 首先给学生表(主键表)增加一条数据
        Student student = new Student();
        student.setStudentName("小明");
        student.setStudentPwd("123456");
        studentRepository.saveAndFlush(student);

        /**
         * 然后给学生成绩表插入两条数据
         * 与单向一对多不同的是:setStudent(student),这个步骤就是给外键字段设置值
         */
        StudentResult studentResultA = new StudentResult();
        studentResultA.setResultSubject("语文");
        studentResultA.setResultScore(89.5f);
        resultRepository.saveAndFlush(studentResultA);
        StudentResult studentResultB = new StudentResult();
        studentResultB.setResultSubject("数学");
        studentResultB.setResultScore(92f);
        resultRepository.saveAndFlush(studentResultB);

        // 查询学生(主)表数据
        System.out.println(studentRepository.findById(student.getStudentId()));
        // 查询成绩(从)表数据
        System.out.println(resultRepository.findById(studentResultA.getResultId()));
        System.out.println(resultRepository.findById(studentResultB.getResultId()));
    }

4、查看日志

// 建表语句跟之前一样
Hibernate: // 插入语句
    insert 
    into
        tb_student
        (student_name, student_pwd) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        tb_student_result
        (result_score, result_subject, student_id) 
    values
        (?, ?, ?)
Hibernate: 
    insert 
    into
        tb_student_result
        (result_score, result_subject, student_id) 
    values
        (?, ?, ?)
Hibernate: // 关联查询语句
    select
        student0_.student_id as student_1_2_0_,
        student0_.student_name as student_2_2_0_,
        student0_.student_pwd as student_3_2_0_,
        studentres1_.student_id as student_4_4_1_,
        studentres1_.result_id as result_i1_4_1_,
        studentres1_.result_id as result_i1_4_2_,
        studentres1_.result_score as result_s2_4_2_,
        studentres1_.result_subject as result_s3_4_2_,
        studentres1_.student_id as student_4_4_2_ 
    from
        tb_student student0_ 
    left outer join
        tb_student_result studentres1_ 
            on student0_.student_id=studentres1_.student_id 
    where
        student0_.student_id=?
Optional[Student{studentId=1, studentName='小明', studentPwd='123456'}]
Hibernate: // 关联查询语句
    select
        studentres0_.result_id as result_i1_4_0_,
        studentres0_.result_score as result_s2_4_0_,
        studentres0_.result_subject as result_s3_4_0_,
        studentres0_.student_id as student_4_4_0_,
        student1_.student_id as student_1_2_1_,
        student1_.student_name as student_2_2_1_,
        student1_.student_pwd as student_3_2_1_ 
    from
        tb_student_result studentres0_ 
    left outer join
        tb_student student1_ 
            on studentres0_.student_id=student1_.student_id 
    where
        studentres0_.result_id=?
Hibernate: 
    select
        studentres0_.student_id as student_4_4_0_,
        studentres0_.result_id as result_i1_4_0_,
        studentres0_.result_id as result_i1_4_1_,
        studentres0_.result_score as result_s2_4_1_,
        studentres0_.result_subject as result_s3_4_1_,
        studentres0_.student_id as student_4_4_1_ 
    from
        tb_student_result studentres0_ 
    where
        studentres0_.student_id=?
Optional[StudentResult{resultId=1, resultSubject='语文', resultScore=89.5}]
Hibernate: // 关联查询语句
    select
        studentres0_.result_id as result_i1_4_0_,
        studentres0_.result_score as result_s2_4_0_,
        studentres0_.result_subject as result_s3_4_0_,
        studentres0_.student_id as student_4_4_0_,
        student1_.student_id as student_1_2_1_,
        student1_.student_name as student_2_2_1_,
        student1_.student_pwd as student_3_2_1_ 
    from
        tb_student_result studentres0_ 
    left outer join
        tb_student student1_ 
            on studentres0_.student_id=student1_.student_id 
    where
        studentres0_.result_id=?
Hibernate: 
    select
        studentres0_.student_id as student_4_4_0_,
        studentres0_.result_id as result_i1_4_0_,
        studentres0_.result_id as result_i1_4_1_,
        studentres0_.result_score as result_s2_4_1_,
        studentres0_.result_subject as result_s3_4_1_,
        studentres0_.student_id as student_4_4_1_ 
    from
        tb_student_result studentres0_ 
    where
        studentres0_.student_id=?
Optional[StudentResult{resultId=2, resultSubject='数学', resultScore=92.0}]

PS 注意1:

双向一对多中,一方/被拥有方/主键表 一般我们是放弃外键维护的,所以想使用单向一对多的方式插入数据,那么外键是会为空的(即:先插入多方/外键表数据,然后插入一方/主键表数据,最后在一方/主键表 内给引用的多方属性设置值。如果是在单向一对多的情况下,最后会有一个update语句修改外键null的值,当时在双向一对多中并不会,最后插入完数据,外键表会为空)

PS 注意2:

双向一对多中,一方/被拥有方/主键表 中我们去掉放弃外键维护配置,然后再次使用单向一对多的方式插入数据,会发现外键字段依旧为null,当时可以看到日志中显示:JPA给我多创建了一个中间表来维护两个表的主外键。还是把两张表关联起来了。

// 去除放弃外键维护配置,然后再运行一对多中测试代码,我们再查看日志
@OneToMany(/*mappedBy = "student",*/fetch = FetchType.EAGER)
private List<StudentResult> studentResults = new ArrayList<>();
Hibernate: 
    
    create table tb_student_student_results (
       student_student_id bigint not null,
        student_results_result_id bigint not null
    ) engine=MyISAM
Hibernate: 
    
    alter table tb_student_student_results 
       drop index UK_3ifcd0d9c34kmalbnsk94a3h2
Hibernate: 
    
    alter table tb_student_student_results 
       add constraint UK_3ifcd0d9c34kmalbnsk94a3h2 unique (student_results_result_id)
Hibernate: 
    
    alter table tb_student_result 
       add constraint FK5101rgeygkbcenssvt342o3kg 
       foreign key (student_id) 
       references tb_student (student_id)
Hibernate: 
    
    alter table tb_student_student_results 
       add constraint FKknyiphtyp1nnff3jm5c5ahe3a 
       foreign key (student_results_result_id) 
       references tb_student_result (result_id)
Hibernate: 
    
    alter table tb_student_student_results 
       add constraint FKfpfap07ekhngj6ya5e9sx9ngb 
       foreign key (student_student_id) 
       references tb_student (student_id)

4、多对多关系映射

4.1、准备多对多

1、表结构和需求

-- 需求:学生表与学生称即表的一对多的关联关系
-- 学生表(主键表):多方
-- 老师表(主键表):多方
-- 学生老师表(外键中间表)

-- 学生表结构
+--------------+--------------+------+-----+---------+----------------+
| Field        | Type         | Null | Key | Default | Extra          |
+--------------+--------------+------+-----+---------+----------------+
| student_id   | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| student_name | varchar(255) | YES  |     | NULL    |                |
| student_pwd  | varchar(255) | YES  |     | NULL    |                |
+--------------+--------------+------+-----+---------+----------------+

-- 老师表结构
+--------------+--------------+------+-----+---------+----------------+
| Field        | Type         | Null | Key | Default | Extra          |
+--------------+--------------+------+-----+---------+----------------+
| teacher_id   | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| teacher_name | varchar(255) | YES  |     | NULL    |                |
| teacher_pwd  | varchar(255) | YES  |     | NULL    |                |
+--------------+--------------+------+-----+---------+----------------+

-- 学生老师表结构
+------------+------------+------+-----+---------+-------+
| Field      | Type       | Null | Key | Default | Extra |
+------------+------------+------+-----+---------+-------+
| student_id | bigint(20) | NO   | MUL | NULL    |       |
| teacher_id | bigint(20) | NO   | MUL | NULL    |       |
+------------+------------+------+-----+---------+-------+

2、创建2个实体类对应的Dao接口

/**
 * 学生表repository接口
 */
public interface StudentRepository extends JpaRepository<Student,Long> {
}
/**
 * 老师表repository接口
 */
public interface TeacherRepository extends JpaRepository<Teacher,Long> {
}

4.2、单向多对多

单向多对多:两张表都为多方,谁都可以为拥有方和被拥有方。这里定义:学生表为拥有方,老师表为被拥有方

1、创建:学生实体类(拥有方)

@Data
@Entity
@Table(name = "tb_student")
public class Student implements Serializable {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name = "student_id")
    private Long studentId;
    @Column(name = "student_name")
    private String studentName;
    @Column(name = "student_pwd")
    private String studentPwd;

    /**
     * @JoinTable:
     *   name:中间表名称
     *   joinColumns          中间表对应本类的信息
     *     @JoinColumn;
     *       name:本类的外键字段(中间表的数据库字段)
     *       referencedColumnName:本类与外键(表)对应的主键(本类的主键字段)
     *   inverseJoinColumns    中间表对应对方类的信息
     *     @JoinColumn:
     *       name:对方类的外键(中间表的数据字段)
     *       referencedColumnName:对方类与外键(表)对应的主键(对方类的主键字段)
     *
     *  注意!注意!注意:
     *  不管是一对多或者多对多:
     *  @OneToMany、@ManyToMany 中的fetch属性默认都是懒加载.
     *  可以理解为查询本实体类中的多方(List/Set)属性时,默认是懒加载
     */
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name="tb_student_teacher",
            joinColumns=@JoinColumn(
                    name="student_id",
                    referencedColumnName = "student_id"),
            inverseJoinColumns=@JoinColumn(
                    name="teacher_id",
                    referencedColumnName = "teacher_id"))
    private List<Teacher> teachers = new ArrayList<>();
}

2、创建:老师实体类(被拥有方)(无需配置)

@Data
@Entity
@Table(name = "tb_teacher")
public class Teacher {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name = "teacher_id")
    private Long teacherId;
    @Column(name = "teacher_name")
    private String teacherName;
    @Column(name = "teacher_pwd")
    private String teacherPwd;
}

3、测试代码

    /**
     * 单向 多对多
     * 先插入被拥有端数据,然后插入拥有端数据
     */
    @Test
    public void testManyToMany(){
        // 首先给老师表(被拥有方)增加数据
        Teacher teacherA = new Teacher();
        teacherA.setTeacherName("语文老师");
        teacherA.setTeacherPwd("666666");
        teacherRepository.saveAndFlush(teacherA);
        Teacher teacherB = new Teacher();
        teacherB.setTeacherName("数学老师");
        teacherB.setTeacherPwd("888888");
        teacherRepository.saveAndFlush(teacherB);
        Teacher teacherC = new Teacher();
        teacherC.setTeacherName("英语老师");
        teacherC.setTeacherPwd("888888");
        teacherRepository.saveAndFlush(teacherC);

        // 然后给学生表数据(拥有方)增加数据
        Student studentA = new Student();
        studentA.setStudentName("小明");
        studentA.setStudentPwd("123456");
        // 这步骤设置值就是给中间表插入数据
        studentA.setTeachers(Arrays.asList(teacherA,teacherB));
        studentRepository.saveAndFlush(studentA);
        Student studentB = new Student();
        studentB.setStudentName("小明");
        studentB.setStudentPwd("123456");
        // 这步骤设置值就是给中间表插入数据
        studentB.setTeachers(Arrays.asList(teacherB,teacherC));
        studentRepository.saveAndFlush(studentB);

        // 查询学生表(拥有方)数据
        System.out.println(studentRepository.findById(studentA.getStudentId()));
        System.out.println(studentRepository.findById(studentB.getStudentId()));
    }

4、查看日志

// 建立中间表日志
Hibernate: 
    
    alter table tb_student_teacher 
       add constraint FKf3vdcta2bcm9seh723ge2tel 
       foreign key (teacher_id) 
       references tb_teacher (teacher_id)
Hibernate: 
    
    alter table tb_student_teacher 
       add constraint FKkhrh8vhu4pnumefhvuwdiby8y 
       foreign key (student_id) 
       references tb_student (student_id)

// 省略了插入数据语句和查询语句...
Optional[Student(studentId=1, studentName=小明, studentPwd=123456, teachers=[Teacher(teacherId=1, teacherName=语文老师, teacherPwd=666666), Teacher(teacherId=2, teacherName=数学老师, teacherPwd=888888)])]
// 查询语句省略...可以参考上面查询语句,一样的
Optional[Student(studentId=2, studentName=小明, studentPwd=123456, teachers=[Teacher(teacherId=2, teacherName=数学老师, teacherPwd=888888), Teacher(teacherId=3, teacherName=英语老师, teacherPwd=888888)])]
// 完整的日志
Hibernate: // 插入数据
    insert 
    into
        tb_teacher
        (teacher_name, teacher_pwd) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        tb_teacher
        (teacher_name, teacher_pwd) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        tb_teacher
        (teacher_name, teacher_pwd) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        tb_student
        (student_name, student_pwd) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        tb_student_teacher
        (student_id, teacher_id) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        tb_student_teacher
        (student_id, teacher_id) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        tb_student
        (student_name, student_pwd) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        tb_student_teacher
        (student_id, teacher_id) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        tb_student_teacher
        (student_id, teacher_id) 
    values
        (?, ?)
Hibernate: // 查询语句
    select
        student0_.student_id as student_1_2_0_,
        student0_.student_name as student_2_2_0_,
        student0_.student_pwd as student_3_2_0_,
        teachers1_.student_id as student_1_5_1_,
        teacher2_.teacher_id as teacher_2_5_1_,
        teacher2_.teacher_id as teacher_1_6_2_,
        teacher2_.teacher_name as teacher_2_6_2_,
        teacher2_.teacher_pwd as teacher_3_6_2_ 
    from
        tb_student student0_ 
    left outer join
        tb_student_teacher teachers1_ 
            on student0_.student_id=teachers1_.student_id 
    left outer join
        tb_teacher teacher2_ 
            on teachers1_.teacher_id=teacher2_.teacher_id 
    where
        student0_.student_id=?
Student(studentId=1, studentName=小明, studentPwd=123456, teachers=[Teacher(teacherId=1, teacherName=语文老师, teacherPwd=666666), Teacher(teacherId=2, teacherName=数学老师, teacherPwd=888888)])
2
Hibernate: 
    select
        student0_.student_id as student_1_2_0_,
        student0_.student_name as student_2_2_0_,
        student0_.student_pwd as student_3_2_0_,
        teachers1_.student_id as student_1_5_1_,
        teacher2_.teacher_id as teacher_2_5_1_,
        teacher2_.teacher_id as teacher_1_6_2_,
        teacher2_.teacher_name as teacher_2_6_2_,
        teacher2_.teacher_pwd as teacher_3_6_2_ 
    from
        tb_student student0_ 
    left outer join
        tb_student_teacher teachers1_ 
            on student0_.student_id=teachers1_.student_id 
    left outer join
        tb_teacher teacher2_ 
            on teachers1_.teacher_id=teacher2_.teacher_id 
    where
        student0_.student_id=?
Student(studentId=2, studentName=小明, studentPwd=123456, teachers=[Teacher(teacherId=2, teacherName=数学老师, teacherPwd=888888), Teacher(teacherId=3, teacherName=英语老师, teacherPwd=888888)])
2
Hibernate: 
    select
        teacher0_.teacher_id as teacher_1_6_0_,
        teacher0_.teacher_name as teacher_2_6_0_,
        teacher0_.teacher_pwd as teacher_3_6_0_ 
    from
        tb_teacher teacher0_ 
    where
        teacher0_.teacher_id=?
Teacher(teacherId=2, teacherName=数学老师, teacherPwd=888888)

4.3、双向多对多

双向多对多:两张表都为多方,谁都可以为拥有方和被拥有方。这里定义:学生表为拥有方,老师表为被拥有方

1、创建:学生实体类(拥有方)(参考单向多对多代码,为了避免死循环重写了ToString方法)

@Data
@ToString(exclude = "teachers")
@Entity
@Table(name = "tb_student")
public class Student implements Serializable {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name = "student_id")
    private Long studentId;
    @Column(name = "student_name")
    private String studentName;
    @Column(name = "student_pwd")
    private String studentPwd;

    /**
     * @JoinTable:
     *   name:中间表名称
     *   joinColumns          中间表对应本类的信息
     *     @JoinColumn;
     *       name:本类的外键字段(中间表的数据库字段)
     *       referencedColumnName:本类与外键(表)对应的主键(本类的主键字段)
     *   inverseJoinColumns    中间表对应对方类的信息
     *     @JoinColumn:
     *       name:对方类的外键(中间表的数据字段)
     *       referencedColumnName:对方类与外键(表)对应的主键(对方类的主键字段)
     *
     *  注意!注意!注意:
     *  不管是一对多或者多对多:
     *  @OneToMany、@ManyToMany 中的fetch属性默认都是懒加载.
     *  可以理解为查询本实体类中的多方(List/Set)属性时,默认是懒加载
     */
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name="tb_student_teacher",
            joinColumns=@JoinColumn(
                    name="student_id",
                    referencedColumnName = "student_id"),
            inverseJoinColumns=@JoinColumn(
                    name="teacher_id",
                    referencedColumnName = "teacher_id"))
    private List<Teacher> teachers = new ArrayList<>();
}

2、创建:老师实体类(被拥有方)(增加@ManyToMany和重写ToString方法)

@Data
@Entity
@Table(name = "tb_teacher")
public class Teacher {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name = "teacher_id")
    private Long teacherId;
    @Column(name = "teacher_name")
    private String teacherName;
    @Column(name = "teacher_pwd")
    private String teacherPwd;

    /**
     * 双向多对多:
     * 需要配置 @ManyToMany:mappedBy + fetch 属性
     * @ManyToMany
     *     mappedBy:放弃维护外键,值为另一方(拥有方)引用本类的属性名
     *     fetch:指定是懒加载还是立即加载(@OneToMany中默认是懒加载)(最好必填)
     */
    @ManyToMany(mappedBy = "teachers",fetch = FetchType.EAGER)
    private List<Student> students = new ArrayList<>();
}

3、测试代码

    /**
     * 单向/双向 多对多
     * 先插入被拥有端数据,然后插入拥有端数据
     */
    @Test
    public void testManyToMany(){
        // 首先给老师表(被拥有方)增加数据
        Teacher teacherA = new Teacher();
        teacherA.setTeacherName("语文老师");
        teacherA.setTeacherPwd("666666");
        teacherRepository.saveAndFlush(teacherA);
        Teacher teacherB = new Teacher();
        teacherB.setTeacherName("数学老师");
        teacherB.setTeacherPwd("888888");
        teacherRepository.saveAndFlush(teacherB);
        Teacher teacherC = new Teacher();
        teacherC.setTeacherName("英语老师");
        teacherC.setTeacherPwd("888888");
        teacherRepository.saveAndFlush(teacherC);

        // 然后给学生表数据(拥有方)增加数据
        Student studentA = new Student();
        studentA.setStudentName("小明");
        studentA.setStudentPwd("123456");
        // 这步骤设置值就是给中间表插入数据
        studentA.setTeachers(Arrays.asList(teacherA,teacherB));
        studentRepository.saveAndFlush(studentA);
        Student studentB = new Student();
        studentB.setStudentName("小明");
        studentB.setStudentPwd("123456");
        // 这步骤设置值就是给中间表插入数据
        studentB.setTeachers(Arrays.asList(teacherB,teacherC));
        studentRepository.saveAndFlush(studentB);

        // 查询学生表(拥有方)数据
        Student student1 = studentRepository.findById(studentA.getStudentId()).get();
        System.out.println(student1.getTeachers().size());
        Student student2 = studentRepository.findById(studentB.getStudentId()).get();
        System.out.println(student2.getTeachers().size());

        // 查询老师表(被拥有方)数据
        Teacher teacher = teacherRepository.findById(teacherB.getTeacherId()).get();
        System.out.println(teacher);
        System.out.println(teacher.getStudents().size());
    }

4、查看日志

Hibernate: 
    select
        student0_.student_id as student_1_2_0_,
        student0_.student_name as student_2_2_0_,
        student0_.student_pwd as student_3_2_0_,
        teachers1_.student_id as student_1_5_1_,
        teacher2_.teacher_id as teacher_2_5_1_,
        teacher2_.teacher_id as teacher_1_6_2_,
        teacher2_.teacher_name as teacher_2_6_2_,
        teacher2_.teacher_pwd as teacher_3_6_2_ 
    from
        tb_student student0_ 
    left outer join
        tb_student_teacher teachers1_ 
            on student0_.student_id=teachers1_.student_id 
    left outer join
        tb_teacher teacher2_ 
            on teachers1_.teacher_id=teacher2_.teacher_id 
    where
        student0_.student_id=?
Hibernate: 
    select
        students0_.teacher_id as teacher_2_5_0_,
        students0_.student_id as student_1_5_0_,
        student1_.student_id as student_1_2_1_,
        student1_.student_name as student_2_2_1_,
        student1_.student_pwd as student_3_2_1_ 
    from
        tb_student_teacher students0_ 
    inner join
        tb_student student1_ 
            on students0_.student_id=student1_.student_id 
    where
        students0_.teacher_id=?
Hibernate: 
    select
        teachers0_.student_id as student_1_5_0_,
        teachers0_.teacher_id as teacher_2_5_0_,
        teacher1_.teacher_id as teacher_1_6_1_,
        teacher1_.teacher_name as teacher_2_6_1_,
        teacher1_.teacher_pwd as teacher_3_6_1_ 
    from
        tb_student_teacher teachers0_ 
    inner join
        tb_teacher teacher1_ 
            on teachers0_.teacher_id=teacher1_.teacher_id 
    where
        teachers0_.student_id=?
Hibernate: 
    select
        students0_.teacher_id as teacher_2_5_0_,
        students0_.student_id as student_1_5_0_,
        student1_.student_id as student_1_2_1_,
        student1_.student_name as student_2_2_1_,
        student1_.student_pwd as student_3_2_1_ 
    from
        tb_student_teacher students0_ 
    inner join
        tb_student student1_ 
            on students0_.student_id=student1_.student_id 
    where
        students0_.teacher_id=?
Hibernate: 
    select
        students0_.teacher_id as teacher_2_5_0_,
        students0_.student_id as student_1_5_0_,
        student1_.student_id as student_1_2_1_,
        student1_.student_name as student_2_2_1_,
        student1_.student_pwd as student_3_2_1_ 
    from
        tb_student_teacher students0_ 
    inner join
        tb_student student1_ 
            on students0_.student_id=student1_.student_id 
    where
        students0_.teacher_id=?
Student{studentId=7, studentName='小明', studentPwd='123456'}
2
Hibernate: 
    select
        student0_.student_id as student_1_2_0_,
        student0_.student_name as student_2_2_0_,
        student0_.student_pwd as student_3_2_0_,
        teachers1_.student_id as student_1_5_1_,
        teacher2_.teacher_id as teacher_2_5_1_,
        teacher2_.teacher_id as teacher_1_6_2_,
        teacher2_.teacher_name as teacher_2_6_2_,
        teacher2_.teacher_pwd as teacher_3_6_2_ 
    from
        tb_student student0_ 
    left outer join
        tb_student_teacher teachers1_ 
            on student0_.student_id=teachers1_.student_id 
    left outer join
        tb_teacher teacher2_ 
            on teachers1_.teacher_id=teacher2_.teacher_id 
    where
        student0_.student_id=?
Hibernate: 
    select
        students0_.teacher_id as teacher_2_5_0_,
        students0_.student_id as student_1_5_0_,
        student1_.student_id as student_1_2_1_,
        student1_.student_name as student_2_2_1_,
        student1_.student_pwd as student_3_2_1_ 
    from
        tb_student_teacher students0_ 
    inner join
        tb_student student1_ 
            on students0_.student_id=student1_.student_id 
    where
        students0_.teacher_id=?
Hibernate: 
    select
        students0_.teacher_id as teacher_2_5_0_,
        students0_.student_id as student_1_5_0_,
        student1_.student_id as student_1_2_1_,
        student1_.student_name as student_2_2_1_,
        student1_.student_pwd as student_3_2_1_ 
    from
        tb_student_teacher students0_ 
    inner join
        tb_student student1_ 
            on students0_.student_id=student1_.student_id 
    where
        students0_.teacher_id=?
Hibernate: 
    select
        teachers0_.student_id as student_1_5_0_,
        teachers0_.teacher_id as teacher_2_5_0_,
        teacher1_.teacher_id as teacher_1_6_1_,
        teacher1_.teacher_name as teacher_2_6_1_,
        teacher1_.teacher_pwd as teacher_3_6_1_ 
    from
        tb_student_teacher teachers0_ 
    inner join
        tb_teacher teacher1_ 
            on teachers0_.teacher_id=teacher1_.teacher_id 
    where
        teachers0_.student_id=?
Hibernate: 
    select
        students0_.teacher_id as teacher_2_5_0_,
        students0_.student_id as student_1_5_0_,
        student1_.student_id as student_1_2_1_,
        student1_.student_name as student_2_2_1_,
        student1_.student_pwd as student_3_2_1_ 
    from
        tb_student_teacher students0_ 
    inner join
        tb_student student1_ 
            on students0_.student_id=student1_.student_id 
    where
        students0_.teacher_id=?
Student{studentId=8, studentName='小明', studentPwd='123456'}
2
Hibernate: 
    select
        teacher0_.teacher_id as teacher_1_6_0_,
        teacher0_.teacher_name as teacher_2_6_0_,
        teacher0_.teacher_pwd as teacher_3_6_0_,
        students1_.teacher_id as teacher_2_5_1_,
        student2_.student_id as student_1_5_1_,
        student2_.student_id as student_1_2_2_,
        student2_.student_name as student_2_2_2_,
        student2_.student_pwd as student_3_2_2_ 
    from
        tb_teacher teacher0_ 
    left outer join
        tb_student_teacher students1_ 
            on teacher0_.teacher_id=students1_.teacher_id 
    left outer join
        tb_student student2_ 
            on students1_.student_id=student2_.student_id 
    where
        teacher0_.teacher_id=?
Hibernate: 
    select
        teachers0_.student_id as student_1_5_0_,
        teachers0_.teacher_id as teacher_2_5_0_,
        teacher1_.teacher_id as teacher_1_6_1_,
        teacher1_.teacher_name as teacher_2_6_1_,
        teacher1_.teacher_pwd as teacher_3_6_1_ 
    from
        tb_student_teacher teachers0_ 
    inner join
        tb_teacher teacher1_ 
            on teachers0_.teacher_id=teacher1_.teacher_id 
    where
        teachers0_.student_id=?
Hibernate: 
    select
        students0_.teacher_id as teacher_2_5_0_,
        students0_.student_id as student_1_5_0_,
        student1_.student_id as student_1_2_1_,
        student1_.student_name as student_2_2_1_,
        student1_.student_pwd as student_3_2_1_ 
    from
        tb_student_teacher students0_ 
    inner join
        tb_student student1_ 
            on students0_.student_id=student1_.student_id 
    where
        students0_.teacher_id=?
Hibernate: 
    select
        teachers0_.student_id as student_1_5_0_,
        teachers0_.teacher_id as teacher_2_5_0_,
        teacher1_.teacher_id as teacher_1_6_1_,
        teacher1_.teacher_name as teacher_2_6_1_,
        teacher1_.teacher_pwd as teacher_3_6_1_ 
    from
        tb_student_teacher teachers0_ 
    inner join
        tb_teacher teacher1_ 
            on teachers0_.teacher_id=teacher1_.teacher_id 
    where
        teachers0_.student_id=?
Hibernate: 
    select
        students0_.teacher_id as teacher_2_5_0_,
        students0_.student_id as student_1_5_0_,
        student1_.student_id as student_1_2_1_,
        student1_.student_name as student_2_2_1_,
        student1_.student_pwd as student_3_2_1_ 
    from
        tb_student_teacher students0_ 
    inner join
        tb_student student1_ 
            on students0_.student_id=student1_.student_id 
    where
        students0_.teacher_id=?
Teacher{teacherId=11, teacherName='数学老师', teacherPwd='888888'}
2

5、多表关系映射问题总结

1、JPA自动建表不生成外键

SpringBoot项目搭配的JPA使用时候,有一对多的关系注解,那么自动会生成外键。外键在有些时候,会导致代码不能走通,我们不想要怎么做。有两种解决方案.

一对多

情况一:单向一对多/多对一,在@JoinColumn内加上@ForeignKey属性配置即可

@OneToMany
@JoinColumn(name="cid",foreignKey = @ForeignKey(name = "none",value = ConstraintMode.NO_CONSTRAINT))

@ManyToOne
@JoinColumn(name="cid",foreignKey = @ForeignKey(name = "none",value = ConstraintMode.NO_CONSTRAINT))

情况二:双向一对多,除了在@JoinColumn加@ForeignKey配置,还需要再另一方(一方)加配置

1.解决方案一:

// 一方加@org.hibernate.annotations.ForeignKey,注意:这个注解被废弃了,所以更新jar包的时候需要注意下
@org.hibernate.annotations.ForeignKey(name = “none”)
@OneToMany

// 多方配置不变
@ManyToOne
@JoinColumn(name="cid",foreignKey = @ForeignKey(name = "none",value = ConstraintMode.NO_CONSTRAINT))

2.解决方案二:

// 一方加@Transient,意思是忽略该字段,我认为加上这个字段后在加@OneToMany也无意义了.
@Transient
@OneToMany

// 多方配置不变
@ManyToOne
@JoinColumn(name="cid",foreignKey = @ForeignKey(name = "none",value = ConstraintMode.NO_CONSTRAINT))

多对多也是类似的处理方式

单向多对多:只要在@JoinTable内的@JoinColumn内加上@ForeignKey属性配置即可,记得连个表外键都要加

@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name="tb_student_teacher",
           joinColumns=@JoinColumn(
               name="student_id",
               referencedColumnName = "student_id",
               foreignKey = @ForeignKey(name = "none",value = ConstraintMode.NO_CONSTRAINT)),
           inverseJoinColumns=@JoinColumn(
               name="teacher_id",
               referencedColumnName = "teacher_id",
               foreignKey = @ForeignKey(name = "none",value = ConstraintMode.NO_CONSTRAINT)))

双向多对多:参照双向一对多/多对一,在另一方加注解即可。

2、关于实体对象查询异常

WARNING: Please consider reporting this to the maintainers of org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
Hibernate: sql语句
java.lang.StackOverflowError
    at java.base/java.lang.AbstractStringBuilder.inflate(AbstractStringBuilder.java:202)
    at java.base/java.lang.AbstractStringBuilder.putStringAt(AbstractStringBuilder.java:1639)
    at java.base/java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:513)
   .......

关以这种错误大多都是编写的toString语句发生了错误,大家在写toString的时候一定要分清主表和从表,在编写主表的toString的时候一定要去除外键字段,而在编写从表的时候一定要加上外键字段,因为平时我们都是通过从表查询数据(因为从表有指向主表的外键),这样可以把主表的数据通过外键查询出来,但是主表上如果也有从表的字段的话就会一只循环,数据没完没了,所有抛异常也是对的,

总结一句话:从表有主表的字段,主表有从表的字段,打印从表顺带打印主表,但是主表里面还有从表字段,然后继续打印从表字段…

解决方案:

  1. 去除 @Data 注解,替换成 @Getter 和 @Setter 注解,使用@ToString(exclude = “引用类型字段”)
  2. 也可以重写toString()方法,把引用类型的输出删掉即可
  3. 查询后可以不要输出打印该对象,这样就不会死循环输出对象中的对象了(治标不治本, 不推荐)
  4. 在实体类上增加@JsonIgnoreProperties(value = "对方类引用本类的属性名")(我暂时试了是无效的,打印对象还是会报错)

3、关于延迟加载事务问题

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: cn.xw.domain.Teacher.students, could not initialize proxy - no Session 
    at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:606)
    at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:218)
    at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:585)
    at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:149)
    at org.hibernate.collection.internal.PersistentSet.toString(PersistentSet.java:327)
    at java.base/java.lang.String.valueOf(String.java:2801)
    at java.base/java.lang.StringBuilder.append(StringBuilder.java:135)

关于在使用延迟加载的时候,在当前的方法上必须设置@Transactional,因为在使用延迟加载底层已经使用了事务的相关方法

4、关于懒加载no session异常

org.hibernate.LazyInitializationException: failed to lazily initialize XXXXX could not initialize proxy - no Session

仔细看英文,懒加载存在,session 都关了,获取不到数据。

解决办法:lazy=“false”。若是用注解生成的。fetch = FetchType.EAGER

# 开启延迟加载,这个配置的意思就是在没有事务的情况下允许懒加载。
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

当然了,这样的话,你的缓存就不起作用了。另外解决办法,获取数据时,使用用 left join fetch 或 inner join fetch 语法。

5、关于数据库方言问题(Dialect)

6月 03, 2020 4:57:00 下午 org.hibernate.dialect.Dialect 
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
省去部分...
Hibernate: create table family (...) type=MyISAM
//上面一局为我们创建的是一张表并设置MyISAM引擎  错误就在这 无法运行了
6月 03, 2020 4:57:01 下午 org.hibernate.tool.schema.internal.ExceptionHandlerLoggedImpl handleException
WARN: GenerationTarget encountered exception accepting command : Error executing DDL "create table family (...) type=MyISAM" via JDBC Statement
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "create table family (...) type=MyISAM" via JDBC Statement
//从上面错误可以看出 程序运行的时候默认的数据库方言设置了 org.hibernate.dialect.MySQLDialect  而这个默认是MyISAM引擎

问题所在:因为我导入的hibernate坐标是5.4.10.Final,在导入这类高版本的坐标往往要为数据库方言设置MySQL5InnoDBDialect的配置,在我前面也测试了,关于坐标版本问题,发现5.0.x.Final左右的版本不用设置数据库方言,默认即可。

# 设置数据库方言,建表使用MyISAM,默认是InnoDB
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect

具体的版本在创建数据库表的时候会抛各种异常,这里我整理了一下数据库方言,方便大家参考

数据库方言
DB2                         org.hibernate.dialect.DB2Dialect
DB2 AS/400                  org.hibernate.dialect.DB2400Dialect
DB2 OS390                   org.hibernate.dialect.DB2390Dialect
PostgreSQL                  org.hibernate.dialect.PostgreSQLDialect
MySQL                       org.hibernate.dialect.MySQLDialect
MySQL with InnoDB           org.hibernate.dialect.MySQLInnoDBDialect
MySQL with MyISAM           org.hibernate.dialect.MySQLMyISAMDialect
Oracle (any version)        org.hibernate.dialect.OracleDialect
Oracle 9i/10g               org.hibernate.dialect.Oracle9Dialect
Sybase                      org.hibernate.dialect.SybaseDialect
Sybase Anywhere             org.hibernate.dialect.SybaseAnywhereDialect
Microsoft SQL Server        org.hibernate.dialect.SQLServerDialect
SAP DB                      org.hibernate.dialect.SAPDBDialect
Informix                    org.hibernate.dialect.InformixDialect
HypersonicSQL               org.hibernate.dialect.HSQLDialect
Ingres                      org.hibernate.dialect.IngresDialect
Progress                    org.hibernate.dialect.ProgressDialect
Mckoi SQL                   org.hibernate.dialect.MckoiDialect
Interbase                   org.hibernate.dialect.InterbaseDialect
Pointbase                   org.hibernate.dialect.PointbaseDialect
FrontBase                   org.hibernate.dialect.FrontbaseDialect
Firebird                    org.hibernate.dialect.FirebirdDialect

你可能感兴趣的:(从零开始学,Spring,Data,JPA,java,hibernate,spring)