Getting duplicate items when querying a collection with Spring Data Rest -
i'm having duplicate results on collection simple model: entity module , entity page. module has set of pages, , page belongs module.
this set spring boot spring data jpa , spring data rest.
the full code accessible on github
entities
here's code entities. setters removed brevity:
module.java
@entity @table(name = "dt_module") public class module {   private long id;   private string label;   private string displayname;   private set<page> pages;    @id   public long getid() {     return id;   }    public string getlabel() {     return label;   }    public string getdisplayname() {     return displayname;   }    @onetomany(mappedby = "module")   public set<page> getpages() {     return pages;   }    public void addpage(page page) {     if (pages == null) {       pages = new hashset<>();     }     pages.add(page);     if (page.getmodule() != this) {       page.setmodule(this);     }   }    @override   public boolean equals(object o) {     if (this == o) return true;     if (o == null || getclass() != o.getclass()) return false;     module module = (module) o;     return objects.equals(label, module.label) && objects.equals(displayname, module.displayname);   }    @override   public int hashcode() {     return objects.hash(label, displayname);   } }   page.java
@entity @table(name = "dt_page") public class page {   private long id;   private string name;   private string action;   private string description;   private module module;    @id   public long getid() {     return id;   }    public string getname() {     return name;   }    public string getaction() {     return action;   }    public string getdescription() {     return description;   }    @manytoone   public module getmodule() {     return module;   }    public void setmodule(module module) {     this.module = module;     this.module.addpage(this);   }    @override   public boolean equals(object o) {     if (this == o) return true;     if (o == null || getclass() != o.getclass()) return false;     page page = (page) o;     return objects.equals(name, page.name) &&         objects.equals(action, page.action) &&         objects.equals(description, page.description) &&         objects.equals(module, page.module);   }    @override   public int hashcode() {     return objects.hash(name, action, description, module);   } }   repositories
now code spring repositories, simple:
modulerepository.java
@repositoryrestresource(collectionresourcerel = "module", path = "module") public interface modulerepository extends pagingandsortingrepository<module, long> { }   pagerepository.java
@repositoryrestresource(collectionresourcerel = "page", path = "page") public interface pagerepository extends pagingandsortingrepository<page, long> { }   config
the configuration comes 2 files:
application.java
@enablejparepositories @springbootapplication public class application {   public static void main(string[] args) {     springapplication.run(application.class, args);   } }   application.properties
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   database
finally db schema , test data:
schema.sql
drop table if exists dt_page; drop table if exists dt_module;  create table dt_module (   id identity  primary key,   label varchar(30) not null,   display_name varchar(40) not null );  create table dt_page (   id identity primary key,   name varchar(50) not null,   action varchar(50) not null,   description varchar(255),   module_id bigint not null references dt_module(id) );   data.sql
insert dt_module (label, display_name) values ('mod1', 'module 1'), ('mod2', 'module 2'), ('mod3', 'module 3'); insert dt_page (name, action, description, module_id) values ('page1', 'action1', 'desc1', 1);   that's it. now, run command line start application: mvn spring-boot:run.  after application starts, can query it's main endpoint this:
$ curl http://localhost:8080/api   response  {   "_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"     }   } }   modules  curl http://localhost:8080/api/module   response  {   "_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   } }   pages 1 module  curl http://localhost:8080/api/module/1/pages   response  {   "_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"         }       }     } ]   } }   so can see, i'm getting same page twice here. what's going on?
bonus question: why works?
i cleaning code submit question, , in order make more compact, moved jpa annotations on page entity field level, this:
page.java
@entity @table(name = "dt_page")  public class page {   @id   private long id;   private string name;   private string action;   private string description;    @manytoone   private module module;   ...   all rest of class remains same. can seen on same github repo on branch field-level.
as turns out, executing same request change api render expected result (after starting server same way did before):
pages 1 modulecurl http://localhost:8080/api/module/1/pages   response  {   "_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"         }       }     } ]   } }      
this causing issue (page entity):
  public void setmodule(module module) {     this.module = module;     this.module.addpage(this); //this line right here   }   hibernate uses setters initialize entity because put jpa annotations on getters.
initialization sequence causes issue:
- module object created
 - set module properties (pages set initialized)
 - page object created
 - add created page module.pages
 - set page properties
 - setmodule called on page object , adds (addpage) current page module.pages second time
 
you can put jpa annotations on fields , work, because setters won't called during initialization (bonus question).
Comments
Post a Comment