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上

导致问题的初始化顺序:

  • 已创建模块对象
  • 设置模块属性(页面集已初始化)
  • 已创建页面对象
  • 将创建的页面添加到Module.pages
  • 设置页面属性
  • 在Page对象上调用setModule,这将第二次将当前页添加到Module.pages(添加页面)

  • 您可以将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
      }