Spring 加强版 ORM 框架 Spring Data 入门

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析DNS,个人版 1个月
全局流量管理 GTM,标准版 1个月
简介: 概述Spring 中有多种操作数据库的方式,通常来说我们优先选择的是 MyBatis,如果业务比较简单我们还会使用 JdbcTemplate,另外据说国外使用 spring-data-jpa 比较多?

概述


Spring 中有多种操作数据库的方式,通常来说我们优先选择的是 MyBatis,如果业务比较简单我们还会使用 JdbcTemplate,另外据说国外使用 spring-data-jpa 比较多?


最近发现了 Spring 中另一款操作关系型数据库的框架,相对 JdbcTemplate 来说使用上又简化了一些,它也是 Spring Data 大家族中的一员,即 spring-data-jdbc,分享给大家。


由于 Spring Data 内容较多,分为上下两篇介绍,本篇我们先介绍一些 Spring Data 的基础知识,下篇再介绍 spring-data-jdbc。


模块划分


Spring Data 大家族中有一些每个模块都要遵守的规范,这些规范定义在 spring-data-commons 模块中,理解这些规范后,切换到具体的实现模块能很快上手。这些模块之间的关系可以用下面的图来表示。


image.png


核心概念


Spring Data 的实现借鉴了领域驱动设计 DDD 的设计原则,规范中的核心概念是 Repository,它表示一个管理 Domain 的仓库,所有的数据库操作都要经过 Repository。


image.png


Domain 则表示数据库表在 Java 中的映射,每个 Domain 都必须有一个唯一的 ID 标识,可以通过 @Id 注解标识。例如,我们有一个关系型数据库表 user,结构如下:


create table user
(
    id          bigint unsigned auto_increment
        primary key,
    username    varchar(20)  null,
    password    varchar(20)  null
);


可以使用如下的类表示。


@Data
public class User {
    @Id
    private Long id;
    private String username;
    private String password;
}


Repository 作为一个接口,接收具体的 Domain 类型和 Domain 的 ID 类型作为泛型参数。接口定义如下。


public interface Repository<T, ID> {
}


自定义 Repository 接口


Repository

Repository 只是一个标记接口,对于开发者来说需要提供一个 Repository 的子接口,然后再定义一些操作数据库的方法。例如,针对上面的 Domain User 我们可以定义一个这样的 Repository。


public interface UserRepository extends Repository<User, Long> {
    Optional<User> findById(Long id);
}


Spring Data 实现模块会根据特定的语法规则将方法解析为具体的查询方式,例如对于 spring-data-jdbc 模块来说,上面的 findById 可以解析为如下 SQL。

select id,username,password from user where id = ?

如果多个 Domain 相同的操作比较多,我们还可以将方法定义到一个 BaseRepository 中,示例如下。


@NoRepositoryBean
public interface BaseRepository<T, ID> extends Repository<T, ID> {
    Optional<T> findById(ID id);
}
public interface UserRepository extends BaseRepository<User, Long> {    
}


创建 Repository 实例


Spring Data 实现模块会为我们定义的接口创建代理对象并将这个代理对象注册为 bean,为了避免将 BaseRepository 注册为 bean,我们需要为自定义的 BaseRepository 添加注解 @NoRepositoryBean 。


最后,我们还需要使用 @EnableXXXRepository 注解开启接口注册为 bean 的功能,以 spring-data-jdbc 为例,如下。


@EnableJdbcRepositories(basePackages = "com.zzuhkp.repository")
@Configuration
public class JdbcConfig {
}


basePackages 属性指定 repository 的接口位置,默认为注解所在类的包。


CrudRepository

Spring 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<T> findById(ID id);
  boolean existsById(ID id);
  Iterable<T> findAll();
  Iterable<T> findAllById(Iterable<ID> ids);
  long count();
  void deleteById(ID id);
  void delete(T entity);
  void deleteAll(Iterable<? extends T> entities);
  void deleteAll();
}


其中 savesaveAll 方法根据是否为新记录进行添加或更新操作。


PagingAndSortingRepository


如果我们需要进行分页查询或排序,我们还可以继承 PagingAndSortingRepository 接口,这个接口的定义如下。


@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
  Iterable<T> findAll(Sort sort);
  Page<T> findAll(Pageable pageable);
}


Sort 声明方式如下。


Sort sort1 = Sort.by("username").ascending().and(Sort.by("password").descending());
Sort.TypedSort<User> sort = Sort.sort(User.class);
Sort sort2 = sort.by(User::getUsername).ascending().and(sort.by(User::getPassword).descending());


Pageable 声明方式如下。


Pageable page = PageRequest.of(0, 10, sort1);


📢 注意:Pageable 的页码是从 0 开始的。


@RepositoryDefinition

如果我们不想继承 Spring Data 提供的接口,我们还可以在自己的接口上标注 @RepositoryDefinition 注解指定 Domain 类型和 ID 类型。如下所示。


@RepositoryDefinition(domainClass = User.class, idClass = Long.class)
public interface UserRepository {
    Optional<User> findById(Long id);
}


自定义 Repository 接口实现


上面定义的 Repository 接口方法都必须借助 Spring Data 自身的实现,Spring Data 还允许我们定义自己的 Repository 实现。

首先定义一个接口。


package com.zzuhkp.demo.repository;
public interface CustomizedUserRepository {
    int save(User entity);
}


然后定义 Repository 实现。


package com.zzuhkp.demo.repository;
@Component
public class CustomizedUserRepositoryImpl<T> implements CustomizedUserRepository {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Override
    public int save(User entity) {
        String sql = "insert into user(username,password) values(?,?)";
        String username = entity.getUsername();
        String password = entity.getPassword();
        return jdbcTemplate.update(sql, username, password);
    }
}


然后我们的 Repository 继承自定义的 CustomizedUserRepository 接口即可。


public interface UserRepository extends Repository<User, Long>, CustomizedUserRepository {
}

📢 注意:Spring Data 只会在自定义 Repository 接口所在包下面查找名称为 XxxImpl 的实现,如果有多个实现会使用名称为 xxxImpl 的 bean。如果不想使用 Impl 后缀,可以通过 @EnableXXXRepository.repositoryImplementationPostfix 注解属性修改。


@EnableJdbcRepositories(repositoryImplementationPostfix = "Impl")
@Configuration
public class JdbcConfig {
}


自定义 Repository 方法


Spring Data 实现模块有两种方式根据方法确认具体的数据库操作,第一种是解析 Repository 方法名,第二种是解析 Repository 方法上的注解。不同的 Spring Data 实现模块使用的注解可能有所不同,但是对于方法名的解析是类似的。


语法规则

Repository 的方法名可以分为 subject 部分和 predicate 部分,例如 find...by... 中的 find 后面的部分表示要查询的 subject,by 后面的部分表示查询条件 predicate。


find 后还可以跟 distinct、top、first 等词语限制查询结果,by 后面可以紧跟 Domain 类的属性名和一些修饰词进行限定。下面是一些示例,可以很容易看明白。


public interface UserRepository extends PagingAndSortingRepository<User, Long> {
   // 查询第一个满足条件的 User
   User findFirstByUsername(String username);
   // 查询前 10 个满足条件的 User
   List<User> findTop10ByUsernameOrderByIdAsc(String username);
   // 根据用户名查询用户,忽略用户名大小写
   List<User> findDistinctByUsernameIgnoreCase(String username);
}


关键字比较多,不同的实现模块可能支持的关键字有所不同,对于 spring-data-jdbc 来说可以参考 Spring Data Repository 方法关键字 查询支持的关键字。


接入 Spring Data 时, Intellij Idea 还会有代码提示,如果你还在用 eclipse,不妨试试 Idea ?


image.png


特殊参数


PagingAndSortingRepository 接口方法支持 Pageable 和 Sort 作为方法参数,如果不想进行分页或者排序,可以传入 Pageable.unpaged() 或 Sort.unsorted()。


返回类型


Spring Data Repository 支持多种方法的返回值,具体如下。


基本类型及其包装类型。


Domain 具体类型以及 Optional。。


集合/迭代类型:List、Set、Iterable,Stream,除此之外还支持 Spring Data 内置的 Streamable 类型。


异步结果:Future、CompletableFuture、ListenableFuture,需要 @EnableAsync 开启异步,并在方法上添加 @Async 注解。


Spring Data 多模块


有时候,我们可能需要使用多个 Spring Data 的模块,例如操作关系型数据库我们可以使用 spring-data-jpa,操作 MongoDB 数据库我们可以使用 spring-data-mongodb,这个时候必须有一种方式能够区分不同类型的 Repository,具体来说有两种方式。


1. 根据 Respsitory 类型区分


如果自定义的 Repository 实现了某个模块特有的接口,可以根据这个特有的接口创建代理。例如:


public interface UserRepository extends JpaRepository<User, Long> { 
}


2. 根据 Domain 注解区分


不用的 Spring Data 模块在 Domain 类型上使用的注解有所不同,如果使用了某个模块特有的注解,也可以识别出 Repository 的类型。例如:

@Data
@Entity
public class User  {
    @Id
    private Long id;
    private String username;
    private String password;
}


@Entity 是 JPA 特有的注解,因此可以识别 UserRepository 是 spring-data-jpa 模块的 Repository。


3. basePackages 区分


有时候我们定义的 Repository 接口可能没继承某个模块特有的注解,并且 Domain 类上混合使用了多个模块的注解,这个时候怎么办呢?


放大招,在 @EnableXXXRepository 注解的 basePackages 属性中指定 Repository 所在的具体包名,这时候 Spring Data 就可以识别出 Repository 接口是哪个模块下的了。


@EnableJdbcRepositories(basePackages = "com.zzuhkp.repository")

事件发布

Repository 管理的 Domain 又被称为聚合根,聚合根可以发布一些 Domain 事件。


Spring Data 支持事件发布的注解是 @DomainEvents,可以在聚合根的方法上添加这个注解,当调用 save、saveAll、delete、deleteAll 时会调用注解标注的方法。例如:


@Data
public class User {
    private Long id;
    @DomainEvents
    Collection<UserEvent> domainEvents() {
        System.out.println("发布事件");
        return Collections.singleton(new UserEvent().setId(this.getId()));
    }
}


如果发布事件时使用了一些资源,还可以使用 @AfterDomainEventPublication 标注的方法在事件发布后清理一些资源。

@Data
public class User {
    @AfterDomainEventPublication
    void callbackMethod() {
        System.out.println("事件已发布");
    }   
}


怎么接收这些事件呢?直接使用 Spring 提供的事件监听器即可。


@EventListener
public void event(UserEvent event) {
    System.out.println("接收到 UserEvent 事件");
}

感觉这个特性用的应该不多,大家有个印象即可。


Spring Web 支持


Spring Data 提供了对 Spring MVC 的支持,可以通过 @EnableSpringDataWebSupport 注解开启。提供的功能特性如下:


1. Domain 解析

开启 Spring Web 支持后 Spring Data 会注册一个 DomainClassConverter 类型的转换器,将路径变量或请求参数转换为 handler 方法中的 Domain 参数值。

@GetMapping("/user/{id}")
User get(@PathVariable("id") User user) {
    return user;
}


2. Pageable、Sort 解析


开启 Spring Web 支持后 Spring Data 会为类型为 Pageable 和 Sort 的 handler 方法参数分别注册一个参数解析器,具体为 PageableHandlerMethodArgumentResolver 和 SortHandlerMethodArgumentResolver。


@GetMapping("/user/list")
List<User> list(Pageable pageable) {
    return null;
}


客户端传哪个参数才能解析呢?先看一个示例。


page=0&size=10&sort=username,asc&sort=password,desc,ignorecase


page 表示页码,从 0 开始;

size 表示页大小,默认为 10。

sort 用于排序,格式为 property(,asc|desc)(,ignorecase), property 为属性名,asc 表示升序, desc 表示降序,ignorecase 表示忽略大小写。


总结

Spring Data 功能特性 = 通用功能 + 各模块独有功能,本篇主要总结了 Spring Data 的通用功能特性部分,这些基础知识适用于 Spring Data 的各模块,了解这些内容后,再学习具体模块能很快上手了。后面会对具体模块进行介绍。


目录
相关文章
|
4天前
|
XML Java 测试技术
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
这篇文章介绍了Spring5框架的三个新特性:支持@Nullable注解以明确方法返回、参数和属性值可以为空;引入函数式风格的GenericApplicationContext进行对象注册和管理;以及如何整合JUnit5进行单元测试,同时讨论了JUnit4与JUnit5的整合方法,并提出了关于配置文件加载的疑问。
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
|
3天前
|
运维 Java Nacos
Spring Cloud应用框架:Nacos作为服务注册中心和配置中心
Spring Cloud应用框架:Nacos作为服务注册中心和配置中心
|
4天前
|
XML Java Maven
Spring5入门到实战------16、Spring5新功能 --整合日志框架(Log4j2)
这篇文章是Spring5框架的入门到实战教程,介绍了Spring5的新功能——整合日志框架Log4j2,包括Spring5对日志框架的通用封装、如何在项目中引入Log4j2、编写Log4j2的XML配置文件,并通过测试类展示了如何使用Log4j2进行日志记录。
Spring5入门到实战------16、Spring5新功能 --整合日志框架(Log4j2)
|
4天前
|
XML Java 数据库
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
这篇文章是Spring5框架的实战教程,详细介绍了事务的概念、ACID特性、事务操作的场景,并通过实际的银行转账示例,演示了Spring框架中声明式事务管理的实现,包括使用注解和XML配置两种方式,以及如何配置事务参数来控制事务的行为。
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
|
4天前
|
XML 数据库 数据格式
Spring5入门到实战------14、完全注解开发形式 ----JdbcTemplate操作数据库(增删改查、批量增删改)。具体代码+讲解 【终结篇】
这篇文章是Spring5框架的实战教程的终结篇,介绍了如何使用注解而非XML配置文件来实现JdbcTemplate的数据库操作,包括增删改查和批量操作,通过创建配置类来注入数据库连接池和JdbcTemplate对象,并展示了完全注解开发形式的项目结构和代码实现。
Spring5入门到实战------14、完全注解开发形式 ----JdbcTemplate操作数据库(增删改查、批量增删改)。具体代码+讲解 【终结篇】
|
Java 数据库连接 开发者
《Spring 5 官方文档》16.ORM和数据访问(一)
16.1介绍一下Spring中的ORM Spring框架在实现资源管理、数据访问对象(DAO)层,和事务策略等方面,支持对Java持久化API(JPA)以及原生Hibernate的集成。以Hibernate举例来说,Spring有非常赞的IoC功能,可以解决许多典型的Hibernate配置和集成问题。
2380 0
|
21天前
|
Java 测试技术 数据库
Spring Boot中的项目属性配置
本节课主要讲解了 Spring Boot 中如何在业务代码中读取相关配置,包括单一配置和多个配置项,在微服务中,这种情况非常常见,往往会有很多其他微服务需要调用,所以封装一个配置类来接收这些配置是个很好的处理方式。除此之外,例如数据库相关的连接参数等等,也可以放到一个配置类中,其他遇到类似的场景,都可以这么处理。最后介绍了开发环境和生产环境配置的快速切换方式,省去了项目部署时,诸多配置信息的修改。
|
1月前
|
Java 应用服务中间件 开发者
Java面试题:解释Spring Boot的优势及其自动配置原理
Java面试题:解释Spring Boot的优势及其自动配置原理
87 0
|
19天前
|
XML Java 数据库连接
Spring Boot集成MyBatis
主要系统的讲解了 Spring Boot 集成 MyBatis 的过程,分为基于 xml 形式和基于注解的形式来讲解,通过实际配置手把手讲解了 Spring Boot 中 MyBatis 的使用方式,并针对注解方式,讲解了常见的问题已经解决方式,有很强的实战意义。在实际项目中,建议根据实际情况来确定使用哪种方式,一般 xml 和注解都在用。
|
22天前
|
Java Spring 容器
Spring Boot 启动源码解析结合Spring Bean生命周期分析
Spring Boot 启动源码解析结合Spring Bean生命周期分析
60 11