Spring Data Rest设计的目的是消除curd的模板代码,减少程序员的刻板的重复劳动,尽管拥有强大的功能和精妙的设计,但它作为Spring Data系列产品,终究不能完全代替传统的SpringMVC,其特点也如Spring Data JPA之与Spring Data JDBC等低封装度的产品,高度封装了许多细节,但在用法上有它自己的一套规则。
〇、使用前提条件
此框架需配合其他ORM框架使用如SDJ,使用时给人的直观感受就是暴露了资源仓库。
一、普通的crud
这部分作为SDR的最大亮点,它的实现形式就是完全消除模板代码,我们需要做的就是访问对应的资源仓库地址实现相关操作
增删改查动作分别对应四种请求类型:post、delete、update/patch、get
增:直接携带请求体请求仓库地址;如有对应关系,一对多的多端的关联对象属性应为对应的关联对象的仓库地址(SDR和SDJ会将该链接转为相关对象储存),多对一的一端的关联对象集合属性应为要储存的关联对象集合。
删:直接请求资源地址;若被删对象的关联对象类(一对多的“一”端)的关系注解处(如@OneToMany)写明了级联操作(如:CascadeType.REMOVE)则关联的对象将被一并删除。
改:直接请求资源地址,put将完全替换原对象,patch将修改指定了值的对象属性(忽略空值);然而只有patch请求能修改关联关系,put请求将忽略关联关系对象。
查:
Spring Data REST 在查询具有对象属性(已使用@ManyToOne等注解建立关联关系)的对象时,默认将对象属性用链接代替,这体现了REST服务概念中的“资源”的特点,但是当遇到需要同时查询多个具有关联关系的对象及其关联的对象的场景的时候,不免要发送多个请求来查询关联对象,Spring官方对这种场景的解决方案是使用@Projection注解自定义返回字段,此查询法适用于查询资源集合和需要显示指定属性的资源对象。
使用步骤:
0、创建实体类,使用@OneToMany等注解建立好对象的关联关系,并创建对应的Repository资源仓库
1、创建一个具有查询对象属性方法的接口,该接口内的所有查询方法返回的字段即为将来使用对应api查询时返回的Json属性;接口包含的查询方法名必须严格匹配getXXX格式(XXX即为对象属性名),否则查询结果无法映射,系统将报错;该接口使用
@Projection(name="X",types={“XX.class”})
注解以表明调用该api的projection参数(X)和它关联的类(XX)。例子如下
2、如需将该projection定义为默认的返回形式,则在对应的Repository仓库上使用
@RepositoryRestResource(excerptProjection = XXXX.class)
注解,但是这种方法将导致查询单个对象时默认返回projection定义的形式。例子如下
3、当需要使用自定义api查询关联对象时,需要在仓库地址后加入projection=X参数
从源码结构中可以看出,其封装了部分mvc的相关功能,这体现了它最大的特点——“便捷”,然而这就不可避免地引入了额外的复杂度,我在使用时就必须根据它要求的用法、它自身特点并考虑项目需求,划分合适的项目结构,编写逻辑代码。
我这两天遇到一个业务场景:管理员可以新增、修改用户(密码)信息,本以为结合spring security一起使用是很方便的,然而在编写代码时发现以下需要注意的地方:
1、sdr默认的仓库权限配置不灵活:常用的仓库方法如GET users、GET user/{id}等,还有自定义的仓库方法,均无法直接在rest入口处配置鉴权检查(因为sdr 自带的mvc没有提供相关接口)
解决:根据官方文档信息得出折衷的解决办法就是在仓库方法上直接配置,然而这样做与我理解的传统mvc配置鉴权的方式有出入,优先级最高的,需要在“入口处”进行的权限检查工作转移到了持久层上。实际上,sdr在rest入口和持久层间有其他增强方法可配置(后面会讲到),这些地方常用来进行参数校验,并且我将部分权限检查移到了那里,但是不论权限配置在这些增强方法上进行还是在上述持久层进行,都不是如“传统的”mvc结构那样在rest入口处进行校验、检查工作
2、需要针对sdr封装的一系列功能进行额外配置:如对mvc配置的cors放行条件对sdr不起作用,(其他配置后续更新)
解决:
3、增强方法的使用上需要注意sdr对于对象的处理流程:我需要对修改密码的用户提交的新密码进行加密,在使用由 @HandleBeforeSave 注解标注的方法时,其传入的对象经过了以下处理:
1、从数据库用id查出对象user0;
2、将传入的对象与user0进行比对;
3、更新user0中与接收的对象不相同的字段
这样我不能简单地对用户的密码进行加密或不加密,这个字段有可能是用户设置的新密码,有可能是数据库中已加密的密码(用户未更新密码),我需要调用spring security提供的正则表达式判断这个密码到底是用户提供的还是数据库中原本 已加密的:
解决:
public void encodePassword(User user) {
Optional.of(user.getPassword())
.filter(pw -> !BCRYPT_PATTERN.matcher(pw).matches())
.ifPresent(
pw ->user.setPassword(passwordEncoder.encode(pw))
);
}