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