二八佳人体似酥,腰间仗剑斩愚夫。虽然不见人头落,暗里教君骨髓枯。

上一章简单介绍了SpringBoot整合JdbcTemplate(五),如果没有看过,​​请观看上一章​​

日常生活中,我们并不使用 JdbcTemplate, 而是使用 JPA和MyBatis,MyBatis-Plus. 这一章节,我们讲解一下, JPA的相关操作。

一. SpringBoot 整合 JPA前期准备

按照老蝴蝶以前讲解的方式,采用Maven 构建SpringBoot项目。

一.一 pom.xml 添加依赖

<!--引入MySql的驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--引入springboot与jpa整合的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

一.二 application.yml 添加JPA的配置

# 引入 数据库的相关配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: abc123
# JPA的相关配置
jpa:
# 设置数据库平台
database-platform: org.hibernate.dialect.MySQLDialect
# 设置数据库
database: mysql
# 是否展示SQL语句
show-sql: true
hibernate:
# 持久化规则是 update
ddl-auto: update
naming:
# 物理命名策略类的全限定名称
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

一.三 创建 User 表

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(15) DEFAULT NULL,
`sex` varchar(20) DEFAULT NULL,
`age` int(6) DEFAULT NULL,
`description` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;

二. 整合JPA应用

SpringBoot整合JPA时,需要创建相应的 实体类,工厂接口。

工厂接口有 Crud接口,有 PagingAndSorting,有 Jpa接口,也有 Specification 动态查询接口。

每一种接口,都有其特殊的功能。

二.一 创建 POJO 类和业务类

在 pojo 包下,创建 User.java 类

@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "user")
@Entity
public class User implements Serializable {
/**
* @param id id编号
* @param name 姓名
* @param sex 性别
* @param age 年龄
* @param description 描述
*/
@Id
//指定生成策略
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
@Column(name = "name")
private String name;
@Column(name="sex")
private String sex;
@Column(name="age")
private Integer age;
@Column(name="description")
private String description;
}

在 service包下,创建相应的接口和实现类

public interface UserService {

}
@Service
public class UserServiceImpl implements UserService {

}

二.二 Crud 工厂接口

二.二.一 接口的相关定义

org.springframework.data.repository.CrudRepository

@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {

}

方法签名

方法解释

<S extends T> S save(S entity);

插入或者更新一个对象

<S extends T> Iterable<S> saveAll(Iterable<S> entities);

插入或者更新多个对象,批量操作

Optional findById(ID id);

根据id 查询对象

boolean existsById(ID id);

是否存在此id的记录

Iterable findAll();

查询所有的记录

Iterable findAllById(Iterable ids);

根据id集合查询相关的记录

long count();

统计数目

void deleteById(ID id);

根据id进行删除

void delete(T entity);

根据实体对象进行删除

void deleteAll(Iterable<? extends T> entities);

根据实体对象集合进行删除,批量操作

void deleteAll();

删除所有的记录

二.二.二. Crud 接口实现

在 repository 包下,创建 UserCrudRepository 接口。

public interface UserCrudRepository extends CrudRepository<User,Integer> {

}

二.二.三 Crud 测试

在 test测试目录下,创建对应的测试类和测试方法,进行测试。

@SpringBootTest
@Log4j2
public class CrudRepositoryTests {
@Autowired
private UserService userService;
}

二.二.三.一 插入 save 方法

测试方法:

@Test
public void addTest(){
//1. 构建对象
User user=new User();
user.setName("欢欢");
user.setAge(22);
user.setSex("女");
user.setDescription("一个非常可爱的女孩纸");
//2. 添加方法
userService.addUser(user);
log.info("添加成功,{}",user);
}

省略接口方法,其对应的实现方法

添加Crud接口注入

@Autowired
private UserCrudRepository userCrudRepository;
@Override
public void addUser(User user) {
userCrudRepository.save(user);
}

控制台打印输出:

SpringBoot整合JPA(六)_整合Jpa

二.二.三.二 更新 save 方法

测试方法

@Test
public void updateTest(){
//1. 构建对象
User user=new User();
user.setId(1); //id不存在,会添加
user.setName("欢欢");
user.setDescription("岳泽霖最好的朋友");
//2. 修改方法
userService.updateUser(user);
log.info("修改成功,{}",user);
}

接口实现方法

@Override
public void updateUser(User user) {
userCrudRepository.save(user);
}

控制台打印输出

SpringBoot整合JPA(六)_Specification_02

先查询,发现有,就更新,如果没有的话,就插入。

二.二.三.三 删除 delete 方法

测试方法

@Test
public void deleteTest(){
userService.deleteUser(1);
}

接口实现方法

@Override
public void deleteUser(Integer id) {
userCrudRepository.deleteById(id);
}

SpringBoot整合JPA(六)_Specification_03

数据库里面,没有此条数据了。

二.二.三.四 批量更新数据 saveAll

重新执行一下, save() 方法,插入一条数据, id=2.

测试方法:

@Test
public void batchAddTest(){
//1. 构建对象
User user=new User();
user.setName("小欢欢");
user.setAge(22);
user.setSex("女");
user.setDescription("一个小坏蛋");

User user1=new User();
user1.setName("小泽霖");
user1.setAge(25);
user1.setSex("男");
user1.setDescription("一个大坏蛋");

//这是修改的操作,id=2已经存在这条记录了。
User user2=new User();
user2.setName("岳泽霖");
user2.setId(2);
user2.setAge(25);
user2.setSex("男性");
user2.setDescription("一个快乐的程序员");

//2. 放置到集合里面
List<User> userList=new ArrayList<>();
userList.add(user);
userList.add(user1);
userList.add(user2);
userService.batchAddUser(userList);
}

接口实现方法:

@Override
public void batchAddUser(List<User> userList) {
userCrudRepository.saveAll(userList);
}

SpringBoot整合JPA(六)_Jpa动态查询_04

会插入前两条,更新第三条记录。

二.二.三.五 根据id 进行查询 findById

@Test
public void findByIdTest(){
User user=userService.findById(3);
log.info(user);
}

接口实现方法

@Override
public User findById(Integer id) {
Optional<User> optional= userCrudRepository.findById(id);
if(optional.isPresent()){
return optional.get();
}
return null;
}

SpringBoot整合JPA(六)_Specification_05

二.二.三.六 查询所有的数据 findAll

@Test
public void findAllTest(){
List<User> userList=userService.findAll();
userList.forEach(n->log.info(n));
}

接口实现方法

@Override
public List<User> findAll() {
Iterable<User> iterator= userCrudRepository.findAll();
List<User> userList=new ArrayList<>();
//将 Iterable 转换成 list
iterator.forEach(n->userList.add(n));
return userList;

}

SpringBoot整合JPA(六)_Crud的Jpa使用_06

二.二.三.七 根据id集合进行查询 findAllById

@Test
public void findByIdsTest(){
List<Integer> ids= Arrays.asList(3,4,6);
List<User> userList=userService.findAllByIds(ids);
userList.forEach(n->log.info(n));
}

接口实现方法

@Override
public List<User> findAllByIds(List<Integer> ids) {
Iterable<User> iterator= userCrudRepository.findAllById(ids);
List<User> userList=new ArrayList<>();
iterator.forEach(n->userList.add(n));
return userList;
}

SpringBoot整合JPA(六)_spring_07

二.二.三.八 查询数目 count

@Test
public void countTest(){
Long count=userService.count();
log.info("总数目{}",count);
}

接口实现方法

@Override
public Long count() {
return userCrudRepository.count();
}

SpringBoot整合JPA(六)_Jpa动态查询_08

这就是 Crud仓库接口的基本用法。

二.三 PagingAndSorting 工厂接口

二.三.一 接口的相关定义

org.springframework.data.repository.PagingAndSortingRepository

分页和排序的接口,继承了 Crud的接口。

@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {

Iterable<T> findAll(Sort sort);

Page<T> findAll(Pageable pageable);
}

方法签名

方法解释

Iterable findAll(Sort sort);

排序

Page findAll(Pageable pageable);

分页

二.三.二 PagingAndSorting 接口实现

在 repository 包下,创建 UserPagingAndSortingRepository 接口

public interface UserPagingAndSortingRepository extends PagingAndSortingRepository<User,Integer> {

}

二.三.三 PagingAndSorting 测试

在 test目录下,创建 PagingAndSortingRepositoryTests 测试类和测试方法。

多添加几条数据,好便于分页和排序。

SpringBoot整合JPA(六)_Jpa动态查询_09

二.三.三.一 排序,按照性别和年龄

@Autowired
private UserPagingAndSortingRepository userPagingAndSortingRepository;

@Test
public void sortTest(){
List<User> userList=userService.findAllOrderBySexAndAge();
userList.forEach(n->log.info(n));
}

业务接口方法

@Override
public List<User> findAllOrderBySexAndAge() {
Sort.Order sort1= Sort.Order.desc("sex");
Sort.Order sort2 = Sort.Order.asc("age");
Sort sort=Sort.by(sort1,sort2);
Iterable<User> userIterable=userPagingAndSortingRepository.findAll(sort);
List<User> userList=new ArrayList<>();
userIterable.forEach(n->userList.add(n));
return userList;
}

SpringBoot整合JPA(六)_spring_10

二.三.三.二 排序并分页

@Test
public void pageTest(){
Page<User> page=userService.pageAll();
log.info("总页数:{}",page.getTotalPages());
log.info("总的数目:{}",page.getTotalElements());
log.info("当前页数:{}",page.getNumber()+1);
log.info("当前页的数目:{}",page.getNumberOfElements());
List<User> userList= page.getContent();
userList.forEach(n->log.info(n));
}

业务接口方法

@Override
public Page<User> pageAll() {
Sort sort=Sort.by(Sort.Direction.DESC,"id");
// 默认从0开始的
Pageable pageable= PageRequest.of(2-1, 4, sort);
Page<User> userPage=userPagingAndSortingRepository.findAll(pageable);
return userPage;
}

SpringBoot整合JPA(六)_Specification_11

这就是 PageAndSorting仓库接口的基本用法。

二.四 JpaRepository 工厂接口

二.四.一接口的相关定义

@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {

}

继承了分页,还有一个查询的接口。

方法签名

方法解释

List findAll();

查询所有,返回对象集合

List findAll(Sort sort);

查询所有,可排序,返回对象集合

List findAllById(Iterable ids);

根据id集合,查询所有的记录,返回对象集合

<S extends T> List*lt;S> saveAll(Iterable<S> entities);

批量保存数据

void flush();

刷新缓存

S saveAndFlush(S entity);

保存并刷新

void deleteInBatch(Iterable entities);

根据对象集合

void deleteAllInBatch();

批量删除所有

T getOne(ID id);

根据id查询当前对象,如果没有的话,返回null

<S extends T> List<S> findAll(Example<S> example);

根据条件,查询数据,返回对象集合

<S extends T> List<S> findAll(Example<S> example, Sort sort);

根据条件,支持排序,查询数据,返回对象集合

二.四.二 Jpa 接口实现

在repository包下,创建 UserJpaRepository 接口。 通常使用的是这个Jpa的接口,会在这接口内添加相应的接口定义。

当接口的方法名称定义符合相应的规范的时候,不用写相应的实现,Jpa会帮我们自动匹配生成相应的sql语句。

public interface UserJpaRepository extends JpaRepository<User,Integer> {

}

SpringBoot整合JPA(六)_Crud的Jpa使用_12

SpringBoot整合JPA(六)_Jpa动态查询_13

二.四.三 JPA测试

在test目录下,创建JpaRepositoryTests 进行测试。

二.四.三.一 查询全部

@Test
public void findAllTest(){
List<User> userList=userService.jpaFindAll();
userList.forEach(n->log.info(n));
}

业务方法

@Autowired
private UserJpaRepository userJpaRepository;

@Override
public List<User> jpaFindAll() {
return userJpaRepository.findAll();
}

不用往 UserJpaRepository 添加方法。

SpringBoot整合JPA(六)_spring_14

二.四.三.二 Example 查询 (通常不用)

测试方法

@Test
public void findByExampleTest(){
User user=new User();
user.setName("泽霖");
user.setAge(25);
user.setSex("男");
//1.创建匹配器
ExampleMatcher exampleMatcher=ExampleMatcher.matching()
.withMatcher("sex",matcher -> matcher.contains())
.withMatcher("age",matcher -> matcher.exact())
.withMatcher("name",matcher ->matcher.contains());
//2. 生成Example 对象
Example example=Example.of(user,exampleMatcher);
//3. 进行查询
List<User>userList=userService.findByExample(example);
userList.forEach(n->log.info(n));
}

业务实现接口

@Override
public List<User> findByExample(Example example) {
return userJpaRepository.findAll(example);
}

不用往 UserJpaRepository 添加方法。

SpringBoot整合JPA(六)_整合Jpa_15

二.四.三.三 根据名称进行查询

@Test
public void findByNameTest(){
List<User> userList=userService.findByName("小欢欢");
userList.forEach(n->log.info(n));
}

业务方法

@Override
public List<User> findByName(String name) {
return userJpaRepository.findByName(name);
}

需要往 UserJpaRepository 里面添加方法,需要符合一定的规则

List<User> findByName(String name);

SpringBoot整合JPA(六)_spring_16

二.四.三.四 根据性别和年龄

@Test
public void findBySexAndAgeTest(){
List<User> userList=userService.findBySexAndAge("男",25);
userList.forEach(n->log.info(n));
}

业务方法

@Override
public List<User> findBySexAndAge(String sex, Integer age) {
return userJpaRepository.findBySexAndAge(sex,age);
}

需要往 UserJpaRepository 里面添加方法,需要符合一定的规则

List<User> findBySexAndAge(String sex, Integer age);

SpringBoot整合JPA(六)_Jpa动态查询_17

二.四.三.五 根据性别查询,年龄排序

@Test
public void findAllOrderByTest(){
List<User> userList=userService.findBySexOrderByAge("女");
userList.forEach(n->log.info(n));
}

业务方法

@Override
public List<User> findBySexOrderByAge(String sex) {
return userJpaRepository.findBySexOrderByAgeDesc(sex);
}

需要往 UserJpaRepository 里面添加方法,需要符合一定的规则

List<User> findBySexOrderByAgeDesc(String sex);

SpringBoot整合JPA(六)_Jpa动态查询_18

二.四.三.六 使用原始SQL进行查询部分字段

@Test
public void findQueryNameTest(){
List<Map<String,Object>> userMapList=userService.findQueryByName("小欢欢");
for(Map<String,Object> map:userMapList){
log.info("id是:{},name是{}",map.get("id"),map.get("name"));
}
}

业务方法

@Override
public List<Map<String,Object>> findQueryByName(String name) {
return userJpaRepository.findQueryByName(name);
}

需要往 UserJpaRepository 里面添加方法,需要符合一定的规则

@Query(value="select id as id,name as name from user where name=:name",nativeQuery = true)
List<Map<String,Object>> findQueryByName(@Param("name") String name);

SpringBoot整合JPA(六)_Jpa动态查询_19

二.四.三.七 使用原始SQL进行查询全部字段

@Test
public void findQueryNameTest(){
List<Map<String,Object>> userMapList=userService.findQueryByName("小欢欢");
for(Map<String,Object> map:userMapList){
log.info("id是:{},name是{}",map.get("id"),map.get("name"));
}
}

业务方法

@Override
public List<User> jpaFindAllSql(String name) {
return userJpaRepository.findAllSql(name);
}

需要往 UserJpaRepository 里面添加方法,需要符合一定的规则

@Query(value="select * from user where name=:name",nativeQuery = true)
List<User> findAllSql(@Param("name")String name);

SpringBoot整合JPA(六)_整合Jpa_20

这就是 jpa的一些基本的用法。

二.五 JpaSpecificationExecutor 动态查询接口

二.五.一 接口的相关定义

public interface JpaSpecificationExecutor<T> {

}

方法签名

方法解释

Optional findOne(@Nullable Specification spec);

根据条件,查询最多只有一条记录

List findAll(@Nullable Specification spec);

根据条件,查询多条记录

Page findAll(@Nullable Specification spec, Pageable pageable);

根据条件,分页查询,可包含排序

List findAll(@Nullable Specification spec, Sort sort);

根据条件,排序查询

long count(@Nullable Specification spec);

查询数目

二.五.二. JpaSpecificationExecutor实现

通常都是与 JpaRepository 一起使用的。

public interface UserSpecificationRepository extends JpaRepository<User, Integer>,
JpaSpecificationExecutor<User>{

}

二.五.三 Specification 动态查询测试

@Test
public void nameAndSexAndDescTest(){
User user=new User();
user.setName("小欢欢1");
user.setSex("女");
user.setAge(27);
user.setDescription("小坏蛋");
List<User> userList=userService.findByNameSexAndDesc(user);
userList.forEach(n->log.info(n));
}

业务接口方法

@Override
public List<User> findByNameSexAndDesc(User user) {
//1. 根据条件创建 Specification 对象信息
Specification<User> specification=new Specification<User>(){
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
//1. 用于接收封装的查询对象
List<Predicate> predicateList = new ArrayList<>();
if(user!=null){
//1.如果name 不为空的话,对name 进行精确匹配
if(!StringUtils.isEmpty(user.getName())){
Predicate namePredicate = criteriaBuilder.equal(root.get("name"), user.getName());
predicateList.add(namePredicate);
}
//2.如果sex 不为空的话,也是精确匹配
if(!StringUtils.isEmpty(user.getSex())){
Predicate sexPredicate=criteriaBuilder.equal(root.get("sex"),user.getSex());
predicateList.add(sexPredicate);
}
//3.如果age不为空的话,就是 < 匹配
if(!StringUtils.isEmpty(user.getAge())){
Predicate agePreDicate=criteriaBuilder.lt(root.get("age"),user.getAge());
predicateList.add(agePreDicate);
}
//4. 如果description 不为空的话,进行模糊匹配
if(!StringUtils.isEmpty(user.getDescription())){
Predicate descPredicate=criteriaBuilder.like(root.get("description"),"%"+user.getDescription()
+"%");
predicateList.add(descPredicate);
}
}
return criteriaBuilder.and(predicateList.toArray(
new Predicate[predicateList.size()]
));

}
};
//传入条件,也可以传入分页信息。这儿就不举例分页了。
return userSpecificationRepository.findAll(specification);

}

如果将 user条件的属性全部去掉,是一种sql查询,sex去掉,又是一种sql查询,会根据属性的不同,动态的处理。

SpringBoot整合JPA(六)_Specification_21

动态查询,也可以传入分页和排序的相关信息。
这个非常重要,需要重点掌握一下。

本章节的代码放置在 github 上:

​https://github.com/yuejianli/springboot/tree/develop/Jpa​

谢谢您的观看,如果喜欢,请关注我,再次感谢 !!!