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这个实体类进行持久化的时候,我们其实就已经定义好了表结构了,现在只要需要启动程序就可以了,如图,当前我们的数据库中是没有表的:

那我们现在启动程序:

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

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。下面我们去看数据库,这时候就已经有表了:

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

测试

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

  1. 创建接口:

返回结果:

  1. 查询接口:

返回结果:

  1. 删除接口

返回结果:

待续

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

参考学习

  • 《Spring Data JPA从入门到精通》 张振华 著
Powered By Valine
v1.5.2
CATALOG
  1. 1. 什么是Spring Data JPA
  2. 2. Spring Data JPA的主要类及结构图
  3. 3. 一个Spring Data JPA的小Demo
  4. 4. 待续
  5. 5. 参考学习