达芬奇密码

除了技术,无它

439篇博客

Spring Data Rest实战

达芬奇密码2018-07-23 09:50
工作中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分别标识外键和关联关系表
如果要深入学习上述注解的话,可以查看 hibernate文档
四、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框架,如果熟练掌握,可以大大减少代码开发量。文章有不足的地方欢迎批评指正。
本文来自网易实践者社区,经作者张震授权发布。