工作中REST风格的代码经常写,突然想试一下Spring Data REST。写了个demo,以前要花半天的时间编写的代码,有了Spring Data REST,只需要1个小时就可以生成了,甚至接口说明文档也能自动生成,那么它有什么神奇之处呢?Spring Data Rest 是基于 Spring Data repositories,分析实体之间的关系。为我们生成Hypermedia API(
HATEOAS
)风格的Http Restful API接口。借用官网上说Spring Data Rest 主要的特点如下:
- 根据model,生成HAL风格的restful API
- 根据model,维护实体之间的关系
- 支持分页
- 允许动态过滤资源
Spring Data Rest 目前支持Spring Data JPA、Spring Data MongoDB、Spring Data Neo4j、Spring Data GemFire、Spring Data Cassandra的 repository 自动转换成REST服务。注意是自动。简单点说,Spring Data REST把我们需要编写的大量REST模版接口做了自动化实现。本次我主要以Spring Data JPA为例,展示Spring Data Rest的基本功能。
一、maven依赖
<!--jpa-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-></artifactId>
</dependency>
<!--data rest-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-></artifactId>
</dependency>
<!--hal-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-></artifactId>
</dependency>
二、基本配置
Spring Data REST以根目录“/”提供REST资源,有2种方法来改变基本路径:
- 注入RepositoryRestConfigurerAdapter的实现类,适合普通spring项目
- 或者直接配置参数:spring.data.rest.basePath=/api,适合springboot项目
除此以外,还有很多参数可以直接配置:
- basePath 设置Spring Data REST的根路径
- defaultPageSize 设置单页内默认个数
- maxPageSize 设置单页内最大个数
- pageParamName 设置请求页码参数名
- limitParamName 设置limit参数名
- sortParamName 设置排序参数名
- defaultMediaType 设置media type
- returnBodyOnCreate 设置是否在创建记录后返回数据
- returnBodyOnUpdate 设置是否在修改记录后返回数据
三、数据模型
假设ER图如上,基本本关系包含一对多、一对一、多对多
model层可以定义如下:
@Entity
@Table(name = "PostCount")
public class PostCount implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@GenericGenerator(name = "increment", strategy = "increment")
private long id;
private long viewCount;
private long hotCount;
private long favoriteCount;
private long shareCount;
}
@Entity
@Table(name = "Tag")
public class Tag implements Serializable{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@GenericGenerator(name = "increment", strategy = "increment")
private long id;
private String tagName;
}
PostCount、Tag是最简单的model,看到可以使用@Id、@GeneratedValue、@GenericGenerator等注解保证id为自增主键。
@Entity
@Table(name = "BlogInfo")
public class BlogInfo implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@GenericGenerator(name = "increment", strategy = "increment")
private long id;
private String blogName; //博客用户名
private String blogNickName; // 博客昵称
private String avaImg; // 大头像
private String selfIntro; //自我介绍
private long blogCreateTime; //博客创建时间
private long avaUpdateTime; //头像更新时间
@OneToMany(cascade = {CascadeType.DETACH}, mappedBy = "blog")
private List posts = new ArrayList<>();
}
BlogInfo为博客model,存在一对多关系,由于对应的Post类型中包含双向关系,此时配置mappedBy属性做反转。
@Entity
@Table(name = "Post")
public class Post implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@GenericGenerator(name = "increment", strategy = "increment")
private long id; //日志id key
private long blogId; //博客的Id 均衡字段
private String title; //日志标题
private long publishTime; //发布时间
private long modifyTime;//修改时间
private String moveFrom; //该篇日志来自哪里
private String ip; //来源ip
private String digest; //摘要
private String content; // 内容
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "post_count_fk")
private PostCount postCount;
@ManyToOne
@JoinColumn(name = "blog_fk")
private BlogInfo blog;
@ManyToMany
@JoinTable(name = "PostAndTag",
joinColumns = @JoinColumn(name = "postId", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "tagId", referencedColumnName = "id"))
private List tags;
}
Post包含一对一关系,多对一关系,多对多关系。
其中,Casccade设置的是级联操作,包含如下:
- CascadeType.REMOVE 级联删除操作。
- CascadeType.MERGE 级联更新(合并)操作。
- CascadeType.DETACH 级联脱管/游离操作。
- CascadeType.REFRESH 级联刷新操作。
- CascadeType.ALL 拥有以上所有级联操作权限。
JoinColumn和JoinTable分别标识外键和关联关系表
四、Repository层与接口映射
其实一般来说Repository直接使用默认实现就能完成大部分工作如下
@RepositoryRestResource(path = "posts")
public interface PostRepository extends JpaRepository {
}
@
RepositoryRestResource的作用是指定当前Repository的基本路径,当然默认也是posts
有多个可继承的接口
CrudRepository 比较简单的增删改查
PagingAndSortingRepository 前者的基础上加了排序和分页
JpaRepository jpa扩充的Repository接口,比较全面
完成Repository之后就基本完成了rest接口的开发,我们来试一下,启动springboot项目,在浏览器内输入localhost:8080/api,得到结果如下:
这个是HAL插件,主要是方便查看HAL接口的数据和资源。
右下角就是当前能够使用的资源,包含如下两个:
http://localhost:8080/api/posts{?page,size,sort}
http://localhost:8080/api/blogInfos{?page,size,sort}
其中每个资源都可以如下形式访问:
注:
{repository}为Repository名字,这里可以为posts、blogInfo;
methods为http的方法,produces为content-type值
a.查询列表
/api/{repository},methods=[GET],produces=[application/hal+json || application/json]
/api/{repository},methods=[GET],produces=[application/x-spring->b.添加
/api/{repository},methods=[POST],produces=[application/hal+json || application/json]
c.获取指定id
/api/{repository}/{id},methods=[GET],produces=[application/hal+json || application/json]
d.修改指定id
/api/{repository}/{id},methods=[PATCH],produces=[application/hal+json || application/json]
/api/{repository}/{id},methods=[PUT],produces=[application/hal+json || application/json]
e.删除指定id
/api/{repository}/{id},methods=[DELETE],produces=[application/hal+json || application/json]
f.自定义搜索
/api/{repository}/search/{search},methods=[GET],produces=[application/x-spring->/api/{repository}/search,methods=[GET],produces=[application/hal+json || application/json]
/api/{repository}/search/{search},methods=[GET],produces=[application/hal+json || application/json]
g.查询指定id的指定属性
/api/{repository}/{id}/{property},methods=[GET],produces=[application/hal+json || application/json]
/api/{repository}/{id}/{property}/{propertyId},methods=[GET],produces=[application/hal+json || application/json]
/api/{repository}/{id}/{property},methods=[GET],produces=[application/x-spring->h.删除指定id的指定属性or属性id,一般为关联表或外键
/api/{repository}/{id}/{property},methods=[DELETE],produces=[application/hal+json || application/json]
/api/{repository}/{id}/{property}/{propertyId},methods=[DELETE],produces=[application/hal+json || application/json]
i.设置指定id的指定属性or设置关联关系
/api/{repository}/{id}/{property},methods=[PATCH || PUT || POST],consumes=[application/json || application/x-spring->举例来说:
1.测试添加:
添加时,使用http://localhost:8080/api/posts地址,post请求,json参数。
2.测试自定义查询
需要在PostRepository中添加方法(根据title字段查询前置匹配),jpa自己会去实现
@RestResource(path = "title", rel = "title")
public List findByTitleStartsWithIgnoringCase(@Param("title") String title);
五、事件监听
Spring Data Rest框架提供了多个事件供开发者使用:
-
BeforeCreateEvent
-
AfterCreateEvent
-
BeforeSaveEvent
-
AfterSaveEvent
-
BeforeLinkSaveEvent
-
AfterLinkSaveEvent
-
BeforeDeleteEvent
-
AfterDeleteEvent
有两种方式监听这些事件:
一种是实现AbstractRepositoryEventListener抽象类
public class BeforeSaveEventListener extends AbstractRepositoryEventListener {
@Override
public void onBeforeSave(Object entity) {
}
@Override
public void onAfterDelete(Object entity) {
}
}
另一种是使用注解
@Service
@RepositoryEventHandler
public class PersonEventHandler {
@HandleBeforeSave
public void handlePersonSave(Person p) {
}
@HandleBeforeSave
public void handleProfileSave(Profile p) {
}
}
六、验证
Spring Data Rest框架支持使用验证器在操作Repository前做自定义验证,有两种方式实现:
1.直接注入
Validator<Model类>接口的实现类,Bean的名称必须为事件名前缀
2.手动编写配置类绑定验证规则,例如
@Override
protected void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener v) {
v.addValidator("beforeSave", new BeforeSaveValidator());
}
七、安全
Spring Data Rest可以与Spring Security配合使用
可以使用@Pre and @Post security注解来保证Repository的权限限制,例如在配置好Spring Security权限后,对Repository做如下处理:
@PreAuthorize("hasRole('ROLE_USER')")
@RepositoryRestResource(path = "posts")
public interface PostRepository extends JpaRepository {
@PreAuthorize("hasRole('ROLE_ADMIN')")
@RestResource(path = "title", rel = "title")
public List findByTitleStartsWithIgnoringCase(@Param("title") String title);
}
上述要求必须有ROLE_USER和ROLE_ADMIN权限才能访问前置查询接口。
当然也可以使用@Secured注解来设置权限限制,例如:
@Secured("ROLE_USER")
@RepositoryRestResource(path = "posts")
public interface PostRepository extends JpaRepository {
@Secured("ROLE_ADMIN")
@RestResource(path = "title", rel = "title")
public List findByTitleStartsWithIgnoringCase(@Param("title") String title);
}
相比@PreAuthorize注解,@Secured注解更加死板只能设置指定的权限,无法配置规则。
启动上述注解的方式是在配置类或Application类上加入启动注解,形如下:
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
总结:Spring Data Rest是一个既方便又灵活的restful框架,如果熟练掌握,可以大大减少代码开发量。文章有不足的地方欢迎批评指正。
本文来自网易实践者社区,经作者张震授权发布。