f10@t's blog

Spring持久层技术-Sping Data JPA使用及总结(一)

字数统计: 3.1k阅读时长: 14 min
2021/01/23

JAP(Java Persistence API)即Java持久技术API,用于进行数据库的操作,通过注解的方式极大的简化了对数据库的操作,实习的时候有用到过,这篇记录总结一下JPA的使用。

什么是Spring Data JPA

什么是JPA?

​ JPA意为Java Persistence API,即Java持久层API,一定要理解什么叫持久,如果你直接翻译Persistence为持久的话,确实不是很好理解。

Persistence我理解在这里的意思是指将Java简单对象(POJO, Plain Ordinary Java Object)储存到数据库中,提供持续的操作能力,如增删改查。所以JPA是用来把运行期的实体对象持久化到数据库中去的。具体来说JPA包括以下3个内容:

  1. 一套JPA标准:具体是在javax.persistence包下,有一系列可以用来进行CRUD操作的API,而不需要我们去写SQL语句。
  2. 面向对象的查询语言:Java Persistence Query Language (PQL),通过面向对象而非面向数据库的查询语言可以避免程序的SQL耦合。
  3. ORM(Object/relational metadata)元数据映射:元数据是用来描述对象和表之间的映射关系的,JPA支持XML和JDK 5.0两种元数据形式,ORM框架将会根据这个映射来把对象持久化到数据库表中。

常见ORM框架

​ 常见的ORM(Object Relation Mapping, 对象关系映射)框架基本就是MyBatis、Hibernate还有Spring Data JPA了,MyBatis就不说了,反正很好用。Hibernate上手会难一点,它实际上是对JDBC的轻量级封装,具有自己的HQL查询语言,数据库移植性很好,且符合JPA规范。而Spring Data JPA本质上是对JPA规范的再次封装抽象,其底层使用的是Hibernate的JPA实现,具有自己的JPQL(Java Persistence Query Language)查询语言,相比Hibernate,它的上手难度会低一些,开发效率也会高一些。

Spring Data JPA的主要类及结构图

​ 在Spring Data JPA中常用到以下的接口和类,粗体为经常使用到的。

​ 接口:

  • repository(org.springframework.data.repository)

  • CrudRepository(org.springframework.data.repository)

  • PagingAndSortingRepository(org.springframework.data.repository)

  • QueryByExampleExecutor(org.springframework.data.repository.query)

  • JpaRepository(org.springframework.data.jpa.repository)

  • JpaSpecificationExecutor(org.springframework.data.jpa.repository)

  • QueryDslPredicateExecutor(org.springframework.data.querydsl)

    实现类:

  • SimpleJpaRepository(org.springframework.data.jpa.repository.support)

  • QueryDslJpaRepository(org.springframework.data.jpa.repository.support)(已弃用)

    真实的底层封装类,这两个类需要好好了解:

  • EntityManager(javax.persistence)

  • EntityManagerImpl(org.hibernate.jpa.internal

UML图如下所示:

一个Spring Data JPA的小Demo

准备工作

​ 下面简单的写一点代码展示一下如何使用JPA来对对象进行持久化、CRUD等,为了方便期间,这里使用了JpaRepository接口来进行数据操作。

​ maven依赖如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>lzw.xidian</groupId>
<artifactId>JPADemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>JPADemo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

</project>

​ 首先我们定义一个People类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package lzw.xidian.jpademo.entity;

import lombok.AllArgsConstructor;
import lombok.Data;

/**
* @author lzwgiter
* @since 2021/05/05
*/
@Data
@AllArgsConstructor
public class People {
/**
* 名字
*/
private String name;

/**
* 年龄
*/
private int age;

/**
* 邮箱地址
*/
private String emailAddress;

@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
", emailAddress='" + emailAddress + '\'' +
'}';
}
}

​ 为了让这个POJO对象进行持久化,我们就需要对其进行映射关系的注解说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package lzw.xidian.jpademo.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;

/**
* @author lzwgiter
* @since 2021/05/05
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity // 使用@Entity注解来表示这是一个需要持久化的类
public class People {
/**
* 名字
*/
@Id // 主键使用@Id来进行标注
private String name;

/**
* 年龄
*/
@Column // 普通行使用@Column来标注
private int age;

/**
* 邮箱地址
*/
@Column
private String emailAddress;

@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
", emailAddress='" + emailAddress + '\'' +
'}';
}
}

​ 这样这个POJO就可以被持久化了,上面只是很简单的使用,其实上述的注解中包含了这个例子里没有用到的有用的属性,比如@Entity注解可以定义表名,@Column注解可以定义字段的类型、名称等等。这里简单示范,我就只使用name作为了主键,实际上这样是不稳妥的(存在姓名相同的人)。

​ 然后我们在Service层定义一个People的服务接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package lzw.xidian.jpademo.service;

import lzw.xidian.jpademo.entity.People;

/**
* @author lzwgiter
* @since 2021/05/05
*/
public interface PeopleService {
/**
* 创建一个People
*
* @param name 名字
* @param age 年龄
* @param emailAddress 邮箱地址
* @return 添加成功则返回true
*/
boolean createPeople(String name, int age, String emailAddress);

/**
* 获取一个People的所有信息
*
* @param name 获取对象的姓名
* @return 该用户的所有信息
*/
String requirePeople(String name);

/**
* 删除一个人的信息
*
* @param name 名字
* @return 删除成功则返回true
*/
boolean deletePeople(String name);
}

然后我们实现一下这个接口,顺带一句,Idea这里可以直接快捷键把你写好基本的代码,很方便:

结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package lzw.xidian.jpademo.impl;

import lzw.xidian.jpademo.entity.People;
import lzw.xidian.jpademo.service.PeopleService;
import org.springframework.stereotype.Service;

/**
* People服务类
*
* @author lzwgiter
* @since 2021/05/05
*/
@Service
public class PeopleServiceImpl implements PeopleService {
/**
* 创建一个People
*
* @param name 名字
* @param age 年龄
* @param emailAddress 邮箱地址
* @return {@link People} People实例
*/
@Override
public People createPeople(String name, int age, String emailAddress) {
return null;
}

/**
* 获取一个People的所有信息
*
* @param name 获取对象的姓名
* @return 该用户的所有信息
*/
@Override
public String requirePeople(String name) {
return null;
}

/**
* 删除一个人的信息
*
* @param name 名字
* @return 删除成功则返回true
*/
@Override
public boolean deletePeople(String name) {
return false;
}
}

​ 然后本文的重点来了,我们去编写一下dao层的代码,这里使用JpaRepository,该类泛型中第一个类型为你的POJO对象类型,第二个类型为该POJO在持久化时所使用的主键类型,我们定义一个PeopleDao如下:

1
2
3
4
5
6
7
8
9
10
11
12
package lzw.xidian.jpademo.dao;

import lzw.xidian.jpademo.entity.People;
import org.springframework.data.jpa.repository.JpaRepository;

/**
* @author lzwgiter
* @since 2021/05/05
*/
public interface PeopleDao extends JpaRepository<People, String> {

}

​ 目前这个接口里我们什么内容都没有写,但是实际上这个接口就已经定义好了基本操作的函数了,并且,我们不需要去实现这个PeopleDao接口,我们直接调用这个接口的方法就可以了。后面我会对上一节提到的常用接口类进行一一的解释,这里直接使用。下面我们去编写我们PeopleService的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package lzw.xidian.jpademo.impl;

import lzw.xidian.jpademo.dao.PeopleDao;
import lzw.xidian.jpademo.entity.People;
import lzw.xidian.jpademo.service.PeopleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
* People服务类
*
* @author lzwgiter
* @since 2021/05/05
*/
@Service
public class PeopleServiceImpl implements PeopleService {

/**
* People持久层
*/
PeopleDao peopleDao;

/**
* 使用构造器注入
*/
@Autowired
public PeopleServiceImpl(PeopleDao peopleDao) {
this.peopleDao = peopleDao;
}

/**
* 创建一个People
*
* @param name 名字
* @param age 年龄
* @param emailAddress 邮箱地址
* @return 添加成功则返回true
*/
@Override
public boolean createPeople(String name, int age, String emailAddress) {
if (this.peopleDao.existsById(name)) {
// 当前已有该People存在
return false;
} else {
People newPeople = new People(name, age, emailAddress);
this.peopleDao.save(newPeople);
return true;
}
}

/**
* 获取一个People的所有信息
*
* @param name 获取对象的姓名
* @return 该用户的所有信息
*/
@Override
public String requirePeople(String name) {
if (!this.peopleDao.existsById(name)) {
return "无此记录!";
} else {
return this.peopleDao.getOne(name).toString();
}
}

/**
* 删除一个人的信息
*
* @param name 名字
* @return 删除成功则返回true
*/
@Override
public boolean deletePeople(String name) {
if (!this.peopleDao.existsById(name)) {
return false;
} else {
this.peopleDao.deleteById(name);
return true;
}
}
}

​ 然后我们在Controller层定义一个接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package lzw.xidian.jpademo.controller;

import lzw.xidian.jpademo.service.PeopleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @author lzwgiter
* @since 2021/05/05
*/
@RestController
public class PeopleController {

@Autowired
PeopleService peopleService;

@PostMapping(path = "/create")
String createNewPeople(String name, int age, String emailAddress) {
if (peopleService.createPeople(name, age, emailAddress)) {
return "添加成功";
} else {
return "添加失败!";
}
}

@GetMapping(path = "require")
String requirePeople(String name) {
return peopleService.requirePeople(name);
}

@PostMapping(path = "delete")
String deletePeople(String name) {
if(peopleService.deletePeople(name)) {
return "删除成功";
} else {
return "删除失败!";
}
}
}

​ 到这里主体代码就完成了,我们去写一下程序的配置文件--application.yaml

1
2
3
4
5
6
7
8
9
10
11
12
spring:
datasource:
# 指定数据库驱动以及IP地址、端口、使用的数据库,注意带上timezone以及SSL选项
url: jdbc:mysql://localhost:3306/JPADemo?serverTimezone=UTC&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
# 要使用的账户
username: jpauser
# 账户密码
password: 123456
jpa:
hibernate:
ddl-auto: update

​ 这里指定了我们使用的数据库是JPADemo,数据库软件驱动是Mysql驱动,下面我们新建一个数据库,并为这个数据库新建一个用户:

1
2
3
4
5
// 新建数据库
create database JPADemo;
// 新建demo中的用户,并为该用户赋予数据库JPADemo的操作权限
create user 'jpauser'@'localhost' identified by '123456';
grant all privileges on jpademo.* to 'jpauser'@'localhost';

​ 至此我们就结束了数据库的相关配置了,你可能会好奇数据表定义呢?这就是JPA的强大之处,在上面对People这个实体类进行持久化的时候,我们其实就已经定义好了表结构了,现在只要需要启动程序就可以了,如图,当前我们的数据库中是没有表的:

image-20210507161547866

​ 那我们现在启动程序:

image-20210507162708123 下面的输出均是日志记录,注意其中这几条:

1
2
3
4
5
2021-05-07 16:47:44.380  INFO 12744 --- [main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.4.27.Final
2021-05-07 16:47:44.645 INFO 12744 --- [main] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2021-05-07 16:47:45.375 INFO 12744 --- [main] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect
2021-05-07 16:47:46.513 INFO 12744 --- [main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2021-05-07 16:47:46.532 INFO 12744 --- [main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'

​ 可以看到第一、二条日志报出了Hibernate的版本号,前面说过Spring Data JPA的底层就是Hibernate框架,第三条提示我们使用的连接为Mysql8。下面我们去看数据库,这时候就已经有表了:

image-20210507164959738

​ 而且,这个表的结构完全和我们在实体类People中定义的完全相同。

测试

接口的测试这里我使用Postman来进行,下面是我们本次测试的三个接口:

  1. 创建接口:

image-20210507165413242

​ 返回结果:

image-20210507165640564

  1. 查询接口:

image-20210507165447209

​ 返回结果:

image-20210507165802917

  1. 删除接口

image-20210507165434567

​ 返回结果:

image-20210507165901606

待续

​ Spring Data JPA中涉及的知识还是不少的,与数据库技术脱不了联系,后续我继续补充文章,继续写关于Spring Data JPA的相关技术,如各个数据库中的字段如何在实体中定义、多个表之间的关联、如何在数据库操作接口(如:JpaRepository)中自定义查询语句等等。

参考学习

《Spring Data JPA从入门到精通》 张振华 著

CATALOG
  1. 1. 什么是Spring Data JPA
    1. 1.1. 什么是JPA?
    2. 1.2. 常见ORM框架
  2. 2. Spring Data JPA的主要类及结构图
  3. 3. 一个Spring Data JPA的小Demo
    1. 3.1. 准备工作
    2. 3.2. 测试
  4. 4. 待续
  5. 5. 参考学习