1. 概述
在本文中,我们将深入探讨 Spring Data REST 提供的两个核心功能:Projection(投影) 和 Excerpt(摘录)。
- ✅ Projection:允许我们为实体创建自定义视图,用于控制返回给客户端的数据字段。
- ✅ Excerpt:是应用于资源集合的默认 Projection,常用于优化列表接口的响应内容。
这两个功能在构建 RESTful API 时非常实用,尤其适用于需要精简响应数据或聚合展示信息的场景。
2. 域模型定义
我们以两个实体为例:Book
和 Author
,它们之间是多对多关系。
Book 实体
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(nullable = false)
private String title;
private String isbn;
@ManyToMany(mappedBy = "books", fetch = FetchType.EAGER)
private List<Author> authors;
}
Author 实体
@Entity
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(nullable = false)
private String name;
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(
name = "book_author",
joinColumns = @JoinColumn(name = "book_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "author_id", referencedColumnName = "id"))
private List<Book> books;
}
Repository 接口
public interface BookRepository extends CrudRepository<Book, Long> {}
public interface AuthorRepository extends CrudRepository<Author, Long> {}
启动项目后,我们可以通过如下接口访问书籍详情:
GET http://localhost:8080/books/1
默认返回内容如下:
{
"title": "Animal Farm",
"isbn": "978-1943138425",
"_links": {
"self": { "href": "http://localhost:8080/books/1" },
"book": { "href": "http://localhost:8080/books/1" },
"authors": { "href": "http://localhost:8080/books/1/authors" }
}
}
可以看到,authors
字段并未直接嵌入,而是以链接形式存在。如果我们希望自定义返回字段,就需要使用 Projection。
3. 创建 Projection
Projection 允许我们为实体创建定制化的数据视图,适用于需要返回特定字段或组合信息的场景。
定义一个 Projection 接口
@Projection(name = "customBook", types = { Book.class })
public interface CustomBook {
String getTitle();
}
✅
@Projection
注解用于定义一个投影接口,name
是投影名称,types
指定适用的实体类型。
访问方式如下:
GET http://localhost:8080/books/1?projection=customBook
返回结果只包含 title
字段:
{
"title": "Animal Farm",
"_links": {
...
}
}
配置 Projection 所在包(可选)
如果你的 Projection 没有放在 Spring Boot 主类的子包下,可以通过如下方式注册:
@Configuration
public class RestConfig implements RepositoryRestConfigurer {
@Override
public void configureRepositoryRestConfiguration(
RepositoryRestConfiguration config, CorsRegistry cors) {
config.getProjectionConfiguration().addProjection(CustomBook.class);
}
}
4. 在 Projection 中添加额外字段
Projection 不仅可以筛选字段,还能添加原本不返回的数据,甚至可以嵌入关联对象或计算值。
4.1 显示隐藏字段(如 id)
默认情况下,Spring Data REST 不会返回 id
字段,但我们可以通过 @Value("#{target.id}")
强制显示:
@Projection(name = "customBook", types = { Book.class })
public interface CustomBook {
@Value("#{target.id}")
long getId();
String getTitle();
}
返回结果:
{
"id": 1,
"title": "Animal Farm",
"_links": { ... }
}
⚠️ 如果字段被
@JsonIgnore
注解修饰,也可以通过这种方式强制显示。
4.2 添加计算字段(如作者数量)
我们可以使用 SpEL 表达式来添加计算字段:
@Projection(name = "customBook", types = { Book.class })
public interface CustomBook {
@Value("#{target.id}")
long getId();
String getTitle();
@Value("#{target.authors.size()}")
int getAuthorCount();
}
返回结果:
{
"id": 1,
"title": "Animal Farm",
"authorCount": 1,
"_links": { ... }
}
4.3 嵌入关联资源(如作者信息)
如果我们希望直接嵌入关联对象,而不是通过链接访问,可以如下定义:
@Projection(name = "customBook", types = { Book.class })
public interface CustomBook {
@Value("#{target.id}")
long getId();
String getTitle();
List<Author> getAuthors();
@Value("#{target.authors.size()}")
int getAuthorCount();
}
返回结果:
{
"id": 1,
"title": "Animal Farm",
"authors": [
{
"name": "George Orwell"
}
],
"authorCount": 1,
"_links": { ... }
}
5. 使用 Excerpt 作为集合资源的默认视图
Excerpt 是一种特殊的 Projection,用于设置资源集合的默认视图。
设置默认视图
修改 BookRepository
:
@RepositoryRestResource(excerptProjection = CustomBook.class)
public interface BookRepository extends CrudRepository<Book, Long> {}
访问书籍列表接口:
GET http://localhost:8080/books
返回结果将自动使用 CustomBook
投影:
{
"_embedded": {
"books": [
{
"id": 1,
"title": "Animal Farm",
"authors": [
{
"name": "George Orwell"
}
],
"authorCount": 1,
"_links": { ... }
}
]
},
"_links": { ... }
}
✅ Excerpt 只对集合资源生效,单个资源仍需使用
?projection=xxx
参数。
踩坑提醒
- ❌ 不建议将 Projection 作为单个资源的默认视图,因为这样会导致部分更新困难。
- ✅ Projection 和 Excerpt 都是只读的,不适用于写入操作。
6. 总结
通过本文,你应该已经掌握了:
- ✅ 如何使用 Projection 创建自定义实体视图
- ✅ 如何在 Projection 中添加隐藏字段、计算字段、嵌入关联对象
- ✅ 如何使用 Excerpt 设置资源集合的默认视图
- ✅ Projection 和 Excerpt 的适用场景与限制
这些功能在构建轻量级、高定制化的 RESTful API 时非常实用,建议在实际项目中根据需求灵活使用。
完整示例代码可参考 GitHub:Spring Data REST Projections 示例