如何将联接 table 中的嵌入式 ID 的关系公开为与 Spring 数据 Rest 的链接
How to expose relations of an embedded id in a join table as links with Spring Data Rest
简介
假设在带有 Spring Data Rest 模块的 Spring 启动应用程序中有两个主要实体(例如 Student
和 LegalGuardian
)。它们通过一个“关联实体”(例如 Guardianship
)连接,该实体由一个嵌入式 ID(例如 GuardianshipId
)标识。此外,这个嵌入的 id 包含与两个主要实体的关系(不是主要实体的 id - 实体本身)。
// The Main entities
@Entity
public class Student extends AbstractPersistable<Long> {
private String name;
@OneToMany(mappedBy = "guardianshipId.student")
private List<Guardianship> guardianships;
// getters and setters
}
@Entity
public class LegalGuardian extends AbstractPersistable<Long> {
private String name;
@OneToMany(mappedBy = "guardianshipId.legalGuardian")
private List<Guardianship> guardianships;
// getters and setters
}
// The association entity
@Entity
public class Guardianship implements Serializable {
@EmbeddedId
private GuardianshipId guardianshipId;
private String name;
// getters, setters, equals and hashCode
@Embeddable
public static class GuardianshipId implements Serializable {
@ManyToOne
private Student student;
@ManyToOne
private LegalGuardian legalGuardian;
// getters, setters, equals and hashCode
}
}
对于所有这些实体,存在单独的存储库:
StudentRepository : JpaRepository<Student, Long>
,
LegalGuardianRepository : JpaRepository<LegalGuardian, Long>
和
GuardianshipRepository : JpaRepository<Guardianship, Guardianship.GuardianshipId>
要通过 REST 通过 id 查询 GuardianshipRepository
的单个 Guardianship
s,还实现了 BackendIdConverter
(这样 id 看起来像 {studentId} _{legalGuardianId}).
如果请求关联实体的存储库,默认情况下嵌入的 id 本身(及其属性)不会被序列化,因此响应如下所示:
$ curl "http://localhost:8080/guardianships/1_2"
{
"name" : "Cool father",
"_links" : {
"self" : {
"href" : "http://localhost:8080/guardianships/1_2"
},
"guardianship" : {
"href" : "http://localhost:8080/guardianships/1_2"
}
}
}
Quesion/Problem
必须做什么,以便响应包含 链接 到在嵌入式 ID 中定义的实体,如下所示:
$ curl "http://localhost:8080/guardianships/1_2"
{
"name" : "Cool father",
"_links" : {
"self" : {
"href" : "http://localhost:8080/guardianships/1_2"
},
"guardianship" : {
"href" : "http://localhost:8080/guardianships/1_2"
},
"student" : {
"href" : "http://localhost:8080/guardianships/1_2/student"
},
"legalGuardian" : {
"href" : "http://localhost:8080/guardianships/1_2/legalGuardian"
}
}
}
(天真无成)Attempt/Try
第一个想法是通过委托嵌入的 id 使嵌套关系可访问:
@Entity
public class Guardianship implements Serializable {
@EmbeddedId
private GuardianshipId guardianshipId;
public Student getStudent() { return guardianshipId.getStudent(); }
public LegalGuardian getLegalGuardian() { return guardianshipId.getLegalGuardian(); }
// the same as before
}
但是这样做,两个实体都被完全序列化并且响应如下所示:
$ curl "http://localhost:8080/guardianships/1_2"
{
"name" : "Cool father",
"student" : {
"name" : "Hans",
"new" : false
},
"legalGuardian" : {
"name" : "Peter",
"new" : false
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/guardianships/1_2"
},
"guardianship" : {
"href" : "http://localhost:8080/guardianships/1_2"
}
}
}
为了完整示例,我创建了一个可执行文件 sample project。
经过一番搜索,我发现了两种可能的方法来将 ID 关系公开为链接:
1.提供 RepresentationModelProcessor
实施 RepresentationModelProcessor
让我可以将自定义链接添加到响应表示。
@Component
public class GuardianshipProcessor
implements RepresentationModelProcessor<EntityModel<Guardianship>> {
@Autowired
private RepositoryEntityLinks repositoryEntityLinks;
@Override
public EntityModel<Guardianship> process(EntityModel<Guardianship> model) {
Link studentLink = repositoryEntityLinks.linkToItemResource(Student.class,
model.getContent().getGuardianshipId().getStudent().getId());
model.add(studentLink);
Link legalGuardianLink = repositoryEntityLinks.linkToItemResource(LegalGuardian.class,
model.getContent().getGuardianshipId().getLegalGuardian().getId());
model.add(legalGuardianLink);
return model;
}
}
$ curl "http://localhost:8080/guardianships/1_2"
{
"name" : "Cool father",
"_links" : {
"self" : {
"href" : "http://localhost:8080/guardianships/1_2"
},
"guardianship" : {
"href" : "http://localhost:8080/guardianships/1_2"
},
"student" : {
"href" : "http://localhost:8080/students/1"
},
"legalGuardian" : {
"href" : "http://localhost:8080/legalGuardians/2"
}
}
}
临:
- 完全匹配所需的响应表示
缺点:
- 更多的关联 classes 导致更多的实现
RepresentationModelProcessor
做或多或少相同的事情
2。配置 RepositoryRestConfiguration
以公开 ID 的
默认情况下,ID 不会被 Spring Data Rest 公开,虽然主题是关于嵌入式 ID,但这些也是 ID。此行为可由 class 配置 class。
@Configuration
public class RepositoryConfig implements RepositoryRestConfigurer {
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(Guardianship.class);
}
}
$ curl "http://localhost:8080/guardianships/1_2"
{
"guardianshipId" : {
"_links" : {
"student" : {
"href" : "http://localhost:8080/students/1"
},
"legalGuardian" : {
"href" : "http://localhost:8080/legalGuardians/2"
}
}
},
"name" : "Cool father",
"_links" : {
"self" : {
"href" : "http://localhost:8080/guardianships/1_2"
},
"guardianship" : {
"href" : "http://localhost:8080/guardianships/1_2"
}
}
}
临:
- 少“执行”
缺点:
- (以这种形式)与原始所需的响应表示不完全匹配(请参阅链接周围的
guardianshipId
-wrapper)
编辑
对于方法二:要公开使用嵌入式(复合)ID 的实体的所有 ID,可以采用如下方式:
@Configuration
public class RepositoryRestConfig implements RepositoryRestConfigurer {
@Autowired
Repositories repositories;
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
repositories.forEach(repository -> {
Field embeddedIdField =
ReflectionUtils.findField(repository, new AnnotationFieldFilter(EmbeddedId.class));
if (embeddedIdField != null) {
config.exposeIdsFor(repository);
}
});
}
}
简介
假设在带有 Spring Data Rest 模块的 Spring 启动应用程序中有两个主要实体(例如 Student
和 LegalGuardian
)。它们通过一个“关联实体”(例如 Guardianship
)连接,该实体由一个嵌入式 ID(例如 GuardianshipId
)标识。此外,这个嵌入的 id 包含与两个主要实体的关系(不是主要实体的 id - 实体本身)。
// The Main entities
@Entity
public class Student extends AbstractPersistable<Long> {
private String name;
@OneToMany(mappedBy = "guardianshipId.student")
private List<Guardianship> guardianships;
// getters and setters
}
@Entity
public class LegalGuardian extends AbstractPersistable<Long> {
private String name;
@OneToMany(mappedBy = "guardianshipId.legalGuardian")
private List<Guardianship> guardianships;
// getters and setters
}
// The association entity
@Entity
public class Guardianship implements Serializable {
@EmbeddedId
private GuardianshipId guardianshipId;
private String name;
// getters, setters, equals and hashCode
@Embeddable
public static class GuardianshipId implements Serializable {
@ManyToOne
private Student student;
@ManyToOne
private LegalGuardian legalGuardian;
// getters, setters, equals and hashCode
}
}
对于所有这些实体,存在单独的存储库:
StudentRepository : JpaRepository<Student, Long>
,LegalGuardianRepository : JpaRepository<LegalGuardian, Long>
和GuardianshipRepository : JpaRepository<Guardianship, Guardianship.GuardianshipId>
要通过 REST 通过 id 查询 GuardianshipRepository
的单个 Guardianship
s,还实现了 BackendIdConverter
(这样 id 看起来像 {studentId} _{legalGuardianId}).
如果请求关联实体的存储库,默认情况下嵌入的 id 本身(及其属性)不会被序列化,因此响应如下所示:
$ curl "http://localhost:8080/guardianships/1_2"
{
"name" : "Cool father",
"_links" : {
"self" : {
"href" : "http://localhost:8080/guardianships/1_2"
},
"guardianship" : {
"href" : "http://localhost:8080/guardianships/1_2"
}
}
}
Quesion/Problem
必须做什么,以便响应包含 链接 到在嵌入式 ID 中定义的实体,如下所示:
$ curl "http://localhost:8080/guardianships/1_2"
{
"name" : "Cool father",
"_links" : {
"self" : {
"href" : "http://localhost:8080/guardianships/1_2"
},
"guardianship" : {
"href" : "http://localhost:8080/guardianships/1_2"
},
"student" : {
"href" : "http://localhost:8080/guardianships/1_2/student"
},
"legalGuardian" : {
"href" : "http://localhost:8080/guardianships/1_2/legalGuardian"
}
}
}
(天真无成)Attempt/Try
第一个想法是通过委托嵌入的 id 使嵌套关系可访问:
@Entity
public class Guardianship implements Serializable {
@EmbeddedId
private GuardianshipId guardianshipId;
public Student getStudent() { return guardianshipId.getStudent(); }
public LegalGuardian getLegalGuardian() { return guardianshipId.getLegalGuardian(); }
// the same as before
}
但是这样做,两个实体都被完全序列化并且响应如下所示:
$ curl "http://localhost:8080/guardianships/1_2"
{
"name" : "Cool father",
"student" : {
"name" : "Hans",
"new" : false
},
"legalGuardian" : {
"name" : "Peter",
"new" : false
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/guardianships/1_2"
},
"guardianship" : {
"href" : "http://localhost:8080/guardianships/1_2"
}
}
}
为了完整示例,我创建了一个可执行文件 sample project。
经过一番搜索,我发现了两种可能的方法来将 ID 关系公开为链接:
1.提供 RepresentationModelProcessor
实施 RepresentationModelProcessor
让我可以将自定义链接添加到响应表示。
@Component
public class GuardianshipProcessor
implements RepresentationModelProcessor<EntityModel<Guardianship>> {
@Autowired
private RepositoryEntityLinks repositoryEntityLinks;
@Override
public EntityModel<Guardianship> process(EntityModel<Guardianship> model) {
Link studentLink = repositoryEntityLinks.linkToItemResource(Student.class,
model.getContent().getGuardianshipId().getStudent().getId());
model.add(studentLink);
Link legalGuardianLink = repositoryEntityLinks.linkToItemResource(LegalGuardian.class,
model.getContent().getGuardianshipId().getLegalGuardian().getId());
model.add(legalGuardianLink);
return model;
}
}
$ curl "http://localhost:8080/guardianships/1_2"
{
"name" : "Cool father",
"_links" : {
"self" : {
"href" : "http://localhost:8080/guardianships/1_2"
},
"guardianship" : {
"href" : "http://localhost:8080/guardianships/1_2"
},
"student" : {
"href" : "http://localhost:8080/students/1"
},
"legalGuardian" : {
"href" : "http://localhost:8080/legalGuardians/2"
}
}
}
临:
- 完全匹配所需的响应表示
缺点:
- 更多的关联 classes 导致更多的实现
RepresentationModelProcessor
做或多或少相同的事情
2。配置 RepositoryRestConfiguration
以公开 ID 的
默认情况下,ID 不会被 Spring Data Rest 公开,虽然主题是关于嵌入式 ID,但这些也是 ID。此行为可由 class 配置 class。
@Configuration
public class RepositoryConfig implements RepositoryRestConfigurer {
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(Guardianship.class);
}
}
$ curl "http://localhost:8080/guardianships/1_2"
{
"guardianshipId" : {
"_links" : {
"student" : {
"href" : "http://localhost:8080/students/1"
},
"legalGuardian" : {
"href" : "http://localhost:8080/legalGuardians/2"
}
}
},
"name" : "Cool father",
"_links" : {
"self" : {
"href" : "http://localhost:8080/guardianships/1_2"
},
"guardianship" : {
"href" : "http://localhost:8080/guardianships/1_2"
}
}
}
临:
- 少“执行”
缺点:
- (以这种形式)与原始所需的响应表示不完全匹配(请参阅链接周围的
guardianshipId
-wrapper)
编辑
对于方法二:要公开使用嵌入式(复合)ID 的实体的所有 ID,可以采用如下方式:
@Configuration
public class RepositoryRestConfig implements RepositoryRestConfigurer {
@Autowired
Repositories repositories;
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
repositories.forEach(repository -> {
Field embeddedIdField =
ReflectionUtils.findField(repository, new AnnotationFieldFilter(EmbeddedId.class));
if (embeddedIdField != null) {
config.exposeIdsFor(repository);
}
});
}
}