后端一定得与数据打交道,所以一定会做数据持久化,在 SpringBoot 中提供的 JPA 的 Api,可以很方便的实现数据的 CRUD。
一 添加依赖
在数据库部分,我使用的是 MySQL,所以在 pom 文件中需要添加如下依赖:
<!-- MySQL 连接依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Jpa 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
二 定义实体类
需要持久化的实体类,可以通过注解很方便的定义,通过给实体类添加一些注解,SpringBoot 就会自动创建表,列的约束以及表之间的关系等。
下面定义一个用户类:
package com.qinshou.springbootdemo.bean;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* Description:用户实体类
* Author: QinHao
* Date: 2019/7/26 9:01
*/
@Entity
@Table(name = "user")
public class UserBean {
/**
* 自增长 Id
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
/**
* 用户名
*/
@Column(name = "username")
private String username;
/**
* 密码
*/
@Column(name = "password")
private String password;
/**
* 昵称
*/
@Column(name = "nickname")
private String nickname;
/**
* 生日
*/
@Column(name = "birthday")
private Long birthday;
}
运行程序后可以看到建表语句(如果配置文件中设置显示 SQL 语句的话):
2.1 数据持久化常用的注解
1.@Entity
使用了该注解的实体类表示需要持久化到数据库,SpringBoot 会创建该实体类对应的数据库。
属性:
name:表名,如果不指定,则会用下划线命名法来转换类名作为表名。表名也可以使用 @Table 注解指定。
2.@Table
该注解用于表的定义。
属性:
name:表名,如果不指定,则会使用 @Entity 注解的表名命名规则。
catalog:数据库名,一般来说,数据库名是在数据库连接语句中指定的(也就是配置文件中),如果不指定该属性,则会使用连接语句中的数据库,如果指定了,则会在指定的数据库中创建该表,但如果指定的数据库不存在则会抛出异常。
schema:
uniqueConstraints:指定唯一约束,既可以指定单列约束也可以指定多列约束,如果是单列约束的时候,效果跟在 @Column 注解上指定 unique 属性一样。
indexes:指定索引,既可以指定单个索引约束也可以指定联合索引。
3.@UniqueConstraint
该属性用在 @Table 注解的 uniqueConstraints 属性中,用于指定约束。
属性:
name:约束名,如果不指定则随机生成。
columnNames:约束的参考列,如果指定的列名不存在则会抛出异常。
4.@Index
该属性用在 @Table 注解的 indexes属性中,用于指定索引。
属性:
name:索引名,如果不指定则随机生成。
columnNames:索引的参考列,如果指定的列名不存在则会抛出异常。
unique:是否唯一,如果为 true,则效果跟使用 @UniqueConstraint 注解一样。
5.@Id
该注解用于标识该属性为主键
6.@GeneratedValue
该注解用于指定主键生成策略。
属性:
strategy:主键生成策略,可选值有:
1)TABLE:使用一个特定的数据库表格来保存主键,持久化引擎通过关系数据库的一张特定的表格来生成主键。
2)SEQUENCE:在某些数据库中,不支持主键自增长,比如 Oracle,所以提供了一种“序列”的机制来生成主键。
3)IDENTITY:主键自增长,相当于 MySQL 中的 AUTO_INCREMENT。
4)AUTO:主键生成策略交给持久化引擎决定。
generator:主键生成器的名称。
7.@Column
该注解用于列的定义。
属性:
name:列名,如果不指定,则会用下划线命名法来转换属性名作为列名。
unique:是否唯一。
nullable:是否可以为空。
insertable:使用 “INSERT” 脚本插入数据时,是否需要插入该字段的值。
updatable:使用 “UPDATE” 脚本插入数据时,是否需要插入该字段的值。
columnDefinition:自定义列的 DDL,在有一些个性化需求的时候会用到,如 “TEXT”,”NOT NULL” 等。
table:当映射多个表时,指定表的表中的字段。默认值为主表的表名。
length:指定字段的长度。
precision:当字段类型为 double 时,表示数值的总长度
scale:当字段类型为 double 时,表示小数点所占的位数。
三 表与表的关系
表与表之间的关系只有三种,一对一,一对多和多对多。这三种关系都可能用到一个注解 @JoinColumn。
@JoinColumn:该注解用于指定关联列。如果不使用该注解的话有可能会生成一些中间表。
属性:
name:用于指定外键的名称。
分别用几个例子来说明一下这几种关系用到的注解。
3.1 一对一
用户实体类:
package com.qinshou.springbootdemo.bean;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
/**
* Description:用户实体类
* Author: QinHao
* Date: 2019/7/26 9:01
*/
@Entity()
@Table(name = "user")
public class UserBean {
/**
* 自增长 Id
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Integer id;
/**
* 用户名
*/
@Column(name = "username")
private String username;
/**
* 密码
*/
@Column(name = "password")
private String password;
/**
* 昵称
*/
@Column(name = "nickname")
private String nickname;
/**
* 生日
*/
@Column(name = "birthday")
private Long birthday;
/**
* 驾照
*/
@JoinColumn(name = "driving_license_id")
@OneToOne(cascade = CascadeType.ALL,orphanRemoval = true)
private DrivingLicenseBean mDrivingLicenseBean;
...
}
驾照实体类:
package com.qinshou.springbootdemo.bean;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;
/**
* Description:驾照实体类
* Author: QinHao
* Date: 2019/7/26 15:08
*/
@Entity
@Table(name = "driving_license")
public class DrivingLicenseBean {
/**
* 自增长 Id
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Integer id;
/**
* 编号
*/
@Column(name = "number")
private String number;
/**
* 用户
*/
@OneToOne(mappedBy = "mDrivingLicenseBean")
private UserBean mUserBean;
@Override
public String toString() {
return "DrivingLicenseBean{" +
"id=" + id +
", number='" + number + '\'' +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public UserBean getUserBean() {
return mUserBean;
}
public void setUserBean(UserBean userBean) {
mUserBean = userBean;
}
}
@OneToOne
该注解表示一对一关系,如一个丈夫对应一个妻子,一个人对应一本驾照等。
属性:
targetEntity:对应实体类的 class,如果不指定该属性则会是 @OneToOne 注解修饰的成员变量的 class。一般不用指定。
cascade:级联关系,如果该属性指定得不对,在做对应操作时会抛出异常。级联关系通常在维护关系的一方中设置。
1)CascadeType.PERSIST:级联插入。
2)CascadeType.REMOVE:级联删除。
3)CascadeType.REFRESH:级联刷新,这个刷新不同于更新,是每次操作一方时,会先重新查询一下另一方的数据的意思。
4)CascadeType.MERGE:级联更新。
4)CascadeType.ALL:以上四种全部操作。
fetch:对应实体类的加载方式。
1)FetchType.EAGER:默认方式,查询时也立即把对应另一方的数据查询出来。
2)FetchType.LAZY:懒加载,什么时候用到了什么时候才会查询另一方的数据。
optional:表示对应实体类是否可以存在 null 值,true 表示可以,false 表示不可以。
mapperBy:用在被维护方,设置了该属性后,被维护方的表就不会生成对应列,注意该属性的值必须是维护方中指定被维护方的变量名,如上方的 mDrivingLicenseBean。
orphanRemoval:是否删除孤儿数据,设置为 true 时,当被维护方的数据没有被维护方关联时会自动删除,比如当一个人的驾照更新,这个人会与新的驾照数据建立关联,与旧的驾照数据也会解除关联,如果该属性为 true,则旧驾照的数据会自动删除。
3.2 一对多(多对一)
用户实体类:
package com.qinshou.springbootdemo.bean;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
/**
* Description:用户实体类
* Author: QinHao
* Date: 2019/7/26 9:01
*/
@Entity()
@Table(name = "user")
public class UserBean {
/**
* 自增长 Id
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Integer id;
/**
* 用户名
*/
@Column(name = "username")
private String username;
/**
* 密码
*/
@Column(name = "password")
private String password;
/**
* 昵称
*/
@Column(name = "nickname")
private String nickname;
/**
* 生日
*/
@Column(name = "birthday")
private Long birthday;
/**
* 驾照
*/
@JoinColumn(name = "driving_license_id")
@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
private DrivingLicenseBean mDrivingLicenseBean;
/**
* 拥有的汽车
*/
@JoinColumn(name = "user_id")
@OneToMany(cascade = CascadeType.ALL)
private List<CarBean> mCarBeanList;
...
}
汽车实体类:
package com.qinshou.springbootdemo.bean;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
/**
* Description:汽车实体类
* Author: QinHao
* Date: 2019/7/26 14:29
*/
@Entity
@Table(name = "car")
public class CarBean {
/**
* 自增长 Id
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Integer id;
/**
* 品牌
*/
@Column(name = "brand")
private String brand;
/**
* 型号
*/
@Column(name = "model")
private String model;
/**
* 车主
*/
@JoinColumn(name = "user_id")
@ManyToOne()
private UserBean user;
...
}
@OneToMany
该注解表示一对多中的一方,如一个人拥有多辆车,一个部门有多个员工等,通常“一”方也是维护关系的一方。它的各属性跟 @OneToOne 差不多,需要注意的是,使用了该注解的属性,应该也要使用 @JoinColumn 注解,并且 @JoinColumn 注解的 name 属性的值应该是一方的参考列,参考上面代码。
@ManyToOne
该注解表示一对多中的多方。它的各属性也跟 @OneToOne 差不多,需要注意的是,使用了该注解的属性,应该也要使用 @JoinColumn 注解,并且 @JoinColumn 注解的 name 属性的值应该是一方的参考列,参考上面代码,如果不加 @JoinColumn 注解则会多生成一张关系表。
3.3 多对多
老师实体类:
package com.qinshou.springbootdemo.bean;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
/**
* Description:老师实体类
* Author: QinHao
* Date: 2019/7/31 9:31
*/
@Entity
@Table(name = "teacher")
public class TeacherBean {
/**
* 自增长 Id
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Integer id;
/**
* 姓名
*/
@Column(name = "name")
private String name;
/**
* 学生列表
*/
@JoinTable(name = "teacher_student_rel"
, joinColumns = {@JoinColumn(name = "teacher_id", referencedColumnName = "id")}
, inverseJoinColumns = {@JoinColumn(name = "student_id", referencedColumnName = "id")})
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<StudentBean> mStudentBeanList;
public TeacherBean() {
}
public TeacherBean(String name) {
this.name = name;
}
@Override
public String toString() {
return "TeacherBean{" +
"id=" + id +
", name='" + name + '\'' +
", mStudentBeanList=" + mStudentBeanList +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<StudentBean> getStudentBeanList() {
return mStudentBeanList;
}
public void setStudentBeanList(List<StudentBean> studentBeanList) {
mStudentBeanList = studentBeanList;
}
}
学生实体类:
package com.qinshou.springbootdemo.bean;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
/**
* Description:学生实体类
* Author: QinHao
* Date: 2019/7/31 9:31
*/
@Entity
@Table(name = "student")
public class StudentBean {
/**
* 自增长 Id
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Integer id;
/**
* 姓名
*/
@Column(name = "name")
private String name;
/**
* 老师列表
*/
@ManyToMany(fetch = FetchType.LAZY, mappedBy = "mStudentBeanList")
private List<TeacherBean> mTeacherBeanList;
public StudentBean() {
}
public StudentBean(String name) {
this.name = name;
}
@Override
public String toString() {
return "StudentBean{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<TeacherBean> getTeacherBeanList() {
return mTeacherBeanList;
}
public void setTeacherBeanList(List<TeacherBean> teacherBeanList) {
mTeacherBeanList = teacherBeanList;
}
}
@ManyToMany
该注解表示多对多关系,如一个老师可以有多个学生,一个学生也可以有多个老师;一个学生可以选多门选修课,一门选修课也可以被多个学生选。该注解和 @JoinTable 注解更配哦,@JoinTable 等一下记录。因为都是多方,所以两方都是使用 @ManyToMany 注解来标识,但是一般也会指定一个关系维护方,一般维护方会设置级联关系,加载方式是瞬时加载,@JoinTable 也是加在维护方的,而被维护方一般都是懒加载。
@JoinTable
该注解用于设置多对多关系的中间表,一般是关系维护方才会加该注解。
属性:
name:关系表的名字。
joinColumns:该属性用于指定自己这一方中在关系表的列。该属性的值是 @JoinColumn 注解数组,@JoinColumn 的 name 表示此方在关系表中的列名,referencedColumnName 表示关系表中外键的参考字段。
inverseJoinColumns:该属性指定对方在关系表的列,该属性的值跟 joinColumns 的值一样。
四 小结
至此,数据持久化常用的注解基本就是这些,当然有了这些注解还不够,这只是利用注解定义了对应的表,还没有进行 CRUD 的操作,后面就会接着学习如何使用 JPA 来进行数据的 CRUD。