Spring Data Commons主要梳理

这篇文章是在看Spring Data Commons文档的时候梳理的内容,都是与Spring Data相关的,里面可能会涉及到Spring Data JPA的内容,但更多的是Commons的内容,Spring Data JPA只是一个具体的实现而已。

1. Spring Data 模块关系

Spring Data家族有多个模块:

  • Spring Data JPA
  • Spring Data Mongo
  • ....

但所有的模块都基于Spring Data Commoms模块进行扩展。例如Spring Data JPA是针对JPA做扩展的一个子模块;Spring Data Mongo是针对MongoDB的子模块,但是共同的接口都是Spring Data。
可以说Spring Data Commons是其所有子模块的抽象,定义了一系列的操作标准及接口。

2. 独立使用Spring Data

Spring Data提供了RepositoryFactory,可以独立于Spring容器之外使用:

RepositoryFactorySupport factory = … // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);

3. Repository - dao - 存储仓库

Spring Data中,都是基于存储仓库Repository对实体类对象进行CRUD操作的。
其中,最核心的是Repository接口,在org.springframework.data.repository包中定义。
Repository接口需要指定操作的实体类的类型实体类ID的类型。在该接口中,是没有任何方法的,只是用于标记让Spring Data知道。
通常我们会使用CurdRepository这个接口,当然,这个接口也是不包含JPA特性的,我们需要使用JPA特性的话,一般用的是JpaRepository接口。
下面给出Spring Data JPA中,JpaRepository的继承结构:

1.jpg

4. 使用指定Spring Data模块

上面一开始说了Spring Data有很多个不同的子模块,每个子模块对应一种数据存储方式或数据源,统称Spring Data *吧。
要让Spring Data在运行的时候知道我们使用哪个模块,就要进行指定,指定模块的方法有两种:

  • 存储库使用Spring Data模块特定的接口指定类型
  • 实体类使用Spring Data模块模块特定的注解指定类型

如果都不指定,在单模块的情形下是不存在问题的,但是如果项目中引入了不同的Spring Data模块,那么Spring Data在实际运行中就无法确定到底需要使用哪个模块。

5. 查询方法的拆分

Spring Data以方法中的结构为findBy...deleteBy...,其结构都是动词加上ByBy是整个语句拆分的关键点。通常By后面跟着的是查询的参数列表,通过AndOr来连接。
By后面的参数中,首先将整个内容作为一个属性,如果找不到,然后再参数中以大写字母为分割,直到找到对应的参数为止。

6. 分页查询、排序查询、限制查询、流式结果查询

6.1 排序查询

Spring Data接受使用Sort类型参数来进行排序查询的工作,Sort主要有两个参数构成:

  • 排序方向:ASC(升序)、DESC(降序)
  • 排序字段

6.2 分页查询参数

PageableSpring Data提供出来进行分页查询参数输入的接口,里面主要定义多个与页面设定的方法:

6.2.jpg

实现有很多,最常用的是PageRequest

6.2-2.jpg

PageRequest废弃了原本的new方式来构建分页参数,建议使用类中提供的of(...)静态方法来创建相关的分页参数实体。
of(...)分页查询中允许带上排序字段以及排序方向两个参数,在源码中,这两个共同构建成了Sort实体。这是很多业务中需要使用上的,首先需要讲内容进行排序,然后再分页列出。
分页查询中,next()previous()返回的分别是下一页上一页的分页参数实体,

6.3 分页查询结果(返回值)

除了支持常用的集合ListSet等查询结果集作为分页查询结果,Spring Data还有以下几种结果:

  • Page<T>
  • Slice<T>
  • List<T>
  • Set<T>

6.4 limit查询

Mysql中,我们需要获得前N个结果,使用limit关键字。
Spring Data中可以使用:

  • top
  • first

来进行限定最终的结果数,在topfirst关键字后面可以加上数字表示最大结果大小,默认值为1

  • User findFirstByUsername(String username)
  • List<Article> findFirst10Bytitle(String title)

当然,这个是支持使用PageableSort的。

6.5 流式查询结果

Spring Data支持使用Stram<T>流式API使用。

@Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();

Stream<User> readAllByFirstnameNotNull();

@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);

一般在try-with-resources中使用:

try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
  stream.forEach(…);
}

7. 自定义实现dao存储库

7.1 自定义存储库片段

都知道当使用Spring Data之后,大部分的CRUD操作我们都不需要去实现。但是当某些情况下,例如有些查询方法需要不同的行为或者无法通过Spring查询实现时,我们确实是要自己手动实现查询方法的。这种做法叫Repository fragments,存储库片段。

步骤如下:

  1. 做出一个自己的接口:
interface CustomizedUserRepository {
  void someCustomMethod(User user);
}
  1. 实现接口,就是很普通的接口实现
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {

  public void someCustomMethod(User user) {
    // Your custom implementation
  }
}

注:

  1. 上面的实现中,一定要以Impl为结尾
  2. 接口的实现可以作为一个普通的Bean存在
  1. *Repository存储库扩展(继承)这个接口:
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {

  // Declare query methods here
}

注:
当有两个片段提供相同的方法和签名,那么将按照继承顺序覆盖。
但是别忘了,每个片段的实现

上面这种做法可以组合Spring Data提供的CURD以及自定义实现的接口。相对自由,但是也相对复杂。

注:
而且对于领域设计的角度来说,能不把业务放到dao层就千万不要这么做!!

7.2 使用命名空间来配置自定义存储库片段Bean

XMLJava Config中的配置base-packageSpring Data的基础架构会在启动的时候到配置的路径下去扫描对应的存储库包,找到实现并配置为Bean
因此,在上面说了实现片段需要后缀为Impl
如果不是,可以通过repository-impl-postfix来配置对应的后缀,如:

<repositories base-package="com.acme.repository" repository-impl-postfix="MyPostfix" />

如果是使用Java Config:

@EnableJpaRepositories(basePackages = "cn.marer", repositoryImplementationPostfix = "DAO")
public class ....

后缀是可以配置多个的,不同的后缀就扫描不同的结果。

7.3 自定义BaseRepository - 基类

在Spring Data出现之前我们会使用BaseDao来实现一些基本的数据库的操作,现在也可以这样做:

class MyRepositoryImpl<T, ID extends Serializable>
  extends SimpleJpaRepository<T, ID> {

  private final EntityManager entityManager;

  MyRepositoryImpl(JpaEntityInformation entityInformation,
                          EntityManager entityManager) {
    super(entityInformation, entityManager);

    // Keep the EntityManager around to used from the newly introduced methods.
    this.entityManager = entityManager;
  }

  @Transactional
  public <S extends T> S save(S entity) {
    // implementation goes here
  }
}

注:

  1. 该类需要具有特定于商店的存储库工厂实现所使用的超类的构造函数。
  2. 如果存储库基类具有多个构造函数,则覆盖使用EntityInformation加号存储特定基础结构对象(例如,EntityManager模板类)的构造函数。

然后还需要在Java Config中指定存储库基类:

@Configuration
@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration { … }

这个就类似于为基类配置一个Bean。

8. 发布事件

直接官方机翻:
由存储库管理的实体是聚合根。在域驱动设计应用程序中,这些聚合根通常会发布域事件。Spring Data提供了一个注释@DomainEvents,可以在聚合根的方法上使用,以使该发布尽可能简单,如以下示例所示:

class AnAggregateRoot {

    @DomainEvents 
    Collection<Object> domainEvents() {
        // … return events you want to get published here
    }

    @AfterDomainEventPublication 
    void callbackMethod() {
       // … potentially clean up domain events list
    }
}

使用的方法@DomainEvents可以返回单个事件实例或事件集合。它不能使用任何参数。
在所有事件发布后,我们有一个注释的方法@AfterDomainEventPublication。它可用于潜在地清除要发布的事件列表(以及其他用途)。

9. Spring Data对Spring MVC支持

9.1 打开Spring data对web的支持

Spring Data提供了对web的友好支持,特别是下面将说到的领域类型(可以先看作实体类)转换的支持以及分页、排序的支持,要打开支持,有两种方式:

  • Java Config
  • XML

还是比较推荐使用Java Config的方式,毕竟Spring Boot大多数都是注解形式嘛:

@EnableSpringDataWebSupport
piublic class WebConfiguration {
    ...
}

XML开启:

<bean class="org.springframework.data.web.config.SpringDataWebConfiguration" />

按照官方文档,如果打开了这个支持,Spring会自动配置了下面三个Bean:

  • DomainClassConverter - 允许在不用通过存储库手动查找的情况下,直接在SpringMVC的控制器方法的签名(参数)中直接使用领域对象

  • PageableHandlerMethodArgumentResolver - 允许通过请求参数Pageable对象注入到controller方法参数 Pageable

  • SortHandlerMethodArgumentResolver - 允许通过请求参数Sort对象注入到controller方法参数 Sort

9.2 DomainClassConverter - 领域类型转换器支持

DomainClassConverter可以在不用通过存储库手动查找的情况下,直接在SpringMVC的控制器方法的签名(参数)中直接使用领域对象。
需要按照上面的两种方式其中一个打开对web的支持。

现在假定有User这个领域对象和UserRepository这个存储库,那么可以直接使用:

@RestController
public class UserController {
    @PostMapping("/user/info/{id}")
    public User getUserInfo(@PathVariable("id") User user) {
        logger.info("Get user info By Spring data web support");
        return user;
    }
}

上面方法中,并没有通过UserRepository存储库对象来获取相对应的用户数据。
SpringMVC将提交上来的参数{id}通过@PathVariable获得ID,然后调用findById(...)查找出对应的实体并注入到方法参数中。所以可以直接通过返回User参数,算是偷懒方式。

注意:

  1. 领域类型对应的存储库最起码是要实现CurdRepository才可以实现这个功能,
  2. 如果找不到数据,会返回500 Internal Server Error,需要对数据做校验或者做异常处理。

9.3 HandlerMethodArgumentResolvers - 分页、排序的支持

在上面说了,需要让Spring Data支持分页或排序功能,就需要在存储库Repository接口方法中传入PageableSort对象,Pageable分页的对象里面也包含了Sort排序。Spring Data会自动解析并对分页、排序进行limitorder by查询。
而Spring Data也对web提供了分页、排序的支持,让我们可以在访问http提交请求参数的时候直接将pageablesort参数注入到controller的方法参数。
主要是配置了@EnableSpringDataWebSupport后,Spring Data会生成PageableHandlerMethodArgumentResolverSortHandlerMethodArgumentResolver两个Bean实例。在我们的Controller的方法中加入参数即可:

@RestController
public class UserController {
    @GetMapping("/user/info/all/page")
    public List<User> getAllUserPage(Pageable pageable){
        logger.info(“try to find user with pageable.”);
        return userRepository.findAll(pageable).getContent();
    }
}

在上面的例子中,要做的是:分页显示用户。使用了Pageable作为例子,因为我们知道Pageable里面包含了Sort,因此Sort的例子在这里就不再叙述了。
上面的例子里面,提交的参数为:

  • page - 第几页,默认为0
  • size - 页面数据数量,默认为20
  • sort - 排序方向,默认为升序ASC

假设:查询第一页的用户并根据用户名降序排序,页面大小为15。则请求API如下:

/user/info/all/page?page=0&size=15&sort=username,DESC

注意:
上面这个例子是GET请求方法的,如果是POST请求方法,请把请求参数放到请求体里面

更多关于Spring Data对Web支持请移步到:
https://docs.spring.io/spring-data/jpa/docs/2.1.3.RELEASE/reference/html/#core.web


此文同时在简书发布:https://www.jianshu.com/p/cb5a3ab2727e
此文同时在CSDN发布:https://blog.csdn.net/nthack5730/article/details/84939027
转载要加原文链接!谢谢支持!


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,360评论 6 343
  • 核心概念 Repository Repository 是Spring Data 的核心接口 。 它将domai...
    金刚_30bf阅读 7,561评论 0 2
  • 他们说,你不会回来了 说你落在一个遥远的海里 像一条鱼一样 永远在那里呼吸 他们说,你不会回来了 说你穿过白茫茫的...
    悦木君阅读 246评论 0 0
  • 群主鄙人@洛一夫,洛阳一农夫,即@田军一员是也。@山水秀故,@天生美丽,无数次@A小语,@玉如意也,其乐陶陶! @...
    金垛愚叟阅读 502评论 1 1