Spring boot 使用Spring数据Rest查询集合时获取重复项
我用这个简单的模型在一个集合上得到了重复的结果:一个实体Spring boot 使用Spring数据Rest查询集合时获取重复项,spring-boot,spring-data-jpa,spring-data-rest,Spring Boot,Spring Data Jpa,Spring Data Rest,我用这个简单的模型在一个集合上得到了重复的结果:一个实体模块和一个实体页面。模块有一组页面,而页面属于该模块 这是用和设置的 完整的代码可以在 实体 这是实体的代码。为了简洁起见,删除了大多数setter: Module.java @实体 @表(name=“dt_模块”) 公共类模块{ 私人长id; 私有字符串标签; 私有字符串显示名; 专用设置页面; @身份证 公共长getId(){ 返回id; } 公共字符串getLabel(){ 退货标签; } 公共字符串getDisplayName(){
模块
和一个实体页面
。模块
有一组页面,而页面
属于该模块
这是用和设置的
完整的代码可以在
实体
这是实体的代码。为了简洁起见,删除了大多数setter:
Module.java
@实体
@表(name=“dt_模块”)
公共类模块{
私人长id;
私有字符串标签;
私有字符串显示名;
专用设置页面;
@身份证
公共长getId(){
返回id;
}
公共字符串getLabel(){
退货标签;
}
公共字符串getDisplayName(){
返回显示名;
}
@OneToMany(mappedBy=“模块”)
公共集getPages(){
返回页面;
}
公共无效添加页(第页){
如果(页面==null){
pages=新HashSet();
}
页面。添加(第页);
如果(page.getModule()!=此){
第页设置模块(本);
}
}
@凌驾
公共布尔等于(对象o){
如果(this==o)返回true;
如果(o==null | | getClass()!=o.getClass())返回false;
模块模块=(模块)o;
返回Objects.equals(label,module.label)和&Objects.equals(displayName,module.displayName);
}
@凌驾
公共int hashCode(){
返回Objects.hash(标签、显示名);
}
}
Page.java
@实体
@表(name=“dt_页”)
公共类页面{
私人长id;
私有字符串名称;
私人串动作;
私有字符串描述;
专用模块;
@身份证
公共长getId(){
返回id;
}
公共字符串getName(){
返回名称;
}
公共字符串getAction(){
返回动作;
}
公共字符串getDescription(){
返回说明;
}
@许多酮
公共模块getModule(){
返回模块;
}
公共无效设置模块(模块){
this.module=模块;
this.module.addPage(本);
}
@凌驾
公共布尔等于(对象o){
如果(this==o)返回true;
如果(o==null | | getClass()!=o.getClass())返回false;
第页=(第页)o;
返回Objects.equals(name,page.name)&&
Objects.equals(action,page.action)&&
Objects.equals(说明,第页说明)&&
Objects.equals(模块,page.module);
}
@凌驾
公共int hashCode(){
返回Objects.hash(名称、操作、描述、模块);
}
}
存储库
现在,Spring存储库的代码非常简单:
ModuleRepository.java
@RepositoryRestResource(collectionResourceRel=“module”,path=“module”)
公共接口模块存储库扩展了分页和排序存储库{
}
PageRepository.java
@RepositoryRestResource(collectionResourceRel=“page”,path=“page”)
公共接口PageRepository扩展了PagingAndSortingRepository{
}
配置
配置来自2个文件:
Application.java
@EnableJpaRepositories
@SpringBoot应用程序
公共类应用程序{
公共静态void main(字符串[]args){
SpringApplication.run(Application.class,args);
}
}
应用程序属性
数据库
最后是db模式和一些测试数据:
schema.sql
如果存在dt\U页面,则删除表格;
如果存在dt_模块,则删除表格;
创建表DT_模块(
id标识主键,
标签varchar(30)不为空,
显示名称varchar(40)不为空
);
创建表DT_页面(
id标识主键,
名称varchar(50)不为空,
动作varchar(50)不为空,
描述varchar(255),
模块id bigint非空引用dt_模块(id)
);
data.sql
插入DT_模块(标签、显示_名称)值('mod1','mod1','mod2','mod2','mod2','mod3','mod3');
在DT_页面(名称、操作、描述、模块id)中插入值('page1','action1','desc1',1);
就这样。现在,我从命令行开始运行这个应用程序:mvn-spring-boot:run
。应用程序启动后,我可以像这样查询它的主要端点:
获取API
$curlhttp://localhost:8080/api
回应
获取所有模块
curlhttp://localhost:8080/api/module
回应
获取一个模块的所有页面
curlhttp://localhost:8080/api/module/1/pages
回应
正如你所看到的,我在这里得到了两次相同的页面。发生什么事了
奖金问题:为什么这样做有效?
我正在清理代码以提交此问题,为了使其更加紧凑,我将页面
实体上的JPA注释移动到字段级别,如下所示:
Page.java
@实体
@表(name=“dt_页”)
公共类页面{
@身份证
私人长id;
私有字符串名称;
私人串动作;
私有字符串描述;
@许多酮
专用模块;
...
该类的所有其余部分都保持不变。这可以在同一个github repo on分支上看到
事实证明,通过对API的更改执行相同的请求将呈现预期的结果(在以与我之前相同的方式启动服务器之后):
获取一个模块的所有页面
curlhttp://localhost:8080/api/module/1/pages
回应
这导致您的问题(页面实体): Hibernate使用setter来初始化实体,因为您将JPA注释放在getter上 导致问题的初始化顺序:
您可以将JPA注释放在字段上,它会起作用,因为在初始化期间不会调用setter(额外问题)。我遇到了这个问题,我刚刚将
fetch=FetchType.EAGER
更改为fetch
spring.jpa.database = H2
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.generate-ddl=false
spring.jpa.hibernate.ddl-auto=validate
spring.datasource.initialize=true
spring.datasource.url=jdbc:h2:mem:demo;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.data.rest.basePath=/api
{
"_links" : {
"page" : {
"href" : "http://localhost:8080/api/page{?page,size,sort}",
"templated" : true
},
"module" : {
"href" : "http://localhost:8080/api/module{?page,size,sort}",
"templated" : true
},
"profile" : {
"href" : "http://localhost:8080/api/alps"
}
}
}
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/module"
}
},
"_embedded" : {
"module" : [ {
"label" : "mod1",
"displayName" : "Module 1",
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/module/1"
},
"pages" : {
"href" : "http://localhost:8080/api/module/1/pages"
}
}
}, {
"label" : "mod2",
"displayName" : "Module 2",
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/module/2"
},
"pages" : {
"href" : "http://localhost:8080/api/module/2/pages"
}
}
}, {
"label" : "mod3",
"displayName" : "Module 3",
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/module/3"
},
"pages" : {
"href" : "http://localhost:8080/api/module/3/pages"
}
}
} ]
},
"page" : {
"size" : 20,
"totalElements" : 3,
"totalPages" : 1,
"number" : 0
}
}
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/module/1/pages"
}
},
"_embedded" : {
"page" : [ {
"name" : "page1",
"action" : "action1",
"description" : "desc1",
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/page/1"
},
"module" : {
"href" : "http://localhost:8080/api/page/1/module"
}
}
}, {
"name" : "page1",
"action" : "action1",
"description" : "desc1",
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/page/1"
},
"module" : {
"href" : "http://localhost:8080/api/page/1/module"
}
}
} ]
}
}
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/module/1/pages"
}
},
"_embedded" : {
"page" : [ {
"name" : "page1",
"action" : "action1",
"description" : "desc1",
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/page/1"
},
"module" : {
"href" : "http://localhost:8080/api/page/1/module"
}
}
} ]
}
}
public void setModule(Module module) {
this.module = module;
this.module.addPage(this); //this line right here
}