Spring Data Hibernate: N+1 problem and pagination - spring

TL;DR -- I cannot find a way to solve the N+1 problem while doing pagination at the same time.
My Entities:
#Entity
public class Invoice {
#Id
#JsonView(InvoiceView.ShortView.class)
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
// other stuff
#OneToMany(targetEntity = InvoiceItem.class)
#JoinColumn(name = "invoice")
private List<InvoiceItem> items;
}
#Entity
public class InvoiceItem {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
// other stuff
}
My repository:
#EntityGraph(attributePaths = {"vendor","items"})
#Query(value = "select i from Invoice i where i.status=:status")
Page<Invoice> getInvoicesWithItemsByStatus(#Param("status") Status status, Pageable pageSpec);
or
#Query(value = "select i from Invoice i join fetch i.items join fetch i.vendor where i.status=:status",
countQuery = "select count(i) from Invoice i where i.status=:status")
Page<Invoice> getInvoicesWithItemsByStatus(#Param("status") Status status, Pageable pageSpec);
Both of these produce this Hibernate warning:
HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
and they do just that: fetch everything and give back the page requested. Every time a new page is requested, naturally.

Your problem and how to solve it is described in this article by Vlad Mihalcea
https://vladmihalcea.com/fix-hibernate-hhh000104-entity-fetch-pagination-warning-message/
Basically, you would need to write a native query.

Related

How to retrieve only a specific field from child entity on #OneToOne relationship, not all fields?

When I use jpa's #OneToOne annotation, I want to get the userName field from the table, not all fields. What should I do instead?
#Setter
#Getter
#Entity
public class Menu implements Serializable {
private static final long serialVersionUID = 4462798713783196961L;
/**
* id
*/
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
#OneToOne
#JoinColumn(name = "createUserId",referencedColumnName = "userId")
private User createUser;
#Column(nullable = false)
private LocalDateTime createTime;
}
What do I need to do, can I get the userName field in the User object, but not all of it? Thank you in advance.
You can create a POJO with required fields. e.g. You only want id from Menu and userName from User:
public class CustomMenu {
private Long menuId;
private String userName;
public CustomMenu(Long menuId, String userName) {
this.menuId = menuId;
this.userName = userName;
}
// getters, setters
}
Then you can write a query with hql using the constructor in the CustomMenu with parameters new com.yourpackage.CustomMenu(m.id, m.createUser.userName) and join User entity (join m.createUser) :
TypedQuery<CustomMenu> query = entityManager.createQuery("select new com.yourpackage.CustomMenu(m.id, m.createUser.userName)"
+ "from com.yourpackage.Menu m join m.createUser", CustomMenu.class);
List<CustomMenu> menus = query.getResultList();
This generates one sql query with inner join fetching only required fields :
select menu0_.id as col_0_0_, user1_.user_name as col_1_0_ from menu menu0_ inner join user user1_ on menu0_.create_user_id=user1_.user_id

Spring JPA - Is there a good way for inner join between entities or the only way is JPQL?

I have the following Entities:
#Entity
public class Organisation {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
// ...
}
#Entity
public class Section{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
// ...
#ManyToOne
#JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation;
}
#Entity
public class SubSection {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
// ...
#ManyToOne
#JoinColumn(name = "section_id")
private Section section;
}
Now I want to find all SubSections by Organisation Id. Right now I am doing it using JPQL at the SubSectionRepository as above:
public interface SubSectionRepository extends JpaRepository<SubSection, Long>{
#Query(value = "SELECT ss.* FROM sub_section as ss INNER JOIN section as s ON ss.section_id = s.id WHERE s.organisation_id = ?1", nativeQuery = true)
List<SubSection> findByOrganisation(Long organisationId);
}
Is there any way that I can make INNER JOIN using the JpaRepository interface?
Did you try this way
subsectionRepository.findBySectionOrganizationId(long organizationId)
Update your method to
public interface SubSectionRepository extends JpaRepository<SubSection, Long> {
List<SubSection> findBySectionOrganisationId(Long organisationId);
}
Hibernate will generate something like this.
select subsec.*,
from sub_section subsec
left outer join section sec on subsection.section_id=sec.id
left outer join organisation org on section.organisation_id=org.id
where org.id=?
Don't worry about the left join term. It is actually a inner join because you have a where condition with id = on the most right table. Because of that, it effectively becomes a inner join. I.e if there is no record on the right table for that record, it will be ignored.

Like Button Implementation in Spring Boot

In my application users can post articles. And other users can like these articles.
Article class :
#Entity
public class Article {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "article_id")
private int id;
#Column(name = "article_title")
private String articleTitle;
#OneToMany(mappedBy = "event")
private List<PeopleWhoLiked> peopleWhoLiked;
}
#Entity
public class PeopleWhoLiked {
#EmbeddedId
private PeopleWhoLiked id;
#ManyToOne #MapsId("articleId")
private Article article;
#ManyToOne #MapsId("userId")
private User user;
}
And there is category entity.Every article has one category.
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "category_id")
private int id;
#NotEmpty
#Column(name = "categoryName")
private String categoryName;
#OneToMany(mappedBy = "article")
private List<Article> articleList;
}
My Like Table
Article_id User_id
x x
These are both foreign keys to related tables.
With
category.getArticleList(); function i can show articles to users.They can like articles.But the thing is the system doesn't know that if the article was liked by user already. So always like button is active.
Querying (select statement for every article on Like table) is looks like has huge time complexity and overload to the system.) Even if i do how can i post this into thymeleaf th:each statement with only Article object.
I think querying 10 article's like per time with one select statement sounds good .But again how can i pass this to thymeleaf with Article object.
Your problem with performance is caused by additional request for every row.
For 1 select returning 100 rows you make additionals 100 select to database.
If you need display complicated result build view and than map result of view to your #Entity class, which will used only for presentation purpose.

spring data jpa findAll generated sql do not use join [duplicate]

I have created two entities Book and Book_Category with one-to-many relationship. When I issued BookCategoryRepository.findAll(), I expected hibernate to use 'INNER JOIN' query. But it just issued query to take data from Book_Category.
What I am missing? What should I do to make hibernate issue JOIN query?
Book.java
#Entity
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
#ManyToOne
#JoinColumn(name = "book_category_id")
private BookCategory bookCategory;
}
BookCategory.java
#Entity
#Table(name = "book_category")
public class BookCategory {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
#OneToMany(mappedBy = "bookCategory", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Set<Book> books;
}
BookCategoryRepository.java
public interface BookCategoryRepository extends JpaRepository<BookCategory, Integer> {
}
bookCategoryRepository.findAll()
Hibernate uses by default a second query to retriev the child collection. One reason for this is a proper limit query. Otherwise, there would be more rows in the result set, than entities for the 1 side, if at least one has more than 1 child.
There exists an annotation to change this behaviour in hibernate which is ignored by the Spring Data Jpa Repositories. The annotation is #Fetch(FetchMode.JOIN). You might consider How does the FetchMode work in Spring Data JPA if you really need this behaviour.

Selecting latest workout set by user

I'm trying to select the latest added workout set associated with a given user.
Users has sessions and sessions has sets. My entities are defined as below.
#Entity(name = "users") // Postgres doesn't like the table name "user"
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToMany(mappedBy = "user")
private Set<Session> sessions;
...
#Entity
public class Session {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne
private User user;
#OneToMany(mappedBy = "session")
private Set<WorkoutSet> sets;
...
#Entity
public class WorkoutSet {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToOne
private User user;
#ManyToOne
private Session session;
private LocalDateTime timestamp = LocalDateTime.now();
...
The following sql query seems to do the trick
select w
from workout_set w
inner join session s
on w.session_id = s.id
where s.user_id = 1
order by w.timestamp DESC
limit 1
But when I'm trying to do something like the below
#Repository
public interface WorkoutSetRepository extends CrudRepository<WorkoutSet, Long> {
#Query("select w from WorkoutSet w inner join Session s on w.session_id = s.id where s.user = :user order by w.timestamp")
List<WorkoutSet> findLastSet(User user, Pageable limit);
I get...
org.springframework.data.mapping.PropertyReferenceException: No property user found for type WorkoutSet!
Any clues about how to do the query right? I'm very open to alternative ways as well because I'd rather avoid writing jpql if possible.
Try this:
#Repository
public interface WorkoutSetRepository extends CrudRepository<WorkoutSet, Long> {
#Query("select w from WorkoutSet w inner join w.session s where s.user = :user order by w.timestamp")
List<WorkoutSet> findLastSet(#Param("user") TestUser user);
}
Note the difference in the join clause. This works with hibernate 5.0.11.

Resources