拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 Spring Data Rest – 序列化实体ID

Spring Data Rest – 序列化实体ID

白鹭 - 2022-07-20 2137 0 2

一、概述

众所周知,当我们想快速开始使用RESTful Web 服务时,Spring Data Rest 模块可以让我们的生活更轻松。但是,此模块带有默认行为,有时可能会令人困惑。

在本教程中,我们将了解为什么Spring Data Rest 默认不序列化实体ID。此外,我们将讨论改变这种行为的各种解决方案。

2. 默认行为

在我们进入细节之前,让我们通过一个简单的例子来理解序列化实体id 的含义。

因此,这是一个示例实体Person

@Entity
 public class Person {
 @Id
 @GeneratedValue
 private Long id;
 private String name;
 // getters and setters
 }

此外,我们还有一个存储库PersonRepository

public interface PersonRepository extends JpaRepository<Person, Long> {
 }

如果我们使用Spring Boot,只需添加spring-boot-starter-data-rest依赖项即可启用Spring Data Rest 模块:

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-data-rest</artifactId>
 </dependency>

有了这两个类和Spring Boot 的自动配置,我们的REST 控制器就可以自动使用了。

下一步,让我们请求资源http://localhost:8080/persons并检查框架生成的默认JSON 响应:

{
 "_embedded" : {
 "persons" : [ {
 "name" : "John Doe",
 "_links" : {
 "self" : {
 "href" : "http://localhost:8080/persons/1"
 },
 "person" : {
 "href" : "http://localhost:8080/persons/1{?projection}",
 "templated" : true
 }
 }
 }, ...]
 ...
 }

为简洁起见,我们省略了一些部分。我们注意到,只有name字段为实体Person.不知何故,id字段被剥离了。

因此,这是Spring Data Rest 中的设计决策。在大多数情况下,暴露我们的内部id 并不理想,因为它们对外部系统没有任何意义

在理想情况下,身份是RESTful 架构中该资源的URL

我们还应该看到,只有当我们使用Spring Data Rest 的端点时才会出现这种情况。我们的自定义@Controller@RestController端点不会受到影响,除非我们使用Spring HATEOAS 的RepresentationModel及其子项(如CollectionModelEntityModel)来构建我们的响应。

幸运的是,公开实体ID 是可配置的。因此,我们仍然可以灵活地启用它。

在接下来的部分中,我们将看到在Spring Data Rest 中公开实体id 的不同方式。

3. 使用RepositoryRestConfigurer

公开实体ID 的最常见解决方案是配置RepositoryRestConfigurer

@Configuration
 public class RestConfiguration implements RepositoryRestConfigurer {
 @Override
 public void configureRepositoryRestConfiguration(
 RepositoryRestConfiguration config, CorsRegistry cors) {
 config.exposeIdsFor(Person.class);
 }
 }

在Spring Data Rest 3.1 版或Spring Boot 2.1 版之前,我们将使用RepositoryRestConfigurerAdapter

@Configuration
 public class RestConfiguration extends RepositoryRestConfigurerAdapter {
 @Override
 public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
 config.exposeIdsFor(Person.class);
 }
 }

虽然类似,但要注意版本。附带说明一下,由于Spring Data Rest 版本3.1RepositoryRestConfigurerAdapter已弃用,并且已在最新的4.0.x分支中删除。

在我们为实体Person,响应还为我们提供了id字段:

{
 "_embedded" : {
 "persons" : [ {
 "id" : 1,
 "name" : "John Doe",
 "_links" : {
 "self" : {
 "href" : "http://localhost:8080/persons/1"
 },
 "person" : {
 "href" : "http://localhost:8080/persons/1{?projection}",
 "templated" : true
 }
 }
 }, ...]
 ...
 }

显然,当我们想要为所有实体启用id 公开时,如果我们有很多实体,这个解决方案是不切实际的。

因此,让我们通过通用方法改进我们的RestConfiguration

@Configuration
 public class RestConfiguration implements RepositoryRestConfigurer {
 @Autowired
 private EntityManager entityManager;
 @Override
 public void configureRepositoryRestConfiguration(
 RepositoryRestConfiguration config, CorsRegistry cors) {
 Class[] classes = entityManager.getMetamodel()
 .getEntities().stream().map(Type::getJavaType).toArray(Class[]::new);
 config.exposeIdsFor(classes);
 }
 }

当我们使用JPA 来管理持久性时,我们可以以通用方式访问实体的元数据。JPA 的EntityManager已经存储了我们需要的元数据。因此,我们实际上可以通过entityManager.getMetamodel()方法收集实体类类型

因此,这是一个更全面的解决方案,因为每个实体的id 公开都是自动启用的。

4. 使用@Projection

另一种解决方案是使用@Projection注释。通过定义PersonView接口,我们也可以公开id字段:

@Projection(name = "person-view", types = Person.class)
 public interface PersonView {
 Long getId();
 String getName();
 }

但是,我们现在应该使用不同的请求进行测试,http://localhost:8080/persons?projection=person-view

{
 "_embedded" : {
 "persons" : [ {
 "id" : 1,
 "name" : "John Doe",
 "_links" : {
 "self" : {
 "href" : "http://localhost:8080/persons/1"
 },
 "person" : {
 "href" : "http://localhost:8080/persons/1{?projection}",
 "templated" : true
 }
 }
 }, ...]
 ...
 }

**要为存储库生成的所有端点启用投影,我们@RepositoryRestResource**在PersonRepository上使用@RepositoryRestResource 注释:

@RepositoryRestResource(excerptProjection = PersonView.class)
 public interface PersonRepository extends JpaRepository<Person, Long> {
 }

在此更改之后,我们可以使用我们通常的请求http://localhost:8080/persons来列出人员实体。

但是,我们应该注意excerptProjection不会自动应用单项资源我们仍然必须使用http://localhost:8080/persons/1?projection=person-view来获取单个Person及其实体ID 的响应。

此外,我们应该记住,我们的投影中定义的字段并不总是按顺序排列的:

{
 ...
 "persons" : [ {
 "name" : "John Doe",
 "id" : 1,
 ...
 }, ...]
 ...
 }

为了保持字段顺序,我们可以将@JsonPropertyOrder注释放在我们的PersonView类上

@JsonPropertyOrder({"id", "name"})
 @Projection(name = "person-view", types = Person.class)
 public interface PersonView {
 //...
 }

5. 在Rest 存储库中使用DTO

覆盖休息控制器处理程序是另一种解决方案。Spring Data Rest 允许我们插入自定义处理程序。因此,我们仍然可以使用底层存储库来获取数据,但在响应到达客户端之前覆盖它在这种情况下,我们将编写更多代码,但我们将拥有完全定制的能力。

5.1。执行

首先,我们定义一个DTO 对象来表示我们的Person实体:

public class PersonDto {
 private Long id;
 private String name;
 public PersonDto(Person person) {
 this.id = person.getId();
 this.name = person.getName();
 }
 // getters and setters
 }

如我们所见,我们在这里添加了一个id字段,它对应于Person的实体id。

下一步,我们将使用一些内置的帮助类来重用Spring Data Rest 的响应构建机制,同时尽可能保持响应结构相同。

所以,让我们定义我们的PersonController来覆盖内置端点:

@RepositoryRestController
 public class PersonController {
 @Autowired
 private PersonRepository repository;
 @GetMapping("/persons")
 ResponseEntity<?> persons(PagedResourcesAssembler resourcesAssembler) {
 Page<Person> persons = this.repository.findAll(Pageable.ofSize(20));
 Page<PersonDto> personDtos = persons.map(PersonDto::new);
 PagedModel<EntityModel<PersonDto>> pagedModel = resourcesAssembler.toModel(personDtos);
 return ResponseEntity.ok(pagedModel);
 }
 }

我们应该注意这里的一些要点,以确保Spring 将我们的控制器类识别为插件,而不是独立的控制器:

  1. 必须使用@Controller而不是@RestController@RepositoryRestController

  2. PersonController类必须放在Spring 的组件扫描可以拾取的包下。或者,我们可以使用@Bean.

  3. @GetMapping路径必须与PersonRepository提供的路径相同。如果我们使用@RepositoryRestResource(path = “…”),那么控制器的get 映射也必须反映这一点。

最后,让我们试试我们的端点http://localhost:8080/persons

{
 "_embedded" : {
 "personDtoes" : [ {
 "id" : 1,
 "name" : "John Doe"
 }, ...]
 }, ...
 }

我们可以在响应中看到id字段。

5.2.缺点

如果我们在Spring Data Rest 的存储库上使用DTO,我们应该考虑几个方面。

一些开发人员不习惯将实体模型直接序列化到响应中。当然,它有一些缺点。公开所有实体字段可能会导致数据泄漏、意外延迟提取和性能问题

但是,为所有端点编写@RepositoryRestController是一种妥协它带走了框架的一些好处。此外,在这种情况下,我们需要维护更多的代码。

六,结论

在本文中,我们讨论了在使用Spring Data Rest 时公开实体ID 的多种方法。


标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *