如果参与过小型团队的生产项目开发,相信你一定被不统一的数据表弄的晕头转向过。代码`pull`到本地后,各种`error`便迫不急待的跳出来与我们会面了。产生问题的原因也很简单:代码与地数据环境不一致。 本节中,让我们使用另外一种集成度高且使用较简单的方法 --- `Spring Data JPA`来使用`JAVA`代码进行数据表的维护。 # ER图 在建立数据表以前,我们首先需要建立数据表图----ER(Entity-relationship model)图。ER图又称为实体关系图,描述的是数据表(实体)信息及数据表(实体)之间的关联信息。当前系统存在两个数据表:教师表、班级表。两个表的关系为:每个班级必然对应1个教师,每个教师可能对应0个或多个班级,所以`教师`与`班级`的关系是`1 : 0..1`。 用ER图来表示为: ![](https://img.kancloud.cn/9e/87/9e87c1ef16f31da6db5b2a9bcf2a3ccb_408x149.png) 除了这种表现形式(IE)以外,还可以用以下形式(IDEF1X)来表示: ![](https://img.kancloud.cn/88/17/8817bb0606898c5792e54dfcc93428d1_426x155.png) 它们只是表现的形式不同而已,所表达的含意是一样的。 在团队的项目中,我们在创建ER图时为了更好的和类图相对应,我们规定: | ER图(JAVA)类型 | 数据表类型 | 备注 | | ---- | ---- | ---- | | Long | bigint | Long在java中为64位,bigint在mysql同为64位 | | String | varchar(255) | 假设字符串的默认长度为255 | # 自动建表 参考ER图,下面来展示如何使用`Spring Data JPA`来进行数据表的维护。 ## 引用依赖 由于`Spring Data JPA`并不属于`Spring Boot`的核心模块,所以在使用JPA时,需要在`pom.xml`中添加如下依赖。 pom.xml ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> ✚ ➊ <groupId>org.springframework.boot</groupId> ✚ ➋ <artifactId>spring-boot-starter-data-jpa</artifactId> ✚ ➌ </dependency> ✚ <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> ``` * ➊ 添加新的依赖. * ➋ 依赖的包所在的班级。 * ➌ 依赖的包的名字。 `pom.xml`变更后IDEA会自动在右下角提示是否自动导入该包,选择`是`即可,这样以后如果该文件再有变更,IDEA则会自动的为我们处理这些变更。如果IDEA没有为我们自动处理或者我们想手动的处理这些依赖,那么也可以打开控制台执行:`mvn install`来手动完成依赖更新。 > IDEA处理依赖的时间长短取决于我们的网络状况,可以点击软件右下角的当前任务来查看处理进度。 ## 配置信息 我们找到`src/main/resources/application.properties`,并增加:`spring.jpa.hibernate.ddl-auto`配置项: ![](https://img.kancloud.cn/12/f7/12f7bf03cf19c4a7154349908fe92174_1218x411.png) 配置项有5个:`create`创建数据表、`create-drop`先创建数据表程序终止时删除数据表、`none`什么也不做、`update`更新数据表、`validate`较验证表。 在此,我们暂时使用`create-drop`做为配置项。 我们虽然在前面配置过数据的连接信息`spring.datasource.url`为`jdbc:mysql:`,即已经指名了数据库使用的为`mysql` 。但`mysql`的版本众多,不同的版本间存在一定的差异,为了让`JPA`能够更好的自动处理这些差异,我们还需要配置数据库`方言`信息。 src/main/resources/application.properties ``` spring.datasource.url=jdbc:mysql://localhost:3307/yunzhi_spring_boot spring.datasource.username=root spring.datasource.password= spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect ➊ ``` * ➊ 告知JPA,我们使用的是`mysql5.7`版本。 如果你使用的是`mysql5.5`或`mysql5.6`,请将上述配置修正为`MySQL55Dialect`。如果你使用的是`MariaDB`请按下图对应修改: ![](https://img.kancloud.cn/55/1c/551c60c39a7085bcb4358e8e368dfa34_319x572.png) **注意:** 类拟于~~MySQL57InnoDBDialect~~这样的标识表示该类的存在为兼容历史版本,其已经被当前版本弃用而不建议使用了。 ## 建立对应的类 我们先建立个`package`,命名为`entity`。 ![](https://img.kancloud.cn/77/4b/774b56574a603fb472cf2f9941e0f54d_559x146.png) 然后在该包下新建Klass类。 ``` . ├── SpringBootStudyApplication.java ├── Teacher.java ├── TeacherController.java └── entity └── Klass.java ``` 代码如下: entity/Klass.java ``` package com.mengyunzhi.springBootStudy.entity; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity ➊ public class Klass { @Id ➎ @GeneratedValue(strategy = GenerationType.IDENTITY) ➏ ➐ private Long id; ➋ private Long teacherId; ➋ private String name; ➋ public Klass() { ➌ } public Long getId() { ➍ return id; } public void setId(Long id) { ➍ this.id = id; } public Long getTeacherId() { return teacherId; } public void setTeacherId(Long teacherId) { this.teacherId = teacherId; } public String getName() { return name; } public void setName(String name) { this.name = name; } } ``` * ➊ 说明Klass是一个与数据表相关的类,在系统启动时将参考该类来`create 生成`、`update 更新`或`validate 验证`数据表 * ➋ 定义相关字段 * ➌ 国际惯例,构建空的构建函数。 * ➍ 国际惯例,设置set/get函数。 * ➎ 说明此数据表的主键为id。 * ➏➐ 定义主健的自增属性。 ## 测试 我们重新启动项目,并使用navicate查看数据库: ![](https://img.kancloud.cn/ce/b1/ceb182e4e20f5d40c3f7d1769ef2daa0_409x143.png) ![](https://img.kancloud.cn/33/35/33352e417980acd752ac34288cd2ca3f_760x162.png) 通过观察可得JPA自动实现了以下功能: * 将JAVA类`Klass`转换为`klass`表。 * 将JAVA类中的属性名转换成了字段名。 * 将`@Id`注解的属性转换为主键且`not null` * 将`@GeneratedValue(strategy = GenerationType.IDENTITY) `注解转换为自增。 * 在转换的过程中,将驼峰式命名转换为下划线式命名。 未实现的功能: * 未设置数据表外键`teacher_id`。 此时,我们如果点击项目停止按钮: ![](https://img.kancloud.cn/71/cb/71cbdbeb7e097095a0f0a6ce014cb89c_734x61.png) 来停止项目,由于我们在前面设置了`create-drop`属性,所以刚刚为我们自动创建的数据表`klass`会被自动删除掉。这样便达到了:在团队开发中,只要保证`pull`的代码是最新的,那么数据表必然也会是同步更新的。 # 设置外键 `JPA`只所以没有成功的设置外键,是由于对`JPA`而言只有使用`@Entity`注解的类才被认为是数据表,所以其认为当前仅有一个`klass`表,在没有`teacher`表的前提下,`JPA`也就当然的无法为我们自动添加此外键了。为了让`JPA`能够看到`teacher`表,我们可以参考`Klass`类建立一个`Teacher`类。写到这我们发现在前面的章节中,我们已经建立了如下的`Teacher`类了: Teacher.java ```java package com.mengyunzhi.springBootStudy; /** * 教师模型,用于更方便的存储查表后返回的数据 */ public class Teacher { private Long id; private String name; private Boolean sex; private String username; private String email; private Long createTime; private Long updateTime; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Boolean getSex() { return sex; } public void setSex(Boolean sex) { this.sex = sex; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public Long getCreateTime() { return createTime; } public void setCreateTime(Long createTime) { this.createTime = createTime; } public Long getUpdateTime() { return updateTime; } public void setUpdateTime(Long updateTime) { this.updateTime = updateTime; } } ``` 与`Klass`相比较,我们只需要增加以下信息该类便自动对应起了数据表: * ★ 说明该类对应数据表。 * ☆ 构建空的构造函数。 * ★ 设置主键。 * ★ 定义主健的自增属性。 > ☆非必要条件;★必要条件。 参考上述四点对`Teacher`表进行改造如下: ``` package com.mengyunzhi.springBootStudy; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; /** * 教师模型,用于更方便的存储查表后返回的数据 */ @Entity ① public class Teacher { @Id ③ @GeneratedValue(strategy = GenerationType.IDENTITY) ④ private Long id; private String name; private Boolean sex; private String username; private String email; private Long createTime; private Long updateTime; public Teacher() { ② } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Boolean getSex() { return sex; } public void setSex(Boolean sex) { this.sex = sex; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public Long getCreateTime() { return createTime; } public void setCreateTime(Long createTime) { this.createTime = createTime; } public Long getUpdateTime() { return updateTime; } public void setUpdateTime(Long updateTime) { this.updateTime = updateTime; } } ``` ## 测试 我们删除数据库中所有的数据表,然后重新运行后台项目,验证是否为我们生成了相应的数据表并对应生成了相应的字段。 ![](https://img.kancloud.cn/82/38/823825f22b14a5a05d85f46453d71e48_622x315.gif) 和我们的预期相一致,JPA非常出色的完成了此项任务。前且当遇到`Boolean`类型时,`JPA`自动的将其转换为`bit(1)`。 ## 添加外键 实体(数据表)与实体间的关系分为以下四种:1对1,1对多,多对1,多对多;在英文中分别是OneToOne、OneToMany、ManyToOne、ManyToMany;在ER图中,我们还会用1:1、1:N、N:1、 M:N 来表示。在`JPA`声明外键只需要: entity/Klass.java ``` private Long teacherId; ✘ @ManyToOne ✚ ➋ private Teacher teacher; ✚ ➊ // 省略了getter/setter函数 ``` * ➊ 类型定义为Teacher实体类 * ➋ 该字段与Teacher实体关联,关系为多对1。 ## 测试 测试是否生成了相应的字段: ![](https://img.kancloud.cn/ae/2a/ae2a01e799994c4509b68a1891886969_498x148.png) 不止如此,`JPA`还自动为我们添加了索引: ![](https://img.kancloud.cn/56/ff/56ffcc9fea7a2661528324a6767326d3_545x87.png) 同时自动为我们添加了外键: ![](https://img.kancloud.cn/9f/c4/9fc4396101fe42bcee5e97e58d15aa55_706x89.png) 这已经完成的符合并超出了我们的预期。 # Spring Data JPA 再认识了`Spring Data JPA`以后,我们再深入地了解下`JPA ---- Java Persistence API JAVA持久层应用接口`。 什么是`持久层`呢,它的作用是什么呢?简单来讲`持久层`的作用就是通过`持久化`的操作将数据保存到数据库当中。`持久化`其实就是`保存数据`的另一种描述方法,只所以这么讲我猜是由于:在数据成功的保存到数据库中以前,数据是在内存中保存的,我们知道内存中的数据是可能随时删除、变更以及被抛弃丢失的(比如突然的停电了),而保存到数据库中以后即使发生了停电的现象,该数据也会不丢失。就像现实生活中我们会把一些我们认为重要的东西记在笔记本上,免得哪天想用这个知识的时候忘记了,这个记笔记的过程在计算机中就做`持久化`。 由于`JPA`本质上是个`接口`,而`接口`的本质就是`规范`。所以通俗来说,`JPA`就是一个在`JAVA`程序下进行数据访问的一种`规范`。而实现这个规范的技术有很多,比如我们刚刚使用的`Spring Data JPA`,除此以外,比较出名的`JPA`还有`hibernate`及`mybatis`。实际上`Spring Data JPA`也是基于`hibernate`的。 为了更好的理解接口与实现我们再拿USB做个例子:其实USB2.0 ,USE3.0都是规范,确切的来说他们就是几张纸一堆文字,规定了接口的大小、形状、有几个触点、每个触点传输什么数据以及如何传输数据等等等等。而我们使用的USE鼠标、U盘等都是在这个规范下的实现。他们的设计遵从了USE规范,从而使其能够与其它同样遵守该规范的设备协作运行。 那么什么是`Spring Data JPA`呢?官方如是说: ***** Spring Data JPA, part of the larger Spring Data family, makes it easy to easily implement JPA based repositories. This module deals with enhanced support for JPA based data access layers. It makes it easier to build Spring-powered applications that use data access technologies. Implementing a data access layer of an application has been cumbersome for quite a while. Too much boilerplate code has to be written to execute simple queries as well as perform pagination, and auditing. Spring Data JPA aims to significantly improve the implementation of data access layers by reducing the effort to the amount that’s actually needed. As a developer you write your repository interfaces, including custom finder methods, and Spring will provide the implementation automatically. ***** 大概的意思就是说,`Spring Data JPA`也是`JPA`,它更简单、更强大。 # 参考文档 | 名称 | 链接 | 预计学习时长(分) | | --- | --- | --- | | 什么是JPA | [https://baike.baidu.com/item/JPA](https://baike.baidu.com/item/JPA) | 10 | | What's JPA | [https://en.wikipedia.org/wiki/Java\_Persistence\_API](https://en.wikipedia.org/wiki/Java_Persistence_API) | 20 | | Spring Data JPA | [https://spring.io/projects/spring-data-jpa](https://spring.io/projects/spring-data-jpa) | 5 | | Accessing Data with JPA | [https://spring.io/guides/gs/accessing-data-jpa/](https://spring.io/guides/gs/accessing-data-jpa/) | 15 | | 源码地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step3.1](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step3.1) | - |