SpringBoot整合JPA(六)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: SpringBoot整合JPA的相关使用,包括Crud,PagingAndSorting,Jpa和Specification的用法。
二八佳人体似酥,腰间仗剑斩愚夫。虽然不见人头落,暗里教君骨髓枯。

上一章简单介绍了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);
    }

控制台打印输出:

image-20210517200731868

二.二.三.二 更新 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);
    }

控制台打印输出

image-20210517200933991

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

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

测试方法

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

接口实现方法

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

image-20210517201136990

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

二.二.三.四 批量更新数据 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);
    }

image-20210517201620156

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

二.二.三.五 根据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;
    }

image-20210517201832665

二.二.三.六 查询所有的数据 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;

    }

image-20210517202000350

二.二.三.七 根据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;
    }

image-20210517202138368

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

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

接口实现方法

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

image-20210517202300945

这就是 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 测试类和测试方法。

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

image-20210517203246175

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

 @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;
    }

image-20210517203519601

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

  @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;
    }

image-20210517203740435

这就是 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> {
    
}

img

img

二.四.三 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 添加方法。

image-20210517205400256

二.四.三.二 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 添加方法。

image-20210517205740509

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

 @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);

image-20210517210006081

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

@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);

image-20210517210146370

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

 @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);

image-20210517210320903

二.四.三.六 使用原始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);

image-20210517210521926

二.四.三.七 使用原始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);

image-20210517210818341

这就是 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查询,会根据属性的不同,动态的处理。

image-20210517211841564

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

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

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

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

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
1月前
|
存储 Java 测试技术
《Spring 测试指南》:JPA、MockMvc 和 @SpringBootTest 详解
Spring 提供了一组测试工具,可以轻松地测试 Spring 应用程序的各个组件,包括控制器、服务、存储库和其他组件。它具有丰富的测试注释、实用程序类和其他功能,以帮助进行单元测试、集成测试等。
67 0
|
8月前
|
缓存 Java Go
解决Spring Data JPA查询存在缓存问题及解决方案
解决Spring Data JPA查询存在缓存问题及解决方案
422 0
|
1月前
|
XML Java 数据库连接
Spring Boot的数据访问之Spring Data JPA以及Hibernate的实战(超详细 附源码)
Spring Boot的数据访问之Spring Data JPA以及Hibernate的实战(超详细 附源码)
55 0
|
7天前
|
SQL Java 数据库
Java一分钟之-Spring Data JPA:简化数据库访问
【6月更文挑战第10天】Spring Data JPA是Spring Data项目的一部分,简化了Java数据库访问。它基于JPA,提供Repository接口,使开发者能通过方法命名约定自动执行SQL,减少代码量。快速上手包括添加相关依赖,配置数据库连接,并定义实体与Repository接口。常见问题涉及主键生成策略、查询方法命名和事务管理。示例展示了分页查询的使用。掌握Spring Data JPA能提升开发效率和代码质量。
28 0
|
1月前
|
Java Spring
Spring Boot利用Spring Data JPA实现排序与分页查询实战(附源码,超详细)
Spring Boot利用Spring Data JPA实现排序与分页查询实战(附源码,超详细)
124 0
|
1月前
|
SQL Java 数据库连接
Springboot框架整合Spring Data JPA操作数据
Spring Data JPA是Spring基于ORM和JPA规范封装的框架,简化了数据库操作,提供增删改查等接口,并可通过方法名自动生成查询。集成到Spring Boot需添加相关依赖并配置数据库连接和JPA设置。基础用法包括定义实体类和Repository接口,通过Repository接口可直接进行数据操作。此外,JPA支持关键字查询,如通过`findByAuthor`自动转换为SQL的`WHERE author=?`查询。
|
1月前
|
Java 数据库 Spring
如何使用Spring Data JPA完成审计功能
如何使用Spring Data JPA完成审计功能
|
1月前
|
Java 数据库连接 API
Spring Boot整合Spring Data JPA进行CRUD和模糊查询
Spring Boot整合Spring Data JPA进行CRUD和模糊查询
46 0
|
1月前
|
Java 数据库 数据安全/隐私保护
使用Spring Boot和JPA实现多数据源的方法
使用Spring Boot和JPA实现多数据源的方法
94 0
|
1月前
|
前端开发 JavaScript Java
【Spring Boot+Vue.js+JPA+Mysql】实现前后端分离的名片系统(附源码 超详细必看 可作为大作业使用)
【Spring Boot+Vue.js+JPA+Mysql】实现前后端分离的名片系统(附源码 超详细必看 可作为大作业使用)
66 0