JAP(Java Persistence
API)即Java持久技术API,用于进行数据库的操作,通过注解的方式极大的简化了对数据库的操作,实习的时候有用到过,这篇记录总结一下JPA的使用。
/title.jpg)
什么是Spring Data JPA
什么是JPA?
JPA意为Java Persistence API
,即Java持久层API,一定要理解什么叫持久
,如果你直接翻译Persistence为持久的话,确实不是很好理解。
Persistence我理解在这里的意思是指将Java简单对象(POJO,
Plain Ordinary Java
Object)储存到数据库中,提供持续的操作能力,如增删改查。所以JPA是用来把运行期的实体对象持久化到数据库中去的。具体来说JPA包括以下3个内容:
- 一套JPA标准:具体是在
javax.persistence
包下,有一系列可以用来进行CRUD操作的API,而不需要我们去写SQL语句。
- 面向对象的查询语言:Java Persistence Query Language
(PQL),通过面向对象而非面向数据库的查询语言可以避免程序的SQL耦合。
- 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图如下所示:
/类图.png)
一个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/> </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;
@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;
@Data @NoArgsConstructor @AllArgsConstructor @Entity public class People {
@Id private String name;
@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;
public interface PeopleService {
boolean createPeople(String name, int age, String emailAddress);
String requirePeople(String name);
boolean deletePeople(String name); }
|
然后我们实现一下这个接口,顺带一句,Idea这里可以直接快捷键把你写好基本的代码,很方便:
/image-20210505152852620.png)
结果如下:
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;
@Service public class PeopleServiceImpl implements PeopleService {
@Override public People createPeople(String name, int age, String emailAddress) { return null; }
@Override public String requirePeople(String name) { return null; }
@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;
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;
@Service public class PeopleServiceImpl implements PeopleService {
PeopleDao peopleDao;
@Autowired public PeopleServiceImpl(PeopleDao peopleDao) { this.peopleDao = peopleDao; }
@Override public boolean createPeople(String name, int age, String emailAddress) { if (this.peopleDao.existsById(name)) { return false; } else { People newPeople = new People(name, age, emailAddress); this.peopleDao.save(newPeople); return true; } }
@Override public String requirePeople(String name) { if (!this.peopleDao.existsById(name)) { return "无此记录!"; } else { return this.peopleDao.getOne(name).toString(); } }
@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;
@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: 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.png)
那我们现在启动程序:
/image-20210507162708123.png)
下面的输出均是日志记录,注意其中这几条:
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.png)
而且,这个表的结构完全和我们在实体类People
中定义的完全相同。
测试
接口的测试这里我使用Postman来进行,下面是我们本次测试的三个接口:
- 创建接口:
/image-20210507165413242.png)
返回结果:
/image-20210507165640564.png)
- 查询接口:
/image-20210507165447209.png)
返回结果:
/image-20210507165802917.png)
- 删除接口
/image-20210507165434567.png)
返回结果:
/image-20210507165901606.png)
待续
Spring Data
JPA中涉及的知识还是不少的,与数据库技术脱不了联系,后续我继续补充文章,继续写关于Spring
Data
JPA的相关技术,如各个数据库中的字段如何在实体中定义、多个表之间的关联、如何在数据库操作接口(如:JpaRepository)中自定义查询语句等等。
参考学习
- 《Spring Data JPA从入门到精通》 张振华 著