在公司刚刚加入一个新的项目组, 发现新项目组使用的ORM框架我没有系统的使用过(JPA), 于是去网上查找了相关资料发现没有很系统的讲解, 便想要系统的记录一下方便日后复习
因为学习这个框架一切都是由生产实际展开的没有了解底层, 所以更加适合快速上手
默认大家对SpringBoot, 构建工具, lombok, Mysql有所了解以及使用
数据源使用: mysql 8.0
一. Spring Data, JPA, SpringDataJPA(JPA)的简单介绍
- Spring Data: Spring 的一个子项目。用于简化数据库访问,支持NoSQL和关系数据库存储。目标是使数据库操作变得简单便捷.
- JPA: Java Persistence API 叫做java持久化接口规范, 定义了java操作数据库的模式.
- Spring Data JPA: 是Spring Data项目的一部分, 基于JPA规范标准, 提供了一套简洁的API和注解, 用于与关系型数据库进行交互, 使我们可以通过简单的Java对象来表示数据库表, 并且通过继承提供的框架类来执行通用的CRUD操作.
- JPA是Java定义的ORM规范, Hibernate是这种ORM规范的实现之一, SpringData JPA是对JPA这种规范的进一步封装与抽象, 底层依然使用了Hibernate技术实现, 简单来说就是进一步增强了对数据库的操作, 将开发人员从繁重的数据库操作以及SQL中解放出来, 可以专注于业务.
二. SpringBoot 整合 SpringDataJPA
1. 导入相关依赖, 构建项目为SpringBoot项目
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
<relativePath/>
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- jpa 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- lombok 依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
2. 创建测试数据库以及创建测试表结构与信息
此处数据库名为jpa_learn, 测试表名为animal.
3. 编写配置信息
在配置文件application.yaml或application.properties中添加以下信息.
server.port=9000
#mysql驱动
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#mysql的jdbc连接
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/jpa_learn
#用户名
spring.datasource.username=root
#密码
spring.datasource.password=${your.pass}
#jpa配置, 显示执行的sql语句
spring.jpa.show-sql=true
#设置hibernate的方言 MySQL5Dialect
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
4. 创建测试包结构
5. 创建与Mysql表映射的Java实体类
@Table(name = "animal")
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@ToString
public class Animal {
@Id
@Column(name = "id")
private Integer id;
@Column(name = "name")
private String name;
@Column(name = "sex")
private String sex;
@Column(name = "age")
private Integer age;
}
Animal类整体上有两种注解标注的, 一类是lombok提供的, 另一部分是JPA提供的, 分别解释:
@Table(name="") : 标注了当前类是与数据库哪张表映射的.
@Entity : 标注了当前类是与数据库表映射的实体类
@Id : 这个属性在数据库表中是主键
@Column : 当前属性是与数据库中哪个字段对应的
三. Spring Data JPA的简单使用
完成了上述准备工作就可以使用JPA框架来进行最基础的数据库操作了, 此处以查询操作为例演示
JPA提供了多种操作数据库的方式, 接下来注意解析
1. 继承JpaRepository<T, ID>
继承类框架可以直接获得常见的数据库操作, 其中 T 是要操作的数据库表所映射的实体类, ID顾名思义是表主键的映射类型
public interface AnimalDao extends JpaRepository<Animal, Integer> {
}
@SpringBootTest
public class JPATest {
@Autowired
private AnimalDao animalDao;
@Test
public void test01() {
animalDao.findAll().forEach(System.out::println);
}
}

2. 使用JPQL进行查询
JPQL: JPQL是一种强大的面向对象查询语言,专为JPA框架设计。它允许开发者通过面向对象的方式检索实体数据,而无需关心底层数据库的具体实现。与传统SQL不同,JPQL直接操作Java实体类及其属性,而非数据库表和列。这种抽象层使开发者能够编写与数据库无关的查询,增强了应用程序的可移植性。JPQL查询会被JPA提供者转换为针对特定数据库的SQL语句执行,从而实现了对象关系映射(ORM)框架的核心功能.
public interface AnimalDao extends JpaRepository<Animal, Integer> {
@Query(value = "select a from Animal a")
List<Animal> getAllAnimals();
}
如果需要查询部分字段或者我们希望查询出的结果由一个Dto来接收我们可以直接通过构造器来实现.
public interface AnimalDao extends JpaRepository<Animal, Integer> {
@Query(value = "select a from Animal a")
public List<Animal> getAllAnimals();
@Query(value = "select new com.yangyang.jpa.dto.AnimalDto(a.name) from Animal a")
public List<AnimalDto> getAllAnimalDtos();
}
3. 使用原生SQL进行查询
@Query() 注解有一个属性叫做 nativeQuery 默认值为false使用JPQL查询, 如果指定为true证明是一个原生SQL
public interface AnimalDao extends JpaRepository<Animal, Integer> {
@Query(value = "select a from Animal a")
public List<Animal> getAllAnimals();
@Query(value = "select new com.yangyang.jpa.dto.AnimalDto(a.name) from Animal a")
public List<AnimalDto> getAllAnimalDtos();
@Query(value = "select * from animal", nativeQuery = true)
public List<Animal> getAnimalsBySql();
}
4. 根据方法名查询
Spring Data 根据 方法名的结构 解析出查询逻辑,生成对应的数据库查询.
方法名结构:
<动词><By><属性名1><关键词><属性名2><排序>
动词:
前缀 | 说明 |
---|---|
findBy / getBy / readBy | 查询单个或多个实体 |
existsBy | 是否存在,返回 boolean |
countBy | 统计数量 |
deleteBy | 删除实体 |
removeBy | 删除(和 deleteBy 一样) |
queryBy | 同 findBy,可自定义语义 |
条件关键字:
关键字 | 说明 | 示例 |
---|---|---|
And | 且 | findByUsernameAndStatus |
Or | 或 | findByUsernameOrEmail |
Between | 范围 | findByAgeBetween(int min, int max) |
LessThan / GreaterThan | 小于 / 大于 | findByAgeGreaterThan(int age) |
Before / After | 时间前 / 时间后 | findByCreatedAtAfter(Date date) |
IsNull / IsNotNull | 判空 | findByEmailIsNull() |
Like | 模糊匹配(需加 %) | findByUsernameLike("%admin%") |
Containing | 包含(自动加 %) | findByUsernameContaining("adm") |
StartingWith / EndingWith | 前缀 / 后缀匹配 | findByUsernameStartingWith("a") |
In / NotIn | 在集合中 | findByIdIn(List<Long> ids) |
True / False | 布尔判断 | findByEnabledTrue() |
排序规则
语法 | 示例 |
---|---|
OrderBy<Field>Asc | findByStatusOrderByAgeAsc() |
OrderBy<Field>Desc | findByStatusOrderByCreatedAtDesc() |
限制返回条数
语法 | 示例 |
---|---|
findTop1By... | 查询符合条件的第一条记录 |
findFirst3By... | 查询前三条 |
返回类型
返回类型 | 说明 |
---|---|
User | 查询单个结果(若多条结果会报错) |
Optional<User> | 单条结果,防止空指针 |
List<User> | 多条结果 |
Page<User> | 分页结果(需传入 Pageable) |
boolean | existsBy... |
long | countBy... |
测试
public interface AnimalDao extends JpaRepository<Animal, Integer> {
@Query(value = "select a from Animal a")
public List<Animal> getAllAnimals();
@Query(value = "select new com.yangyang.jpa.dto.AnimalDto(a.name) from Animal a")
public List<AnimalDto> getAllAnimalDtos();
@Query(value = "select * from animal", nativeQuery = true)
public List<Animal> getAnimalsBySql();
public Animal findById(int id);
// 根据id, 姓名, 年龄范围来进行查询
Animal findByIdAndNameContainingAndAgeBetween(Integer id, String name, Integer ageAfter, Integer ageBefore);
}
@Test
public void test06() {
System.out.println(animalDao.findByIdAndNameContainingAndAgeBetween(1, "狗", 1, 100));
}
其实简单来说就是首先规定进行的数据操作, 之后规定属性和条件, 之后去定义属性条件之间的拼接方式
四. 动态查询
刚刚说完了简单的数据库查询, 现在来说一下重要的动态查询, 动态查询无非就是构建请求条件和排序规则的过程, 在Mybatis中我们通过动态标签来实现, 在MP中我们构建QueryWrapper来实现
1. Specification
Specification是一个函数式接口, 由这个接口可以定义一个动态查询的条件, 因为是一个函数式接口我们可以通过lambda表达式的方式来构建
首先我们需要将dao层去继承JPASpecificationExecutor<T>接口, 其中定义了多种支持传入Specification的方法, 也就是动态查询
@Test
public void test07() {
AnimalDto animalDto = AnimalDto.builder().name("狗").minAge(1).maxAge(100).build();
Specification<Animal> specification =
(root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
if (animalDto.getName() != null) {
Predicate predicate = criteriaBuilder.like(root.get("name"), "%" + animalDto.getName() + "%");
predicates.add(predicate);
}
if (animalDto.getMinAge() != null) {
Predicate predicate = criteriaBuilder.greaterThanOrEqualTo(root.get("age"), animalDto.getMinAge());
predicates.add(predicate);
}
if (animalDto.getMaxAge() != null) {
Predicate predicate = criteriaBuilder.lessThanOrEqualTo(root.get("age"), animalDto.getMaxAge());
predicates.add(predicate);
}
return query.where(predicates.toArray(new Predicate[0])).getRestriction();
};
List<Animal> all = animalDao.findAll(specification);
all.forEach(System.out::println);
}
2. 公司框架使用的是通过 ...QueryCriteria配合工具类来构建查询语句
请求直接传入...QueryCriteria, 工具类通过反射构建出查询条件Specification直接进行查询
五. 分页查询
介绍两个关键的接口/类, Pageable(接口)主要用于接收前端传来的分页数据, PageRequest(类)主要构建分页数据
PageRequest:
@Test
public void test08() {
PageRequest pageRequest = PageRequest.of(0, 10);
Page<Animal> all = animalDao.findAll(pageRequest);
List<Animal> content = all.getContent();
content.forEach(System.out::println);
System.out.println(all.getTotalElements());
System.out.println(all.getTotalPages());
}
Pageable:
添加web依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
写controller直接接收Pageable作为参数, 在service中直接调用dao方法传入pageable参数查询返回Page其中封装了分类结果
@Service
public class AnimalServiceImpl implements AnimalService {
@Autowired
private AnimalDao animalDao;
@Override
public List<Animal> getAnimalByPage(Pageable pageable) {
Page<Animal> all = animalDao.findAll(pageable);
return all.getContent();
}
}
六. 更加复杂一点的, 结合条件查询的分页查询
@Service
public class AnimalServiceImpl implements AnimalService {
@Autowired
private AnimalDao animalDao;
@Override
public List<Animal> getAnimalByPage(Pageable pageable) {
Page<Animal> all = animalDao.findAll(new Specification<Animal>() {
@Override
public Predicate toPredicate(Root<Animal> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
return criteriaBuilder.like(root.get("name"), "%狗%");
}
}, pageable);
return all.getContent();
}
}