资讯专栏INFORMATION COLUMN

Spring Data JPA ——默认方法使用

melody_lql / 2790人阅读

摘要:暂未验证声明主键。为提供一个默认的构造方法。提供一个不可为的属性的构造方法以防止出错。根据字段查询并排序根据字段查询并排序,默认是顺序。统计对象数量统计对象数量这是调用接口的方法来统计对象数量。

初探 SDJ

先让我们来巩固一下 Java 的基础知识。Java 是面向对象的语言,目的是解决现实生活中的问题,可以通过使用类来包装现实生活中的事物成对象、使用属性来描述对象的特点并使用方法来控制对象的行为。我们在 Java 里一切的操作都是针对对象本身,这也是为什么我们需要 ORM 来操作存储在数据库里面的“对象”。题外话,对于类似于 Redis 这种基于 Key-Value 存储的 NoSQL(非关系型数据库)来说,我们并不能很显然地观察到存储在里面的“对象”,因为这种“对象”不同于关系型数据库里的对象,比如 MySQL。在 MySQL 中,其 Table 就对应 Java 中的类,每一条 Column 对应的就是对象,每一个字段对应的就是属性,当然 MySQL 也有自己的方法,不过不在本文讨论范围内。我们可以很直观地通过 SQL 的查询语句来观察“对象”,但在 NoSQL 中,我们只能通过序列化和反序列化来写和读对象。相信我,您宁愿看汇编也不想看序列化之后的对象……

为了节省时间,关于用什么和怎么来构建基本环境不在此赘述,这里提供 SDJ 的官方指南之一供您参考,只要您能引用 SDJ 的包(org.springframework.data:spring-data-jpa:1.11.6.RELEAS)就行。

SDJ 对于应用来说最应该关注的就是以下几个核心接口(按照从子类到父类的继承顺序):

JpaRepository —— JPA 协议的具体实现的接口。

    package org.springframework.data.jpa.repository;

    public interface JpaRepository extends PagingAndSortingRepository, QueryByExampleExecutor {}

PagingAndSortingRepository ——分页和排序的接口。QueryByExampleExecutor ——范例查询的接口。

    package org.springframework.data.repository;

    public interface PagingAndSortingRepository extends CrudRepository {}
    
    package org.springframework.data.repository.query;

    public interface QueryByExampleExecutor {}

CrudRepository ——通用CRUD操作的接口。

    package org.springframework.data.repository;
    
    public interface CrudRepository extends Repository {}

就如同名称所示,我们可以根据情况继承特定的接口,不同的接口提供不同的功能,如果我们需要分页和排序,就继承 PagingAndSortingRepository 接口。但为了全方位地了解 SDJ,本文使用 JpaRepository 接口,由于其位于继承树的最底端,可以理解成二叉树里面的树叶,所以可以使用包括其父类的所有未被重写方法。当然这样也有一些不影响功能实现的矛盾点,我们一会会见到。

为使用默认的接口方法做准备

如同之前所说,我们需要针对性地通过类来构建一个对象。这里创建一个用户类(推荐使用 Lombok 来简化代码)。

// SDJ 使用 @Entity,NoSQL 使用 @Document。(暂未验证)
@Entity
public class User {
    // 声明主键。
    @Id
    // 声明由程序控制主键生成策略。
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    // 约束、不可为空、长度16。
    @Column(unique = true, nullable = false, length = 16)
    private String username;
    @Column(unique = true, nullable = false)
    private String email;
    // 设定默认值。
    private Double balance = 0.0;
    // 不允许更新。
    @Column(updatable = false)
    private Date createTime = new Date();

    // 为 SDJ 提供一个默认的构造方法。
    public User() {
    }

    // 提供一个不可为 null 的属性的构造方法以防止出错。
    public User(String username, String email) {
        this.username = username;
        this.email = email;
    }

    // 省略一大堆 Getter、Setter 和 ToString 方法。也可以通过 Lombok 插件以注解的方式大幅度简化,各大 IDE 均提供!
}

然后创建一个简单到可能怀疑自己人生接口,继承自 JpaRepository

import org.springframework.data.jpa.repository.JpaRepository;
import top.cciradih.spring.data.jpa.entity.User;

//  泛型指定了能接受的对象类型和其主键类型,主键类型在一些方法里很有用。
public interface UserRepository extends JpaRepository {
}

惊不惊喜?意不意外?我们已经可以使用它了!什么都不用写!

试试接口默认的方法 保存 保存单个对象并返回
        // 保存单个对象并返回。
        User savedUser = userRepository.save(new User("Cciradih", "hidarichaochen@gmail.com"));

这是调用 CrudRepository 接口的 S save(S entity) 方法来保存并返回存储的对象。

保存多个对象并返回
        // 保存多个对象并返回。
        List userList = new ArrayList<>();
        User newUser;
        for (int i = 0; i < 10; i++) {
            newUser = new User("Cciradih" + i, "hidarichaochen@gmail.com" + i);
            userList.add(newUser);
        }
        List savedUserList = userRepository.save(userList);

这是调用 JpaRepository 接口的 List save(Iterable entities) 方法来保存多个对象。

查询 根据主键查询单个对象
        // 根据主键查询单个对象。
        User foundUser = userRepository.findOne(1L);

这是调用 CrudRepository 接口的 T findOne(ID id) 方法来查询单个对象。

查询全部对象
        // 查询全部对象。
        List foundUserList = userRepository.findAll();

这是调用 JpaRepository 接口的 List findAll() 方法来查全部对象。

根据 ID 字段查询并排序
import org.springframework.data.domain.Sort;

        // 根据 id 字段查询并排序,默认是顺序(ASC)。
        List foundASCSortedUserList = userRepository.findAll(new Sort("id"));
        // 根据 id 字段倒序查询(DESC)。
        List foundDESCSortedUserList = userRepository.findAll(new Sort(Sort.Direction.DESC, "id"));

这里涉及到新的对象—— new Sort("id")。从名字可以看得出来是为了排序的,其内部的具体逻辑在本文就不赘述。通过 JpaRepository 接口的 List findAll(Sort sort) 方法来查询并排序。

范例查询
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;

        User user = new User("Cciradih", "hidarichaochen@gmail.com");
        // 1.使用 Example。
        // 创建 Example。
        Example userExample = Example.of(user);
        User foundExampleUser = userRepository.findOne(userExample);
        // 2.使用 ExampleMatcher。
        // 创建 ExampleMatcher。
        ExampleMatcher exampleMatcher = ExampleMatcher.matching()
                // 忽略 id 和 createTime 字段。
                .withIgnorePaths("id", "createTime")
                // 忽略大小写。
                .withIgnoreCase()
                // 忽略为空字段。
                .withIgnoreNullValues();
        // 携带 ExampleMatcher。
        userExample = Example.of(user, exampleMatcher);
        User foundExampleWithExampleMatcherUser = userRepository.findOne(userExample);

这里涉及到 Example 和 ExampleMatcher 的使用。

Example 通过其静态方法 Example of(T probe) 来引入一个 Example 对象。然后就通过 QueryByExampleExecutor 接口的 S findOne(Example example) 方法来查询符合范例的对象。当然这里在通常情况下是查询不到任何对象的,因为 User 有不同的 Id 和某种意义上讲不会相同的 createTime。

ExampleMatcher 通过静态方法 Example of(T probe, ExampleMatcher matcher) 携带一个符合的泛型对象和 ExampleMatcher 对象,这里忽略了 id 和 createTime 字段,所以能够查找到符合条件的对象。

ExampleMatcher 是对 Example 的拓展,这也是为什么我说 SDJ 的控制细腻度依然很高的原因。

关于 ExampleMatcher 更多的过滤方法可以自行参照源码。

分页查询
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;

        // 分页查询,从 0 页开始查询 5 个。
        Page foundUserPage = userRepository.findAll(new PageRequest(0, 5));
        // 分页表。
        List content = foundUserPage.getContent();
        // 总数量。
        long totalElements = foundUserPage.getTotalElements();
        // 总页数。
        long totalPages = foundUserPage.getTotalPages();
        // 分页表大小。
        int size = foundUserPage.getSize();

这里涉及到新的对象—— new PageRequest(0, 5)。从名字可以看得出来是为了分页的,其内部的具体逻辑在本文就不赘述。通过 PagingAndSortingRepository 接口的 Page findAll(Pageable pageable) 方法来查询 Page 对象。Page 包含了所有分页需要的属性,可以通过其方法来获取。

删除 根据主键删除单个对象
        // 根据主键删除单个对象
        userRepository.delete(1L);

这是调用 CrudRepository 接口的 void delete(ID id) 方法来根据主键删除单个对象。

根据对象删除单个对象
        // 根据对象删除单个对象
        User user = new User("Cciradih", "hidarichaochen@gmail.com");
        userRepository.delete(user);

这是调用 CrudRepository 接口的 void delete(T entity) 方法来根据对象删除单个对象。和 Example 的例子一样,也不会删除任何对象,请自行思考。

删除全部
        // 删除全部
        userRepository.deleteAll();

这是调用 CrudRepository 接口的 void deleteAll() 方法来根据对象删除单个对象。

之前提到过的一些不影响功能实现的矛盾点,在这里体现。由于 JpaRepository 间接继承自 CrudRepository 所以我们可以同时使用 void deleteAllInBatch()void deleteAll() 方法,两者的功能是一样的,可以二者取其一。想要避免这样的事,只能在不影响实际业务逻辑的情况下往上继承接口。

删除多个对象
        // 删除多个对象
        List userList = new ArrayList<>();
        User user;
        for (int i = 0; i < 10; i++) {
            user = new User("Cciradih" + i, "hidarichaochen@gmail.com" + i);
            userList.add(user);
        }
        userRepository.delete(userList);

这是调用 CrudRepository 接口的 void delete(Iterable entities) 方法来删除多个对象。和 Example 的例子一样,也不会删除任何对象,请自行思考。

统计对象数量
        // 统计对象数量
        long count = userRepository.count();

这是调用 CrudRepository 接口的 long count() 方法来统计对象数量。

根据主键判断对象是否存在
        // 判断对象是否存在
        boolean exists = userRepository.exists(1L);

这是调用 CrudRepository 接口的 boolean exists(ID id) 方法来根据主键判断对象是否存在。

结语

我尽可能地列举了默认方法,但为了行文方便,其中有些方法是可以配合使用的,比如 JpaRepository 接口的 List findAll(Example example, Sort sort),可以同时传入 Example 和 Sort 对象进行查询,请自行参照源码使用。

我希望您能够通过此文对 SDJ 的默认方法达到“知其然”的程度,在软件开发中,我认为需要从“知其然”到”知其所以然“再到”造其所以然“的地步,但路需要一步一步走。

源码仓库

Learn/Spring Data JPA at master · cciradih/Learn

系列目录

Spring Data JPA 详尽指南

参考

Spring Data JPA - Reference Documentation

我的咖啡馆

如果您对本文有什么建议或者问题,欢迎您来我的咖啡馆坐坐338147322。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70375.html

相关文章

  • Springboot项目搭建(四)整合MySQL数据库(JPA

    摘要:整合数据库一文件添加依赖二配置文件主参数指定指定数据源用户名指定数据源密码指定当使用内嵌数据库时,默认是,否则为是否开启的,默认为参考建议配置属性之三配置实体类主键主键自增四实现单表操作此处泛型为映射类型 springboot整合MySQL数据库(JPA) 一、POM文件添加依赖 org.springframework.boot spring-boot-starte...

    hikui 评论0 收藏0
  • Spring Boot QuickStart (5) - Spring Data JPA

    摘要:关联关系的关联关系定义上,感觉并不是很灵活,姿势也比较难找。如,定义在关联关系上的参数可以设置级联的相关东西。因为序列化会涉及到实体类关联对象的获取,会触发所有的关联关系。 接(4) - Database 系列. Java Persistence API,可以理解就是 Java 一个持久化标准或规范,Spring Data JPA 是对它的实现。并且提供多个 JPA 厂商适配,如 Hi...

    sutaking 评论0 收藏0
  • Spring Boot [组件学习-Spring Data JPA]

    摘要:与的关系是什么是官方提出的持久化规范。它为开发人员提供了一种对象关联映射工具来管理应用中的关系数据。他的出现主要是为了简化现有的持久化开发工作和整合技术,结束现在,,等框架各自为营的局面。定义了在对数据库中的对象处理查询和事务运行时的的。 导读: 在上篇文章中对Spring MVC常用的一些注解做了简要的说明,在这篇文章中主要对Spring Data JPA 做一个简要的说明,并附有一...

    andong777 评论0 收藏0
  • 【从零入门系列-2】Spring Boot 之 数据库实体定义实现

    摘要:文章系列从零入门系列之从零入门系列之程序结构设计说明前言本篇文章开始代码实践,系统设计从底向上展开,因此本篇先介绍如何实现数据库表实体类的设计实现。主键由数据库自动生成主要是自动增长型主键由程序控制。 文章系列 【从零入门系列-0】Sprint Boot 之 Hello World 【从零入门系列-1】Sprint Boot 之 程序结构设计说明 前言 本篇文章开始代码实践,系统...

    nemo 评论0 收藏0
  • 猫头鹰的深夜翻译:为什么要使用Spring Boot?

    摘要:初次使用的人往往会困惑,不知道该使用哪种方法。目前来说,团队推荐使用基于的方法来提供更高的灵活性。配置,从而在应用启动时执行脚本来初始化数据库。目前为止我们没有任何消息需要配置,所以只在文件夹中创建一个空的文件。将配置为,它包含的上下文。 前言 spring是一个用于创建web和企业应用的一个很流行的框架。和别的只关注于一点的框架不同,Spring框架通过投资并组合项目提供了大量的功能...

    Jaden 评论0 收藏0
  • 一起来学SpringBoot | 第六篇:整合SpringDataJpa

    摘要:忽略该字段的映射省略创建数据访问层接口,需要继承,第一个泛型参数是实体对象的名称,第二个是主键类型。 SpringBoot 是为了简化 Spring 应用的创建、运行、调试、部署等一系列问题而诞生的产物,自动装配的特性让我们可以更好的关注业务本身而不是外部的XML配置,我们只需遵循规范,引入相关的依赖就可以轻易的搭建出一个 WEB 工程 上一篇介绍了Spring JdbcTempl...

    Dionysus_go 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<