I have the Repo to interact with ES index:
#Repository
public interface RegDocumentRepo extends ElasticsearchRepository<RegDocument, String> {
}
RegDocument class is a POJO of reg-document index:
#Document(indexName = "reg-document")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class RegDocument {
#Id
String id;
#Field(type = FieldType.Nested, includeInParent = true)
private List<Map<String, Object>> attachments;
private String author;
#Field(type = FieldType.Nested, includeInParent = true)
private List<Map<String, Object>> classification;
private String content;
private String intent;
#Field(type = FieldType.Nested, includeInParent = true)
private List<Map<String, Object>> links;
private String name;
#Field(name = "publication_date")
private String publicationDate;
private Integer raiting;
private Long status;
private String title;
private String type;
private String version;
}
To hide my business-logic I use service:
#RequiredArgsConstructor
#Service
public class SearchServiceImpl {
#Autowired
RegDocumentRepo regDocumentRepo;
public RegDocument updateRating(String uuid, Integer rating) throws IOException {
final RegDocument regDocument = regDocumentRepo
.findById(uuid)
.orElseThrow(() -> new IOException(String.format("No document with %s id", uuid)));
Integer ratingFromDB = regDocument.getRaiting();
ratingFromDB = ratingFromDB == null ? rating : ratingFromDB + rating;
regDocument.setRaiting(ratingFromDB);
final RegDocument save = regDocumentRepo.save(regDocument);
return save;
}
}
So I had the such document in my ES index:
{
"_index" : "reg-document",
"_type" : "_doc",
"_id" : "9wEgQnQBKzq7IqBZMDaO",
"_score" : 1.0,
"_source" : {
"raiting" : null,
"attachments" : null,
"author" : null,
"type" : "answer",
"classification" : [
{
"code" : null,
"level" : null,
"name" : null,
"description" : null,
"id_parent" : null,
"topic_type" : null,
"uuid" : null
}
],
"intent" : null,
"version" : null,
"content" : "В 2019 году размер материнского капитала составляет 453026 рублей",
"name" : "Каков размер МСК в 2019 году?",
"publication_date" : "2020-08-26 06:49:10",
"rowkey" : null,
"links" : null,
"status" : 1
}
}
But after I update my ranking score, I have next structure:
{
"_index" : "reg-document",
"_type" : "_doc",
"_id" : "9wEgQnQBKzq7IqBZMDaO",
"_score" : 1.0,
"_source" : {
"raiting" : 4,
"type" : "answer",
"classification" : [
{
"code" : null,
"level" : null,
"name" : null,
"description" : null,
"id_parent" : null,
"topic_type" : null,
"uuid" : null
}
],
"content" : "В 2019 году размер материнского капитала составляет 453026 рублей",
"name" : "Каков размер МСК в 2019 году?",
"publication_date" : "2020-08-26 06:49:10",
"status" : 1
}
}
As you can see, Java service skip NULL values. But if the field is nested, null values were saved.
ElasticSearch version - 7.8.0
maven dependency for spring-data:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
So how can i SAVE null values, not skip them?
**
UDP
**
I have investigated spring-data-elasticsearch-4.0.0 dependency and find out, as Best Answer author said, that MappingElasticsearchConverter.java has following methods:
#Override
public void write(Object source, Document sink) {
Assert.notNull(source, "source to map must not be null");
if (source instanceof Map) {
// noinspection unchecked
sink.putAll((Map<String, Object>) source);
return;
}
Class<?> entityType = ClassUtils.getUserClass(source.getClass());
TypeInformation<?> type = ClassTypeInformation.from(entityType);
if (requiresTypeHint(type, source.getClass(), null)) {
typeMapper.writeType(source.getClass(), sink);
}
Optional<Class<?>> customTarget = conversions.getCustomWriteTarget(entityType, Map.class);
if (customTarget.isPresent()) {
sink.putAll(conversionService.convert(source, Map.class));
return;
}
ElasticsearchPersistentEntity<?> entity = type.getType().equals(entityType)
? mappingContext.getRequiredPersistentEntity(type)
: mappingContext.getRequiredPersistentEntity(entityType);
writeEntity(entity, source, sink, null);
}
This methods explain why nested data was saved as null and wasn't skip. It just put Map inside.
So the next method use reflection in such way. So if it is a null value, it's just skip it:
protected void writeProperties(ElasticsearchPersistentEntity<?> entity, PersistentPropertyAccessor<?> accessor,
MapValueAccessor sink) {
for (ElasticsearchPersistentProperty property : entity) {
if (!property.isWritable()) {
continue;
}
Object value = accessor.getProperty(property);
if (value == null) {
continue;
}
if (property.hasPropertyConverter()) {
ElasticsearchPersistentPropertyConverter propertyConverter = property.getPropertyConverter();
value = propertyConverter.write(value);
}
if (!isSimpleType(value)) {
writeProperty(property, value, sink);
} else {
Object writeSimpleValue = getWriteSimpleValue(value);
if (writeSimpleValue != null) {
sink.set(property, writeSimpleValue);
}
}
}
}
There is no official solution. So i have created a Jira ticket
The null values of the inner objects are stored, because this happens when the Map with null values for keys is stored.
Entity properties with a null value are not persisted by Spring Data Elasticsearch are not persisted as this it would store information that is not needed for saving/retrieving the data.
If you need the null values to be written, this would mean, that we'd need to add some flag to the #Field annotation for this, can you add an issue in Jira (https://jira.spring.io/projects/DATAES/issues) for this?
Edit: Implemented in versions 4.0.4.RELEASE and 4.1.0.RC1
Related
In the below code I want to generate addrId automatically and show it in the Person document, but the addrId is not showing up in the document.
#Document
public class Person {
#Id
String id;
List<Address> addresses;
}
public class Address {
#Id
String addrId;
String street;
}
public class Example {
public Person createAddress(Person person, Address addr) {
Set<Address> addresses = new HashSet<>();
addresses.add(addr);
Query query = new Query();
query.addCriteria(Criteria.where("id").is(id));
Person person = mongoTemplate.findOne(query, Person.class);
person.setAddresses(addresses);
return mongoTemplate.save(person);
}
}
Expected document with addrId:
{
"_id" : ObjectId("592c7029aafef820f432c5f3"),
"_class" : "tutorial.mongodb.documents.Person",
"addresses" : [{
"addrId" : ObjectId("321c7029aafed220f432d321"),
"street" : "London street"
}]
}
but addrId is not getting displayed as seen in the below document:
{
"_id" : ObjectId("592c7029aafef820f432c5f3"),
"_class" : "tutorial.mongodb.documents.Person",
"addresses" : [{
"street" : "London street"
}]
}
This works for me as expected:
public String createAddress(Person person, Address addr) {
addr.setAddrId(String.valueOf(UUID.randomUUID()));
List<Address > addrs = person.getAddresses();
addrs.add(addr);
person.setAddresses(addrs);
mongoTemplate.save(person);
return addr.getAddrId();
}
I'm running Spring 2.2.7 with spring-data-mongodb.
I've an entity called BaseSample stored in a samples MongoDb collection and I want to group records by minute from the created date and getting the average value collected. I don't know how to use the DateOperators.Minute in the group aggregation operation.
detailed explanation
#Data
#Document(collection = "samples")
#EqualsAndHashCode
public class BaseSample extends Message {
private float value;
private UdcUnitEnum unit;
private float accuracy;
public LocalDateTime recorded;
}
that extends a Message class
#Data
#EqualsAndHashCode
public class Message {
#Indexed
private String sensorUuId;
#Indexed
private String fieldUuId;
#Indexed
private String baseStationUuId;
#Indexed
private Date created;
}
on which I want to apply this query
db.samples.aggregate ([
{
$match: {
$and : [ {fieldUuId:"BS1F1"}, {unit: "DGC"}, {created : {$gte : ISODate("2020-05-30T17:00:00.0Z")}}, {created : {$lt : ISODate("2020-05-30T17:15:00.0Z")}}]
}
},
{
$group: {
_id : { $minute : "$created"},
date : {$first : {$dateToString:{date: "$created", format:"%Y-%m-%d"}}},
time : {$first : {$dateToString:{date: "$created", format:"%H:%M"}}},
unit : {$first : "$unit"},
data : { $avg : "$value"}
}
},
{
$sort: {date:1, time:1}
}
])
on the samples collection (exerpt)
{
"_id" : ObjectId("5ed296150af58a1c60c4f154"),
"value" : 90.85242462158203,
"unit" : "HPA",
"accuracy" : 0.6498473286628723,
"recorded" : ISODate("2020-05-30T17:21:25.850Z"),
"sensorUuId" : "458f0ffd-13f9-466d-81a1-8d2e1c808da9",
"fieldUuId" : "BS1F2",
"baseStationUuId" : "BS1",
"created" : ISODate("2020-05-30T17:21:25.777Z"),
"_class" : "org.open_si.udc_common.models.BaseSample"
}
{
"_id" : ObjectId("5ed296150af58a1c60c4f155"),
"value" : 40.84038162231445,
"unit" : "HPA",
"accuracy" : 0.030185099691152573,
"recorded" : ISODate("2020-05-30T17:21:25.950Z"),
"sensorUuId" : "b396264f-fcd5-4653-8ac8-358ca3a4cb87",
"fieldUuId" : "BS2F3",
"baseStationUuId" : "BS2",
"created" : ISODate("2020-05-30T17:21:25.868Z"),
"_class" : "org.open_si.udc_common.models.BaseSample"
}
I coded the following method to get the average value of samples grouped per minute for a selected unit type (degree, ...) in a selected field (sensors logical group)
public List aggregateFromField(String fieldUuId, UdcUnitEnum unit, LocalDateTime from, LocalDateTime to, Optional pageNumber, Optional pageSize){
Pageable paging = new PagingHelper(pageNumber, pageSize).getPaging();
MatchOperation fieldMatch = Aggregation.match(Criteria.where("fieldUuId").is(fieldUuId));
MatchOperation unitMatch = Aggregation.match(Criteria.where("unit").is(unit.name()));
MatchOperation fromDateMatch = Aggregation.match(Criteria.where("created").gte(from));
MatchOperation toDateMatch = Aggregation.match(Criteria.where("created").lt(to));
DateOperators.Minute minute = DateOperators.Minute.minuteOf("created");
GroupOperation group = Aggregation.group("created")
.first("created").as("date")
.first("created").as("time")
.first("unit").as("unit")
.avg("value").as("avg")
;
SortOperation sort = Aggregation.sort(Sort.by(Sort.Direction.ASC, "$date")).and(Sort.by(Sort.Direction.ASC, "$time"));
SkipOperation skip = Aggregation.skip(paging.getOffset());
LimitOperation limit = Aggregation.limit(paging.getPageSize());
Aggregation agg = Aggregation.newAggregation(
fieldMatch,
unitMatch,
fromDateMatch,
toDateMatch,
group,
sort,
skip,
limit
);
AggregationResults<SampleAggregationResult> results = mongoTemplate.aggregate(agg, mongoTemplate.getCollectionName(BaseSample.class), SampleAggregationResult.class);
return results.getMappedResults();
}
this result class is :
#Data
public class SampleAggregationResult {
private String date;
private String time;
private String unit;
private float data;
}
Any idea on using the DateOperators.Minute type in the agrregation group operation ?
thnaks in advance.
How configure Spring Data REST to remove entity association links (left only "self") on Collection resource response of Repository interface endpoints, without set exported=false on #ResResource annotation (need keep exported the endpoints)
We has entities where the _links part has the bigest size on the response:
On Item resource _ links are useful to navigate throught the associations.
But on Collection Resources , mainly on large collections, this information is not important and makes the response unnecesary biger .
We need change this response:
{
"_embedded" : {
"persons" : [ {
"id" : "bat_3191",
"name" : "B",
"_links" : { // 80% of response size !!
"self" : {
"href" : "http://localhost:8080/api/persons/bat_3191"
},
"orders" : {
"href" : "http://localhost:8080/api/persons/bat_3191/order"
},
"payments" : {
"href" : "http://localhost:8080/api/persons/bat_3191/payments"
},
"childrens" : {
"href" : "http://localhost:8080/api/persons/bat_3191/childrens"
},
"invoices" : {
"href" : "http://localhost:8080/api/persons/bat_3191/invoices"
},
"brands" : {
"href" : "http://localhost:8080/api/persons/bat_3191/brands"
},
}
},
{ person [2] }
...
{ person [N] }
]
},
"_links" : {
[page links]
}
To a only "self" on _links part:
{
"_embedded" : {
"persons" : [ {
"id" : "bat_3191",
"name" : "B",
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/persons/bat_3191"
}
}
}, {
"id" : "bat_2340",
"name" : "B",
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/persons/bat_2340"
}
}
If I wanted to control the hateos links in springboot I used a resource assembler. As an example:
#Autowired
private EmployeeAddressResourceAssembler assembler;
#GetMapping(value="/{empId}", produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<EmployeeAddressResource> getEmployeeAddress(#PathVariable Integer empId) {
EmployeeAddressItem employeeAddressItem = restTemplate.getForObject(
serviceUrl + "/employee/address/{empId}",
EmployeeAddressItem.class, empId);
return ResponseEntity.ok( assembler.toResource(employeeAddressItem) );
}
And then I used the resource assembler.
#Component
public class EmployeeAddressResourceAssembler
extends ResourceAssemblerSupport<EmployeeAddressItem, EmployeeAddressResource> {
public EmployeeAddressResourceAssembler() {
super(EmployeeAddressController.class, EmployeeAddressResource.class);
}
#Override
public EmployeeAddressResource toResource(EmployeeAddressItem item) {
// createResource(employeeAddressItem);
EmployeeAddressResource resource = createResourceWithId(item.getEmpId(), item);
resource.fromEmployeeAddressItem(item);
// … do further mapping
resource.add(linkTo(methodOn(EmployeeAddressController.class).deleteEmployeeAddress(item.getEmpId())).withRel("delete"));
return resource;
}
}
and the ResourceAssembler uses a Resource
public class EmployeeAddressResource extends ResourceSupport {
private Integer empId;
private String address1;
private String address2;
private String address3;
private String address4;
private String state;
private String country;
public void fromEmployeeAddressItem(EmployeeAddressItem item) {
this.empId = item.getEmpId();
this.address1 = item.getAddress1();
this.address2 = item.getAddress2();
this.address3 = item.getAddress3();
this.address4 = item.getAddress4();
this.state = item.getState();
this.country = item.getCountry();
}
All this at RestPracticeWithHateos
I'm having duplicate results on a collection with this simple model: an entity Module and an entity Page. A Module has a set of pages, and a Page belongs to the module.
This is set up with Spring Boot with Spring Data JPA and Spring Data Rest.
The full code is accessible on GitHub
Entities
Here's the code for the entities. Most setters removed for 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 the code for the Spring repositories, which is fairly 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 from 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 the db schema and some 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 INTO DT_MODULE (label, display_name) VALUES ('mod1', 'Module 1'), ('mod2', 'Module 2'), ('mod3', 'Module 3');
INSERT INTO DT_PAGE (name, action, description, module_id) VALUES ('page1', 'action1', 'desc1', 1);
That's about it. Now, I run thus from the command line to start the application: mvn spring-boot:run. After the application starts, I can query it's main endpoint like this:
Get API
$ 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"
}
}
}
Get all 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
}
}
Get all pages for one 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 as you can see, I'm getting the same page twice here. What's going on?
Bonus question: Why this works?
I was cleaning the code to submit this question, and in order to make it more compact, I moved the JPA Annotations on the Page entity to field level, like 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 the rest of the class remains the same. This can be seen on the same github repo on branch field-level.
As it turns out, executing the same request with that change to the API will render the expected result (after starting the server the same way I did before):
Get all pages for one 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"
}
}
} ]
}
}
This is causing your issue (Page Entity):
public void setModule(Module module) {
this.module = module;
this.module.addPage(this); //this line right here
}
Hibernate uses your setters to initialize the entity because you put the JPA annotations on getters.
Initialization sequence that causes the issue:
Module object created
Set Module properties (pages set is initialized)
Page object created
Add the created Page to Module.pages
Set Page properties
setModule is called on the Page object and this adds (addPage) the current Page to Module.pages the second time
You can put the JPA annotations on the fields and it will work, because setters won't be called during initialization (bonus question).
I had this issue and I just changed fetch=FetchType.EAGER to fetch=FetchType.LAZY
This solved my problem!
I've started working on a REST API using Spring. I'm using the tutorial project gs-accessing-data-rest-initial, which is easy to dowload via Spring Tool Suite, in order to get some stuff working as soon as possible.
I've exposed two related entities (aplicacion and registros_app), using PagingAndSortingRepository and annotated both with #RepositoryRestResource, which enables me to expose entities correctly. The result I'm getting when I query on aplicacion is
**GET http://localhost:8090/aplicacion**
{
"_links" : {
"self" : {
"href" : "http://localhost:8090/aplicacion/{?page,size,sort}",
"templated" : true
}
},
"_embedded" : {
"aplicacion" : [ {
"nombre" : "app1",
"_links" : {
"self" : {
"href" : "http://localhost:8090/aplicacion/2"
},
"registrosApp" : {
"href" : "http://localhost:8090/aplicacion/2/registrosApp"
},
"tipoRegistrosApp" : {
"href" : "http://localhost:8090/aplicacion/2/tipoRegistrosApp"
}
}
}, {
"nombre" : "app2",
"_links" : {
"self" : {
"href" : "http://localhost:8090/aplicacion/1"
},
"registrosApp" : {
"href" : "http://localhost:8090/aplicacion/1/registrosApp"
},
"tipoRegistrosApp" : {
"href" : "http://localhost:8090/aplicacion/1/tipoRegistrosApp"
}
}
} ]
},
"page" : {
"size" : 20,
"totalElements" : 2,
"totalPages" : 1,
"number" : 0
}
}
Which is exactly what I've expected to obtain. So, I was expecting to get the same when I navigate to registrosApp, in terms of pagination; however, when I perform a get on any registrosApp link, what I retrieve from the query is
**GET http://localhost:8090/aplicacion/2/registrosApp**
{
"_embedded" : {
"registrosapp" : [ {
"datos" : "{\"FechaInicio\":\"2014-09-16 18:08:44\",\"UsoMemoria\":\"UsedMemory:3 FreeMemory:491 Total Memory:495 Max Memory:989 \",\"InfoPool\":\"Active: 2\"}",
"fecha_hora" : "2014-09-17T14:04:07.000+0000",
"codTipoRegistro" : 1,
"_links" : {
"self" : {
"href" : "http://localhost:8090/registrosApp/605"
},
"aplicacion" : {
"href" : "http://localhost:8090/registrosApp/605/aplicacion"
}
}
},{
"datos" : "{\"FechaInicio\":\"2014-09-16 18:08:44\",\"UsoMemoria\":\"UsedMemory:3 FreeMemory:491 Total Memory:495 Max Memory:989 \",\"InfoPool\":\"Active: 2\"}",
"fecha_hora" : "2014-09-17T14:04:07.000+0000",
"codTipoRegistro" : 1,
"_links" : {
"self" : {
"href" : "http://localhost:8090/registrosApp/667"
},
"aplicacion" : {
"href" : "http://localhost:8090/registrosApp/667/aplicacion"
}
}
} ]
}
}
Which is not actually paginated. I need to get a paginated json when I navigate across links because registrosApp table grows very quickly. ¿What can I do about it?
Here is the code for my registrosApp and aplicacion repository
#RepositoryRestResource(collectionResourceRel = "registrosapp", path = "registrosApp")
public interface RegistrosAppRepository extends PagingAndSortingRepository<RegistrosApp, Long> {
}
#RepositoryRestResource(collectionResourceRel = "aplicacion", path = "aplicacion")
public interface AplicacionRepository extends PagingAndSortingRepository<Aplicacion, Long> {
//List<Person> findByLastName(#Param("name") String name);
}
And those are the entities I've defined
#Entity
#Table(name = "registros_app")
public class RegistrosApp {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long idRegistrosApp;
private String datos;
private Date fecha_hora;
private long codTipoRegistro;
public long getCodTipoRegistro() {
return codTipoRegistro;
}
public void setCodTipoRegistro(long codTipoRegistro) {
this.codTipoRegistro = codTipoRegistro;
}
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "idAplicacion", nullable = false, insertable = false, updatable = false)
Aplicacion aplicacion;
// private long idAplicacion;
/*
* public long getRegistros_app() { return idAplicacion; }
*
* public void setRegistros_app(long registros_app) { this.idAplicacion =
* registros_app; }
*/
public String getDatos() {
return datos;
}
public void setDatos(String datos) {
this.datos = datos;
}
public Date getFecha_hora() {
return fecha_hora;
}
public void setFecha_hora(Date fecha_hora) {
this.fecha_hora = fecha_hora;
}
}
#Entity
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Aplicacion {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long aplicacionId;
private String nombre;
//relaciones uno a varios
//relacion con la tabla registros_app
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "idAplicacion", nullable = false)
private Set<RegistrosApp> registrosApp = null;
//relacion con la tabla tipo_registro_app
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "idApp", nullable = false)
private Set<TipoRegistrosApp> tipoRegistrosApp = null;
public Set<TipoRegistrosApp> getTipoRegistrosApp() {
return tipoRegistrosApp;
}
public void setTipoRegistrosApp(Set<TipoRegistrosApp> tipoRegistrosApp) {
this.tipoRegistrosApp = tipoRegistrosApp;
}
#JsonProperty
public Set<RegistrosApp> getRegistrosApp() {
return registrosApp;
}
/**
* Sets list of <code>Address</code>es.
*/
public void setRegistrosApp(Set<RegistrosApp> rapps) {
this.registrosApp= rapps;
}
#JsonProperty
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
}
You can notice that I have a #onetomany annotation between aplicacion and registrosapp in my entities.
TL;DR When I query directly on registrosapp I get a paginated result as I expect. The problem here is when I navigate between related entities, I'm not getting the pagination information I need. ¿What can I do in order to get pagination when I navigate across entities? Any help with this will be truly appreciated. Thanks in advance.
I will answer myself in order to get this question useful for someone else who is struggling with this problem. This answer is closely related to - Spring Data Rest Pageable Child Collection -
What I've done is to set a method within RegistrosAppRepository, so it stays like this
#RepositoryRestResource(collectionResourceRel = "registrosapp", path = "registrosApp")
public interface RegistrosAppRepository extends PagingAndSortingRepository<RegistrosApp, Long> {
#RestResource(path = "byAplicacion", rel = "byAplicacion")
public Page<RegistrosApp> findByAplicacion(#Param("aplicacion_id") Aplicacion aplicacion, Pageable p);
}
Then I hide the link to registrosApp which appears in aplicacion, by setting the annotation #RestResource(exported=false) before the Set of registrosApp. So the aplicacion entity stays like this
#Entity
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Aplicacion {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long aplicacionId;
private String nombre;
//relaciones uno a varios
//relacion con la tabla registros_app
#RestResource(exported=false)
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "idAplicacion", nullable = false)
private Set<RegistrosApp> registrosApp = null;
//relacion con la tabla tipo_registro_app
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "idApp", nullable = false)
private Set<TipoRegistrosApp> tipoRegistrosApp = null;
public Set<TipoRegistrosApp> getTipoRegistrosApp() {
return tipoRegistrosApp;
}
public void setTipoRegistrosApp(Set<TipoRegistrosApp> tipoRegistrosApp) {
this.tipoRegistrosApp = tipoRegistrosApp;
}
#JsonProperty
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
}
finally, I'm able to navigate between those entities this way:
**GET http://localhost:8090/registrosApp/search/byAplicacion?aplicacion_id=2&page=1&size=1**
{
"_links" : {
"next" : {
"href" : "http://localhost:8090/registrosApp/search/byAplicacion?aplicacion_id=2&page=2&size=1"
},
"prev" : {
"href" : "http://localhost:8090/registrosApp/search/byAplicacion?aplicacion_id=2&page=0&size=1"
},
"self" : {
"href" : "http://localhost:8090/registrosApp/search/byAplicacion?aplicacion_id=2&page=1&size=1{&sort}",
"templated" : true
}
},
"_embedded" : {
"registrosapp" : [ {
"datos" : "{\"FechaInicio\":\"2014-09-16 18:08:44\",\"UsoMemoria\":\"UsedMemory:2 FreeMemory:492 Total Memory:495 Max Memory:989 \",\"InfoPool\":\"Active: 2\"}",
"fecha_hora" : "2014-09-17T14:04:07.000+0000",
"codTipoRegistro" : 1,
"_links" : {
"self" : {
"href" : "http://localhost:8090/registrosApp/593"
},
"aplicacion" : {
"href" : "http://localhost:8090/registrosApp/593/aplicacion"
}
}
} ]
},
"page" : {
"size" : 1,
"totalElements" : 56,
"totalPages" : 56,
"number" : 1
}
}
and the link in aplicacion doesn't show the registrosApp link whithin the json:
**GET http://localhost:8090/aplicacion**
{
"_links" : {
"self" : {
"href" : "http://localhost:8090/aplicacion{?page,size,sort}",
"templated" : true
}
},
"_embedded" : {
"aplicacion" : [ {
"nombre" : "app1",
"_links" : {
"self" : {
"href" : "http://localhost:8090/aplicacion/2"
},
"tipoRegistrosApp" : {
"href" : "http://localhost:8090/aplicacion/2/tipoRegistrosApp"
},
"aplicacion" : {
"href" : "http://localhost:8090/aplicacion/2/aplicacion"
}
}
}, {
"nombre" : "app2",
"_links" : {
"self" : {
"href" : "http://localhost:8090/aplicacion/1"
},
"tipoRegistrosApp" : {
"href" : "http://localhost:8090/aplicacion/1/tipoRegistrosApp"
},
"aplicacion" : {
"href" : "http://localhost:8090/aplicacion/1/aplicacion"
}
}
} ]
},
"page" : {
"size" : 20,
"totalElements" : 2,
"totalPages" : 1,
"number" : 0
}
}