Spring Data Rest adding excerpt projection switches off lazy fetching - loading

I'm new to Spring Data Rest and trying to play around with its basic concepts. Everything works well so far, but few days ago I noticed that the application performance suddenly dropped after putting the projections into business.
These are my entities, repositories and the projection
#Entity
public class Item {
#Id
#GeneratedValue(strategy = TABLE)
private long id;
private String code;
private String name;
#ManyToOne(targetEntity=Category.class)
#JoinColumn(name="category_id", referencedColumnName="id")
private Category category;
//getters & setters
}
#Entity
public class Category {
#Id
#GeneratedValue(strategy = TABLE)
private long id;
private String name;
#OneToMany(mappedBy="category", targetEntity=Item.class, fetch=FetchType.LAZY)
private Set<Item> items;
//getters & setters
}
#RepositoryRestResource(excerptProjection=ItemExcerpt.class)
public interface ItemRepository extends CrudRepository<Item, Long>{
}
#RepositoryRestResource
public interface CategoryRepository extends CrudRepository<Category, Long>{
}
#Projection(name="excerpt", types=Item.class)
public interface ItemExcerpt {
String getName();
}
So, all worked fine untill I added the excerpt projection to the ItemRepository #RepositoryRestResource(excerptProjection=ItemExcerpt.class)
Before doing this, when I hit http://localhost:9191/categories Hibernate output was as I expected it to be:
select
category0_.id as id1_0_,
category0_.name as name2_0_
from
category category0_
This is the output that I get after adding excerptProjection=ItemExcerpt.class
Hibernate:
select
category0_.id as id1_0_,
category0_.name as name2_0_
from
category category0_
Hibernate:
select
items0_.category_id as category4_1_0_,
items0_.id as id1_1_0_,
items0_.id as id1_1_1_,
items0_.category_id as category4_1_1_,
items0_.code as code2_1_1_,
items0_.name as name3_1_1_
from
item items0_
where
items0_.category_id=?
My conclusion is that the excerpt projection makes lazy fetching being ignored on #OneToMany relationship, which leads to a performance drop.
Does anyone know a way to bypass this issue, or is this maybe an expected behaviour?

It isn't exactly that excerpt projections make lazy fetching be ignored. More specifically, it is that an excerpt projection is telling spring data to include the excerpted data wherever a collection resource would be returned.
From the reference docs Projections Excerpts, "An excerpt is a projection that is applied to a resource collection automatically.". The unfortunate side-effect of this is that spring-hateoas then ignores that property and instead puts in the hypermedia link to the resource instead. There is no combination of annotations that i have found that will correct this behavior for you while preserving the output. #JsonIgnore will not prevent the extra queries. #RestResource(exported = false) will prevent the queries, but will also prevent the hypermedia link.

Related

how to write the JpaRepository for tables which has composite keys

Please refer attached screenshot to understand the table structure.
Empd_Id is the primary key in 'Employee' table which in turn becomes as a part of composite key along with 'product_id' in table called 'product'.
Any employee can have multiple products so in that case it becomes 'One-to-Many' relationship between 'Employee-Product' tables. Now I'm confused whether I need to write just 1 JpaRepository interface i.e. for employee or 2 JpaRepository interfaces (1 for Employee and another for Product). My gut feeling is just 1 interface for Employee table but how???
Following is my code snippet:-
1st JPA repository interface
public interface MyRepository extends JpaRepository<Product, EmpProd> {
}
Entity:-
#Entity
#Table(name="product")
public class Product{
#EmbeddedId
private EmpProd empProd;
#Column(name="product_name")
private String commerceUserId;
#Column(name="description")
private String description;
For composite keys:-
#Embeddable
public class EmpProd implements Serializable{
private static final long serialVersionUID = 1L;
#NotNull
#Column(name="emp_id")
private String empId;
#NotNull
#Column(name="product_id")
private String productId;
2nd Jpa repository interface
public interface MyMainDataRepository extends JpaRepository<Employee, String> {
}
Entity class:-
#Entity
#Table(name="employee")
public class Employee{
#Id
#NotNull
#Column(name="emp_id")
private String empId;
#Column(name="first_name")
private String firstName;
Though, I have written 2 separate JPA repositories, I strongly believe there will be need for just 1, the main one i.e.
public interface MyMainDataRepository extends JpaRepository {
}
But I do not know to related both entity classes and fetch data from using single Jpa repository as I'm new to Spring Data JPA. I would really appreciate if someone can help me here. Thanks
The two entities Product and Employee don't have any connection as far as JPA is concerned. Therefore you can't access both through a single repository.
If for example, Product would have an actual reference to an Employee you could use a ProductRepository to load Products and navigate from there to the referenced Employees.
But even if that might be feasible, I'd guess that Product and Employee should be considered different aggregates and therefore, should have their own repository each. See Are you supposed to have one repository per table in JPA? for more information on that question.
Given the entities, your repositories look just fine. Note that the entities do look atypical due to the use of String productId instead of Product product.
If you wanted to fetch the employee details, you need the following interface,
public interface MyMainDataRepository extends JpaRepository<Employee, String> {
}
If you wanted to fetch the product details, you need the following interface,
public interface MyRepository extends JpaRepository<Product, EmpProd> {
}
The employee is related to product table, the iteration happens via product and related employees. From this, you can not access the employee table directly and retrieve the employee results from MyRepository interface.

Spring Data Rest - sort by nested property

I have a database service using Spring Boot 1.5.1 and Spring Data Rest. I am storing my entities in a MySQL database, and accessing them over REST using Spring's PagingAndSortingRepository. I found this which states that sorting by nested parameters is supported, but I cannot find a way to sort by nested fields.
I have these classes:
#Entity(name = "Person")
#Table(name = "PERSON")
public class Person {
#ManyToOne
protected Address address;
#ManyToOne(targetEntity = Name.class, cascade = {
CascadeType.ALL
})
#JoinColumn(name = "NAME_PERSON_ID")
protected Name name;
#Id
protected Long id;
// Setter, getters, etc.
}
#Entity(name = "Name")
#Table(name = "NAME")
public class Name{
protected String firstName;
protected String lastName;
#Id
protected Long id;
// Setter, getters, etc.
}
For example, when using the method:
Page<Person> findByAddress_Id(#Param("id") String id, Pageable pageable);
And calling the URI http://localhost:8080/people/search/findByAddress_Id?id=1&sort=name_lastName,desc, the sort parameter is completely ignored by Spring.
The parameters sort=name.lastName and sort=nameLastName did not work either.
Am I forming the Rest request wrong, or missing some configuration?
Thank you!
The workaround I found is to create an extra read-only property for sorting purposes only. Building on the example above:
#Entity(name = "Person")
#Table(name = "PERSON")
public class Person {
// read only, for sorting purposes only
// #JsonIgnore // we can hide it from the clients, if needed
#RestResource(exported=false) // read only so we can map 2 fields to the same database column
#ManyToOne
#JoinColumn(name = "address_id", insertable = false, updatable = false)
private Address address;
// We still want the linkable association created to work as before so we manually override the relation and path
#RestResource(exported=true, rel="address", path="address")
#ManyToOne
private Address addressLink;
...
}
The drawback for the proposed workaround is that we now have to explicitly duplicate all the properties for which we want to support nested sorting.
LATER EDIT: another drawback is that we cannot hide the embedded property from the clients. In my original answer, I was suggesting we can add #JsonIgnore, but apparently that breaks the sort.
I debugged through that and it looks like the issue that Alan mentioned.
I found workaround that could help:
Create own controller, inject your repo and optionally projection factory (if you need projections). Implement get method to delegate call to your repository
#RestController
#RequestMapping("/people")
public class PeopleController {
#Autowired
PersonRepository repository;
//#Autowired
//PagedResourcesAssembler<MyDTO> resourceAssembler;
#GetMapping("/by-address/{addressId}")
public Page<Person> getByAddress(#PathVariable("addressId") Long addressId, Pageable page) {
// spring doesn't spoil your sort here ...
Page<Person> page = repository.findByAddress_Id(addressId, page)
// optionally, apply projection
// to return DTO/specifically loaded Entity objects ...
// return type would be then PagedResources<Resource<MyDTO>>
// return resourceAssembler.toResource(page.map(...))
return page;
}
}
This works for me with 2.6.8.RELEASE; the issue seems to be in all versions.
From Spring Data REST documentation:
Sorting by linkable associations (that is, links to top-level resources) is not supported.
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#paging-and-sorting.sorting
An alternative that I found was use #ResResource(exported=false).
This is not valid (expecially for legacy Spring Data REST projects) because avoid that the resource/entity will be loaded HTTP links:
JacksonBinder
BeanDeserializerBuilder updateBuilder throws
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of ' com...' no String-argument constructor/factory method to deserialize from String value
I tried activate sort by linkable associations with help of annotations but without success because we need always need override the mappPropertyPath method of JacksonMappingAwareSortTranslator.SortTranslator detect the annotation:
if (associations.isLinkableAssociation(persistentProperty)) {
if(!persistentProperty.isAnnotationPresent(SortByLinkableAssociation.class)) {
return Collections.emptyList();
}
}
Annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface SortByLinkableAssociation {
}
At project mark association as #SortByLinkableAssociation:
#ManyToOne
#SortByLinkableAssociation
private Name name;
Really I didn't find a clear and success solution to this issue but decide to expose it to let think about it or even Spring team take in consideration to include at nexts releases.
Please see https://stackoverflow.com/a/66135148/6673169 for possible workaround/hack, when we wanted sorting by linked entity.

Get entity property with Spring JPA

I'm using Spring JPA in my DAO layer. I have an entity Projet having inside an entity property Client:
Project.java
#Entity
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int projetId;
private String libelle;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="client_id")
private Client client;
// ... constructors, getters & setters
}
Client.java
#Entity
public class Client {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int clientId;
private String denomination;
// ... constructors, getters & setters
}
in my DAO interface I have the following specifications:
ProjetDao.java
#Repository
#Transactional
public interface ProjetDao extends CrudRepository<Projet, Integer> {
#Transactional
public Projet findByLibelle(String libelle);
#Transactional
public Projet findByProjetId(int projetId);
}
My question is: How can I specify in my DAO interface a method that will return all clients distinct in List<Client>?
From the documentation and JIRA:
List<Project> findAllDistinctBy();
The query builder mechanism built into Spring Data repository infrastructure is useful for building constraining queries over entities of the repository. The mechanism strips the prefixes find…By, read…By, query…By, count…By, and get…By from the method and starts parsing the rest of it. The introducing clause can contain further expressions such as a Distinct to set a distinct flag on the query to be created. However, the first By acts as delimiter to indicate the start of the actual criteria. At a very basic level you can define conditions on entity properties and concatenate them with And and Or.
You are dealing with a one-to-one relationship, in this case I guess the list that you need is not really related to specific project, you just want a distinct list of clients.
You will need to create another repository (ClientRepository) for the Client entity and add a findAllDistinct method in this repository.

Spring JPA one to many denormalized count field

I have two entities, Books and Comments, in a one to many relationship (one book can have many comments). I want to be able to list books and number of comments about a book. I want it denormalized, meaning the books entity will have a counter that has number of comments for that book, and it will be updated every time a comment is entered (just playing with the concept, no need to discuss about the need of denormalizing here).
I think (correct me if I am wrong) this could be easily done with a trigger in the database (whenever a new comment is created, update a counter in the books table to the corresponding bookId), but for the sake of learning I want to do it through JPA, if it makes sense.
What I have so far: //omitted some annotations, just general info
Boks entity:
#Entity
public class Books {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private String author;
private Long numComments;
// getters and setters...
}
Comments entity:
#Entity
public class Comments {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String comment;
private Long authorId;
private Long bookId;
// getters and setters...
}
Books repository: I added here a query to perform the update
/**
* Spring Data JPA repository for the Books entity.
*/
public interface BooksRepository extends JpaRepository<Books,Long> {
#Modifying
#Query("UPDATE Books v SET v.numComments = v.numComments + 1 WHERE v.id = :bookId")
int updateCounter(#Param("bookId")Long bookId);
}
And now the question: What next? I think I can put the update of the Books entity annotating with #PostPersist a method of the entity Comments, but I have been unsuccessful so far. I can imagine something like this:
#PostPersist //This function in the entity Comments
protected void updateBooks() {
//Likely some call to the repository here that updates the count
// in books the info we have from current entity.
}
Any idea on how to do this? Some best practices about this kind of denormalization in JPA? Better to use the database triggers?
spring not managed your entity classes and your idea is possible but you must inject BooksRepository in enttiy class then stay at you get Nullpointerexception because spring not managed enttiy classes,The reason your BooksRepository not initlaized, try also read this post Bean injection inside a JPA #Entity and anotate entity class #Configurable after
try this
#PostPersist
protected void updateBooks(Comments comment) {
int totalComment = BooksRepository.updateCounter(comment.getBookId());
System.out.println(totalComment); // see totalComment in console
}
but good aprroach in service classes after call updateCounter when insert comment
example in your CommendService : when try a insert commend after call your updateCounter
if(comment.getBookId() != null) //Simple Control
{
CommentRepository.save(comment);
BooksRepository.updateCounter(comment.getBookId());
}

Hibernate and JPA always load referenced tables

I am working with Hibernate 4+ Spring MVC + Spring Data with JPA annotations:
#Entity
public class ClassOne implements Serializable{
......
#OneToMany(mappedBy = "mapper", fetch=FetchType.LAZY)
private Set<ClassTwo> element = new HashSet<ClassTwo>(0);
//more fields
//getters and setters
//equals and hashcode
}
#Entity
public class ClassTwo implements Serializable{
......
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "CEN_CEN_CODIGO", nullable = false)
private ClassOne classOne;
//more fields
//getters and setters
//equals and hashcode
}
public interface ClassOneRepository extends CrudRepository<ClassOne, Long> {
#Override
#Query("select c from ClassOne c")
public List<ClassOne> findAll();
}
#Service
public class ClassOneService {
#Autowired
private ClassOneRepository classOneRepository;
#Transactional(readOnly=true)
public List<ClassOne> findAll() {
return classOneRepository.findAll();
}
}
And finally I call thie service from my #Controller
#Autowired
ClassOneService classOneService;
I expect results ONLY from ClassOne but retrieving the JOIN values with ClassTwo and all the database tree associate. Is it possible to get only values for ONE table using this schema? Is it a cache problem or Fetching not LAZY?
EDIT: I added the relatioship between two classes
Thank you
You must have the following anotation above your Set<ClassTwo> or its getter:
#OneToMany(fetch = FetchType.LAZY, ...)
See http://docs.oracle.com/javaee/7/api/javax/persistence/OneToMany.html#fetch()
It seems to be that simple "SELECT *" JPA query returns all eagerly fetched fields for the entity.
Please refer to: JPA - Force Lazy loading for only a given query
and http://forcedotcom.github.io/java-sdk/jpa-queries.
So my solution would be to use SessionFactory to get current session and then use "classic" method
return getCurrentSession().createCriteria(persistentClass).list();
Another possible way is to create let's say a POJO object without Set which uses the same table as ClassOne.
I've just added #Lazy for each #ManyToOne and #OneToMany relationship. It seems that Spring Data needs Spring annotations but I supposed that just was necessary to add fetch = FetchType.LAZY. No more Eager behaviours ;).
Thanks for your responses

Resources