Spring Data REST中的投影和摘录

2023/05/18

1. 概述

在本文中,我们介绍Spring Data REST中的投影和摘录概念。

我们将学习如何使用投影创建模型的自定义视图,以及如何使用摘录作为资源集合的默认视图

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;
}

这两个实体具有多对多的关系。

接下来,我们为每个模型定义标准的Spring Data REST Repository:

public interface BookRepository extends CrudRepository<Book, Long> {}
public interface AuthorRepository extends CrudRepository<Author, Long> {}

现在,我们可以访问Book端点http://localhost:8080/books/{id}并传递其id获取特定Book的详细信息:

{
    "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"
        }
    }
}

请注意,由于Author模型有其Repository,因此Author详细信息不属于响应的一部分。不过我们可以通过http://localhost:8080/books/1/authors得到Author的详细信息。

3. 创建投影

有时,我们只对实体属性的子集或自定义视图感兴趣。对于这种情况,我们可以使用投影。让我们使用Spring Data REST投影为我们的Book创建一个自定义视图,首先创建一个名为CustomBook的简单投影:

@Projection(name = "customBook", types = {Book.class }) 
public interface CustomBook { 
    String getTitle();
}

请注意,我们的投影被定义为带有@Projection注解的接口。我们可以使用name属性来自定义投影的名称,也可以使用types属性定义它所应用的对象。

在我们的示例中,CustomBook投影将仅包含Book的title属性:

{
    "title": "Animal Farm",
    "isbn": "978-1943138425",
    "_links": {
        "self": {
            "href": "http://localhost:8080/books/1"
        },
        "book": {
            "href": "http://localhost:8080/books/1{?projection}",
            "templated": true
        },
        "authors": {
            "href": "http://localhost:8080/books/1/authors"
        }
    }
}

我们可以看到访问投影的链接,下面访问我们在http://localhost:8080/books/1?projection=customBook创建的视图:

{
    "title": "Animal Farm",
    "_links": {
        "self": {
            "href": "http://localhost:8080/books/1"
        },
        "book": {
            "href": "http://localhost:8080/books/1{?projection}",
            "templated": true
        },
        "authors": {
            "href": "http://localhost:8080/books/1/authors"
        }
    }
}

在这里,返回的JSON体中只包含title属性,而isbn不再出现在自定义视图中。

一般来说,我们可以在http://localhost:8080/books/1?projection={projection name}访问投影结果。

另外,请注意我们需要在与我们的模型相同的包中定义我们的投影。或者,我们可以使用RepositoryRestConfigurerAdapter显式添加它:

@Configuration
public class RestConfig implements RepositoryRestConfigurer {
 
    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration repositoryRestConfiguration, CorsRegistry cors) {
        repositoryRestConfiguration.getProjectionConfiguration().addProjection(CustomBook.class);
    }
}

4. 向投影中添加新数据

现在,让我们看看如何将新数据添加到我们的投影中。正如我们在上一节中讨论的那样,我们可以使用投影来选择要包含在视图中的属性。更重要的是,我们还可以添加原始视图中不包含的数据。

4.1 隐藏数据

默认情况下,原始资源视图中不包含id。要在结果中返回id的值,我们可以显式包含id字段:

@Projection(name = "customBook", types = { Book.class }) 
public interface CustomBook {
    @Value("#{target.id}")
    long getId(); 
    
    String getTitle();
}

现在访问http://localhost:8080/books/1?projection={projection name}得到的结果将是:

{
    "id": 1,
    "title": "Animal Farm",
    "_links": {
        ...
    }
}

请注意,我们还可以包含使用@JsonIgnore从原始视图中隐藏的数据。

4.2 计算数据

我们还可以包括根据我们的资源属性计算的新数据。例如,我们可以在我们的投影中包含Author对象的数量:

@Projection(name = "customBook", types = { Book.class }) 
public interface CustomBook {
 
    @Value("#{target.id}")
    long getId(); 
    
    String getTitle();
        
    @Value("#{target.getAuthors().size()}")
    int getAuthorCount();
}

同样,下面是访问http://localhost:8080/books/1?projection=customBook返回的结果:

{
    "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.getAuthors().size()}")
    int getAuthorCount();
}

最终的投影输出将是:

{
    "id": 1,
    "title": "Animal Farm",
    "authors": [
        {
            "name": "George Orwell"
        }
    ],
    "authorCount": 1,
    "_links": {
        "self": {
            "href": "http://localhost:8080/books/1"
        },
        "book": {
            "href": "http://localhost:8080/books/1{?projection}",
            "templated": true
        },
        "authors": {
            "href": "http://localhost:8080/books/1/authors"
        }
    }
}

5. 摘录

摘录是我们将其作为默认视图应用于资源集合的投影

下面我们自定义BookRepository以自动将customBook Projection用于收集响应。为此,我们使用@RepositoryRestResource注解的excerptProjection属性:

@RepositoryRestResource(excerptProjection = CustomBook.class)
public interface BookRepository extends CrudRepository<Book, Long> {}

现在我们可以通过调用http://localhost:8080/books来确保customBook是books集合的默认视图:

{
    "_embedded": {
        "books": [
            {
                "id": 1,
                "title": "Animal Farm",
                "authors": [
                    {
                        "name": "George Orwell"
                    }
                ],
                "authorCount": 1,
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/books/1"
                    },
                    "book": {
                        "href": "http://localhost:8080/books/1{?projection}",
                        "templated": true
                    },
                    "authors": {
                        "href": "http://localhost:8080/books/1/authors"
                    }
                }
            }
        ]
    },
    "_links": {
        "self": {
            "href": "http://localhost:8080/books"
        },
        "profile": {
            "href": "http://localhost:8080/profile/books"
        }
    }
}

这同样适用于通过http://localhost:8080/authors/1/books访问特定作者的书籍:

{
    "_embedded": {
        "books": [
            {
                "id": 1,
                "authors": [
                    {
                        "name": "George Orwell"
                    }
                ],
                "authorCount": 1,
                "title": "Animal Farm",
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/books/1"
                    },
                    "book": {
                        "href": "http://localhost:8080/books/1{?projection}",
                        "templated": true
                    },
                    "authors": {
                        "href": "http://localhost:8080/books/1/authors"
                    }
                }
            }
        ]
    },
    "_links": {
        "self": {
            "href": "http://localhost:8080/authors/1/books"
        }
    }
}

如前所述,摘录仅自动应用于集合资源。对于单个资源,我们必须使用前面部分所示的projection参数。这是因为如果我们将投影应用为单个资源的默认视图,那么很难知道如何从局部视图更新资源。

最后一点,重要的是要记住投影和摘录是只读的

6. 总结

在本文中,我们介绍了如何使用Spring Data REST投影来创建我们模型的自定义视图,并学习了如何使用摘录作为资源集合的默认视图。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章