6.4 SpringData JPA的使用
引言:该文档是参考尚硅谷的关于springboot教学视屏后整理而来。当然后面还加入了一些自己从网上收集整理而来的案例!
一、SpringData JPA初步使用
1. springdata简介
2. springboot整合springdata jpa(这是一个最简单的集成案例)
pom.xml引入架包依赖:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.atguigu</groupId> <artifactId>spring-boot-06-data-jpa</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>spring-boot-06-data-jpa</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.10.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <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-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--lombok插件--> <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> </plugin> </plugins> </build> </project>
application.yml中配置如下:

spring:
datasource:
url: jdbc:mysql://192.168.15.22/jpa
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
jpa:
hibernate:
# 更新或者创建数据表结构
ddl-auto: update
# 控制台显示SQL
show-sql: true
建表sql:

/* Navicat Premium Data Transfer Source Server : 47.98.202.86 Source Server Type : MySQL Source Server Version : 50721 Source Host : 47.98.202.86:3306 Source Schema : springboot Target Server Type : MySQL Target Server Version : 50721 File Encoding : 65001 Date: 09/04/2019 23:51:27 */ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for tbl_user -- ---------------------------- DROP TABLE IF EXISTS `tbl_user`; CREATE TABLE `tbl_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `last_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `email` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of tbl_user -- ---------------------------- INSERT INTO `tbl_user` VALUES (1, '刘亦菲', '123@qq.com'); INSERT INTO `tbl_user` VALUES (2, '唐嫣', '123@qq.com'); INSERT INTO `tbl_user` VALUES (3, '??', 'aa'); SET FOREIGN_KEY_CHECKS = 1;
实体类User:

package com.atguigu.springboot.entity; import lombok.Data; import javax.persistence.*; //使用JPA注解配置映射关系 @Entity //告诉JPA这是一个实体类(和数据表映射的类) @Table(name = "tbl_user") //@Table来指定和哪个数据表对应;如果省略默认表名就是user; @Data public class User { @Id //这是一个主键 @GeneratedValue(strategy = GenerationType.IDENTITY)//自增主键 private Integer id; @Column(name = "last_name",length = 50) //这是和数据表对应的一个列 private String lastName; @Column //省略默认列名就是属性名 private String email; }
数据层接口UserRepository:

package com.atguigu.springboot.repository; import com.atguigu.springboot.entity.User; import org.springframework.data.jpa.repository.JpaRepository; //继承JpaRepository来完成对数据库的操作 public interface UserRepository extends JpaRepository<User,Integer> { }
Controller层UserController:这里省略了service,主要是为了测试集成springdata jpa

package com.atguigu.springboot.controller; import com.atguigu.springboot.entity.User; import com.atguigu.springboot.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController public class UserController { @Autowired UserRepository userRepository; @GetMapping("/user/{id}") public User getUser(@PathVariable("id") Integer id){ User user = userRepository.findOne(id); return user; } @GetMapping("/user") public User insertUser(User user){ User save = userRepository.save(user); return save; } }
测试(这里只是通过get请求简单的测试):
查询用户id=2的员工,访问网址:http://localhost:8080/user/2
返回结果:{"id":2,"lastName":"唐嫣","email":"123@qq.com"}
增加一个用户,访问网址:http://localhost:8080/user?lastName=王菲&email=aa
返回结果为:{"id":3,"lastName":"王菲","email":"aa"}
二、springdata jpa 详解
1. springdata jpa 中的接口
先参考【Spring Data JPA--接口方法】,后期在自己整理!
2. 实现复杂查询 参考:https://www.cnblogs.com/zjfjava/p/8456771.html
(1)基于方法名解析的概念
JpaRepository支持接口规范方法名查询。意思是如果在接口中定义的查询方法符合它的命名规则,就可以不用写实现。
spring-data-jpa会根据方法的名字来自动生成sql语句,我们只需要按照方法定义的规则即可;
例如:findByName(String name): 这个方法表示从数据库中查询Name这个属性等于XXX的所有记录
类似于SQL语句:select * from xxTable where name=xxx这种形式
这段话有两个重点:方法名需要在接口中设定; 必须符合一定的命名规范;
(2)方法名构造方法
find+全局修饰+By+实体的属性名称+限定词 + 连接词+ ...(其它实体属性)+OrderBy+排序属性+排序方向
例如:find Distinct By FirstName IgnoreCase And LastName OrderBy Age Desc(String firstName,String lastName);
findDistinctByFirstNameIgnoreCaseAndLastNameOrderByAgeDesc(String firstName,String lastName)
其中:
Distinct是全局修饰(非必须),
FirstName和LastName是实体的属性名,
And是连接词,
IgnoreCase是限定词,
Age是排序属性,
Desc是排序方向,限定词和连接词统称为“关键词”。
spring-data-jpa规定,在属性后面接关键字.
(3)目前支持的关键词
全局修饰:Distinct,Top,First
排序方向:Asc,Desc
关键字:

And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2 Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2 Is,Equals findByFirstname,findByFirstnameIs,findByFirstnameEquals … where x.firstname = ?1 Between findByStartDateBetween … where x.startDate between ?1 and ?2 LessThan findByAgeLessThan … where x.age < ?1 LessThanEqual findByAgeLessThanEqual … where x.age <= ?1 GreaterThan findByAgeGreaterThan … where x.age > ?1 GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1 After findByStartDateAfter … where x.startDate > ?1 Before findByStartDateBefore … where x.startDate < ?1 IsNull findByAgeIsNull … where x.age is null IsNotNull,NotNull findByAge(Is)NotNull … where x.age not null Like findByFirstnameLike … where x.firstname like ?1 NotLike findByFirstnameNotLike … where x.firstname not like ?1 StartingWith findByFirstnameStartingWith … where x.firstname like ?1 (parameter bound with appended %) EndingWith findByFirstnameEndingWith … where x.firstname like ?1 (parameter bound with prepended %) Containing findByFirstnameContaining … where x.firstname like ?1 (parameter bound wrapped in %) OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc Not findByLastnameNot … where x.lastname <> ?1 In findByAgeIn(Collection<Age> ages) … where x.age in ?1 NotIn findByAgeNotIn(Collection<Age> ages) … where x.age not in ?1 TRUE findByActiveTrue() … where x.active = true FALSE findByActiveFalse() … where x.active = false IgnoreCase findByFirstnameIgnoreCase … where UPPER(x.firstame) = UPPER(?1)
(4)嵌套实体方法命名规则

构词法:主实体中子实体的名称+ _ +子实体的属性名称 例如: List<Person> findByAddress_ZipCode(ZipCode zipCode) 表示查询所有 Address(地址)的zipCode(邮编)为指定值的所有Person(人员) //查询需求: 从数据库中查询电话号码(phone)以指定字符串开始(例如:136)的,并且地址(address)中包含指定字符串(例如:路)的记录; 提取前两条,降序排列 //select * from user where phone like '136%' and address like '%路%' order by phone desc limit 0,2 List<User> findTop2ByPhoneStartingWithAndAddressContainingOrderByPhoneDesc(String phone,String address); List<User> findTop2ByPhoneStartingWithAndAddressContaining(String phone,String address,Sort sort); //分页要用到Pageable接口 Page<User> findByPhoneStartingWithAndAddressContaining(String phone,String address,Pageable pageable);
三、Criteria(条件)查询
转载:SpringDataJpa的Specification查询 Specification动态构建多表查询
相应的接口是JpaSpecificationExecutor,这个接口基本是围绕着Specification接口来定义的, Specification接口(JPA)中只定义了如下一个方法:
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb); 这个方法的参数和返回值都是JPA标准里面定义的对象。
1、Criteria(条件)查询基本概念:
Root接口:代表Criteria查询的根对象,Criteria查询的查询根定义了实体类型,能为将来导航获得想要的结果,它与SQL查询中的FROM子句类似;
(1) Root实例是类型化的,且定义了查询的FROM子句中能够出现的类型;
(2) 查询根实例能通过传入一个实体类型给 AbstractQuery.from方法获得;
(3) Criteria查询,可以有多个查询根;
(4) AbstractQuery是CriteriaQuery 接口的父类,它提供得到查询根的方法;
CriteriaQuery接口:代表一个specific的顶层查询对象,它包含着查询的各个部分,比如:select 、from、where、group by、order by等;
注意:CriteriaQuery对象只对实体类型或嵌入式类型的Criteria查询起作用;
CriteriaBuilder接口:用来构建CritiaQuery的构建器对象Predicate:一个简单或复杂的谓词类型,其实就相当于条件或者是条件组合;
2. Criteria查询基本对象的构建
(1) 通过EntityManager的getCriteriaBuilder或EntityManagerFactory的getCriteriaBuilder方法可以得到CriteriaBuilder对象;(可以构建查询条件对象)
(2) 通过调用CriteriaBuilder的createQuery或createTupleQuery方法可以得到CriteriaQuery对象;(执行查询条件对象)
(3) 通过调用CriteriaQuery的from方法可以获得Root实例过滤条件;
a. 过滤条件会被应用到SQL语句的FROM子句中。在criteria 查询中,查询条件通过Predicate或Expression实例应用到CriteriaQuery对象上;
b. 这些条件使用 CriteriaQuery .where 方法应用到CriteriaQuery 对象上;(查询条件怎么应用到顶层查询对象上)
c. CriteriaBuilder也作为Predicate实例的工厂,通过调用CriteriaBuilder 的条件方( equal,notEqual, gt, ge,lt, le,between,like等)创建Predicate对象。
d. 复合的Predicate 语句可以使用CriteriaBuilder的and, or and not 方法构建。
构建简单的Predicate示例:
Predicate p1=cb.like(root.get(“name”).as(String.class), “%”+uqm.getName()+“%”);
Predicate p2=cb.equal(root.get("uuid").as(Integer.class), uqm.getUuid());
Predicate p3=cb.gt(root.get("age").as(Integer.class), uqm.getAge());
构建组合的Predicate示例:
Predicate p = cb.and(p3,cb.or(p1,p2));
使用实例:
(1)当然也可以形如前面动态拼接查询语句的方式,比如:
/** * 测试条件查询 * @return */ @GetMapping("/user/list") public List<User> getList(){ //假设这是前端传过来的条件 User user = new User(); user.setLastName("fei"); user.setId(1); Specification<User> spec = new Specification<User>() { public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) { //用于暂时存放查询条件的集合 List<Predicate> list = new ArrayList<Predicate>(); //------------------- 查询条件示例 ------------------------- //like示例 if (user.getLastName() != null && user.getLastName().trim().length() > 0){ // Predicate lastNamePredicate = cb.like(root.get("lastName").as(String.class), "%"+user.getLastName()+"%"); Predicate lastNamePredicate = cb.like(root.get("lastName"), '%'+user.getLastName()+'%');//和上面一句等效 list.add(lastNamePredicate); } //equal示例 if(user.getId() >0){ // Predicate idPredicate = cb.equal(root.get("id"), user.getId()); Predicate idPredicate = cb.equal(root.get("id").as(Integer.class), user.getId()); list.add(idPredicate); } //between示例() 以下注释的部分,自己参照修改 // if (birthday != null) { // Predicate birthdayPredicate = cb.between(root.get("birthday"), birthday, new Date()); // predicatesList.add(birthdayPredicate); // } //排序示例(先根据学号升序排序,后根据姓名升序排序) // query.orderBy(cb.asc(root.get("studentNumber")),cb.asc(root.get("name"))); //最终将查询条件拼好然后return Predicate[] p = new Predicate[list.size()]; return cb.and(list.toArray(p)); } }; List<User> users = userRepository.findAll(spec); users.forEach(System.out::println); return users; }
说明:以下是上面sql的打印结果,打印的应该是预编译的sql,至于为啥主键id的值被替换了?以后再做探讨,如果有大神了解,补充上!
Hibernate: select user0_.id as id1_2_, user0_.email as email2_2_, user0_.last_name as last_nam3_2_ from tbl_user user0_ where (user0_.last_name like ?) and user0_.id=1;
User(id=1, lastName=liuyifei, email=123@qq.com);
注意:此处记录下我遇到的坑,由于我只是做简单的动态条件查询测试,所以就没有考虑编码的问题,原来我在数据库中存的是中文的用户名,但是这就导致我在以上测试的时候,
尽快代码没有问题,但是死活查不出结果,我一直在纠结我预编译的那个sql,还以为是我代码或者是yml中jpa配置的有问题,导致花了很长时间,后来我将数据库中的用户 名改为英文的就可以了!(编码问题是所有中国程序员都要考虑的问题)
(2)使用CriteriaQuery来得到最后的Predicate
/** * 测试条件查询 * @return */ @GetMapping("/user/list1") public List<User> getList1(){ User user = new User(); user.setId(1); user.setLastName("fei"); user.setEmail("123@qq.com"); Specification<User> spec = new Specification<User>() { public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) { Predicate p1 = cb.like(root.get("lastName").as(String.class), "%"+user.getLastName()+"%"); Predicate p2 = cb.equal(root.get("email").as(String.class), user.getEmail()); Predicate p3 = cb.gt(root.get("id").as(Integer.class), user.getId()); //把Predicate应用到CriteriaQuery中去,因为还可以给CriteriaQuery添加其他的功能,比如排序、分组啥的 query.where(cb.and(p3,cb.or(p1,p2))); //添加排序的功能 query.orderBy(cb.desc(root.get("id").as(Integer.class))); return query.getRestriction(); } }; List<User> users = userRepository.findAll(spec); users.forEach(System.out::println); return users; // sql: select user0_.id as id1_2_, user0_.email as email2_2_, user0_.last_name as last_nam3_2_ from tbl_user user0_ // where user0_.id>1 and (user0_.last_name like ? or user0_.email=?) order by user0_.id desc // User(id=2, lastName=tanying, email=123@qq.com) }
综上所述,我们可以用Specification的toPredicate方法构建更多更复杂的查询方法。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现