一直对数据库映射这块缺乏练习,导致在实践中总是卡在一个某个具体的问题上。因此这里花上一小段时间将映射这块练习一下,方便日后回顾。
环境准备:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.automannn</groupId>
<artifactId>hibernateRelationShipPractice</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
</project>
数据源,目录,以及启动类:
package com.automannn;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author [email protected]
* @time 2018/10/20 16:51
*/
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class);
}
}
一对一单向映射的练习:
设 一个学校有且仅有一个校长,一个校长仅能任职于一个学校。 因此他们就构成了一对一的关系。
package com.automannn.entity;
import javax.persistence.*;
/**
* @author [email protected]
* @time 2018/10/20 17:08
*/
@Entity
public class HeadMaster {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String headMasterName;
@OneToOne
private School school;
}
package com.automannn.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
/**
* @author [email protected]
* @time 2018/10/20 17:07
*/
@Entity
public class School {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String schoolName;
}
这个时候,校长是主控方,持有学校的信息。运行之后查表:
在这种场合下,我们可以通过主控方查看另一方的信息。即通过校长查看到相应的学校信息。 反之,若学校为主控方,那么相应的也要反转,这是单向的。 并且他们的联系是通过外键关联的,但是我看到很多的博客都说不要使用外键,关系的对应通过程序控制。
一对一双向映射练习:
package com.automannn.entity;
import javax.persistence.*;
/**
* @author [email protected]
* @time 2018/10/20 17:08
*/
@Entity
public class HeadMaster {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String headMasterName;
@OneToOne(mappedBy = "headMaster")
private School school;
}
package com.automannn.entity;
import javax.persistence.*;
/**
* @author [email protected]
* @time 2018/10/20 17:07
*/
@Entity
public class School {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String schoolName;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "headMaster_id")
private HeadMaster headMaster;
}
它们的表结构如下:
要注意,将hibernate在update状态下,貌似对于删除表结构的操作可能有缺陷,所以导致我测试浪费了一些时间。
最简形式:
通过它的构成形式,可以猜测,这个步骤是hibernate的工作,而非数据库的工作。 同时,注解的属性与类中的属性通过程序上下文联系起来。 不具有固定性!!! 后面思考了下,这种说法不准确,虽然是上下文,但是属性变量实际上是起到了一个管道的作用,因此要写的对应关系还是很好理解的了。
多与一的常见关系映射练习:
设: 某个班具有多个学生,每个学生只能属于一个班。 因此,构成了这样的一个多与一的映射关系。
现有一个需求如下:
需要知道每个班有哪些学生? 那么可以通过单向一对多,单向多对一考虑。
单向一对多:
package com.automannn.entity;
import javax.persistence.*;
import java.util.List;
/**
* @author [email protected]
* @time 2018/10/20 16:53
*/
@Entity
public class Clazz {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String clazzName;
@OneToMany
private List<Student> studentList;
}
package com.automannn.entity;
import javax.persistence.*;
/**
* @author [email protected]
* @time 2018/10/20 16:52
*/
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
}
这种情况下,它生成的表结构:
会生成一张只具有外键的中间表。 并不是我们想要的效果。
这是在具有最简注解:的情况。
当我们在Clazz.class修改如下:
可以发现它的表结构:
可以发现一个现象,就是我们的那个joinColumn是在 Clazz.class实体写的,但是加入的属性列在Student表中。 因为,只要是一与多的关系,hibernate都是在多的一方记录信息。
多对一,与一对多的根本区别在于:一致性维护由哪一方完成,因为这里涉及到性能的问题。但是由于这里是单向的关系,jpa规范应该是默认由多的一方完成吧,因为在注解中没找到inverse属性,同时看网上的解释也有:
因此就不再去看了。 另外要注意,mappedBy 与 joinColumn不能同时出现,会报错。mappedBy只适用于双向关系的那种可能。
最简配置:
结构:
以上可以满足通过班级查询学生,而且是比较直观的理解。
那么,多对一实际也可以满足这种需求。
单向一对多的练习:
package com.automannn.entity;
import javax.persistence.*;
/**
* @author [email protected]
* @time 2018/10/20 16:52
*/
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
@ManyToOne
private Clazz clazz;
}
package com.automannn.entity;
import javax.persistence.*;
import java.util.List;
/**
* @author [email protected]
* @time 2018/10/20 16:53
*/
@Entity
public class Clazz {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String clazzName;
}
它生成的表结构:
这种情况下,我们实际上是去查询学生表,将班级以属性的方式进行查询的。
而上面的那种情况是查询班级表,以列表方式直观的将本班级学生给查询出来。
值得注意的是,在这两种情况下,我们都能够对两个表的信息进行增删该查。 并且表的结构也一样,只是查询的逻辑不同,还有就是性能方面可能有一些不同。
由于我并没有很多的实战经验,但是我的感觉是,当查询的请求为主时,完全可以使用单向一对多的方式将所有的信息给查出来。否则,当以数据更新操作为主的时候,应该使用单向多对一的方式。
Ok,如果有另一个需求,需要知道每个学生所在的班级信息。 也就是需要从多的一端获取一的数据。这个时候可以这样干,那就是在根本上修改这种一与多的关系,导致它变长多与多的关系,形成一个回环。 但是这样很明显是不明智的。正确的做法应该是使用双向关系。因为这里是双向的,所以也就不存在方向性了,因为它的表结构都是一样的。
双向多与一映射关系练习:
package com.automannn.entity;
import javax.persistence.*;
import java.util.List;
/**
* @author [email protected]
* @time 2018/10/20 16:53
*/
@Entity
public class Clazz {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String clazzName;
@OneToMany(mappedBy = "clazz")
private List<Student> studentList;
}
package com.automannn.entity;
import javax.persistence.*;
/**
* @author [email protected]
* @time 2018/10/20 16:52
*/
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
@ManyToOne
private Clazz clazz;
}
它生成的表结构:
注意一下,@ManyToOne是没有mappedBy属性的。
fetch属性一般默认即可,因为默认的都是推荐的。
可以说,双向的这种一与多的关系简直就是我们的理想状态了。 并且将维护关系设置为多的一方,那我们就可以很方便,愉快的进行开发哈哈哈。 都说对性能有影响,但是我觉得性能是一个虚的概念,当业务需求根本不可能到达瓶颈的时候,性能问题根本不是问题了。
很开心愉快的进行了一对一单双向,一对多单向,多对一单向,一与多双向的练习。 接下来就进入最后一个的练习。 多对多映射。
多对多映射的练习:
设:一个老师有多个学生,一个学生可以被多个老师教。 因此他们就构成了一种多对多的关系。
在这种多对多的关系中,我们经常要通过一方查询另一方的需求,因此也就不考虑单双向的问题了。
但是发现多对多关系中是存在单双向问题的:
package com.automannn.entity;
import javax.persistence.*;
import java.util.List;
/**
* @author [email protected]
* @time 2018/10/20 19:29
*/
@Entity
public class Teacher {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String teacherName;
@ManyToMany
private List<Student> student;
}
package com.automannn.entity;
import com.automannn.before.Clazz;
import javax.persistence.*;
/**
* @author [email protected]
* @time 2018/10/20 16:52
*/
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
}
生成的表结构如下:
这是单向的情况。 接着修改代码,使之变为双向,以满足业务需求。
package com.automannn.entity;
import javax.persistence.*;
import java.util.List;
/**
* @author [email protected]
* @time 2018/10/20 19:29
*/
@Entity
public class Teacher {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String teacherName;
@ManyToMany(mappedBy ="teacherList" )
private List<Student> studentList;
}
package com.automannn.entity;
import com.automannn.before.Clazz;
import javax.persistence.*;
import java.util.List;
/**
* @author [email protected]
* @time 2018/10/20 16:52
*/
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
@ManyToMany
private List<Teacher> teacherList;
}
它的表结构:
实践过程的一些反思与总结:
1.OnetoOne,ManyToOne,OneToMany,ManyToMany这几种关系中,是这样来表示逻辑关系的: to之前的表示本端,或者说本实体对象,to之后的表示当前的属性对象,即被注解的实体对象。
2.哪些情况下会在不设置其它的附加信息的情况下会生成中间表? 当被映射的实体对象为“一”的逻辑地位(不论是To之前还是之后),同时具有“多”外键的情况下的时候,会生成中间表!!! 即当“一”的一方需要维护多的一方关系的时候,会自动生成中间表。如:单向OneToMany,且不加@JoinColumn的情况。
3.当逻辑地位为“多”的属性时候,必需设置为集合,否则会报错! 如: @ManyToOne private Student student;正确,而 @OneToMany private Student student; 错误!