#Batchsize annotation not working for OneToMany - spring-boot

I have following classes and on annotating #BatchSize annotation it is not working and I am getting n+1 select query.
Class Shipment{
#OneToMany(fetch = FetchType.LAZY, mappedBy = order.shipment, cascade = CascadeType.ALL,
orphanRemoval = true)
#BatchSize(size=20)
Set<Orders> orders = new Hashset(); <---- Batch size annotation not working
}
Order.class
class Order{
#ToString.Exclude
#ManyToOne
#JoinColumn(name = "item_fk")
Item item;
#ToString.Exclude
#ManyToOne
#JoinColumn(name = "shipment_fk")
Shipment shipment; }
Item.class
class Item{
String id;
String name;
}
What is mistake in implementation that i am getting n+1 queries?

Try to use List<Orders> instead of Set<Orders>.
Please note as it's mentioned in the documentation:
However, although #BatchSize is better than running into an N+1 query issue, most of the time, a DTO projection or a JOIN FETCH is a much better alternative since it allows you to fetch all the required data with a single query.

Your N + 1 query issue is due to the fact that you do eager fetching of Item in Order. Change to LAZY there and you should be good to go.

Related

Hibernate N+1 issue for Multiple children

I have a entity class which has multiple children with oneToMany association:
public class A{
private Long id;
private String name;
#OneToMany(mappedBy = "A", fetch = FetchType.LAZY, cascade = CascadeType.ALL,
orphanRemoval = true)
private List<B>bList= new ArrayList<>();
#OneToMany(mappedBy = "A", fetch = FetchType.LAZY, cascade = CascadeType.ALL,
orphanRemoval = true)
private List<C>cList= new ArrayList<>();
#OneToMany(mappedBy = "A", fetch = FetchType.LAZY, cascade = CascadeType.ALL,
orphanRemoval = true)
private List<D>dList= new ArrayList<>();
//getters and setters
}
For B,C and D I have set ManyToOne. In a word, they are in a bi-directional relationship.
Now, If I fetch A by id, I see a lot of queries get fired which turns out to be N+1 problem. To solve this, I added #Fetch(FetchMode.SUBSELECT) to all of the oneToMany relationships above which cause less queries to be fired.
My question is:
is it okay using #Fetch(FetchMode.SUBSELECT) or I can optimize it further?
what if I want to fetch all "As" by calling findAll() method? What should be the syntax for multiple children? Like
"select a from A a join fetch a.b then ??"
List< A > findAll()
Now, If I fetch A by id, I see a lot of queries get fired which turns out to be N+1 problem. To solve this, I added #Fetch(FetchMode.SUBSELECT) to all of the oneToMany relationships above which cause less queries to be fired.
You are not saying how/when these queries are fired, so a possible reason for the problem is that you are returning entities from your HTTP endpoints which are then serialized. Using #Fetch(FetchMode.SUBSELECT) is one way to "improve" the performance but will only work nicely if the base query which you use to fetch A is simple. If it gets too complex (pagination, complex predicates, etc.) you should stick to the default which is SELECT and instead configure a proper batch size via the #BatchSize( size = 32 ) annotation. A good value for the batch size would be the amount of A instances that you expect to be returned such that only a single query is executed per collection. If you allow a max page size of e.g. 50, you setting the value to 50 would be perfect.
List< A > findAll()
Do not ever do this if you care about performance and usability. It rarely makes sense to allow returning all elements, as no user can handle more than ~20 elements at a time anyway. As mentioned before, always have some kind of upper limit on the page size to prevent misuses that can cause performance issues.

"could not initialize proxy - no Session" For Multiple ManyToMany relationships in the parent

I have a Parent User Class that has multiple ManyToMany Relationships.
#Table(name = "user")
public class User {
..
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.DETACH})
#JoinTable(
name = "user_address",
joinColumns = { #JoinColumn(name = "user_id")},
inverseJoinColumns = { #JoinColumn(name = "address_id")}
)
#JsonIgnore
private final List<Address> addresses = new ArrayList<Address>();
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.DETACH})
#JoinTable(
name = "reports",
joinColumns = { #JoinColumn(name = "user_id")},
inverseJoinColumns = { #JoinColumn(name = "reports_id")}
)
#JsonIgnore
private final List<Reports> reports = new ArrayList<Reports>();
}
When I access the FIRST ManyToMany property, everything works fine. However, immediately after
accessing the first, when I try to access the SECOND ManyToMany Property I get the "could not initialize proxy - no Session" exception:
#Component
public class Combiner {
public void combineData() {
...
List<Address> addresses = user.getAddress(); // This works
List<Reports> reports = user.getReports(); // Get the error here
..
}
}
The Address and Reports classes have the inverse relationship as many ManyToMany back to the User Entity Above.
public class Address {
#ManyToMany(mappedBy = "addresses", fetch = FetchType.LAZY)
private final List<User> users = new ArrayList<User>();
}
public class Reports {
#ManyToMany(mappedBy = "reports", fetch = FetchType.LAZY)
private final List<User> users = new ArrayList<User>();
}
I tried searching SO for the same error where there are MULTIPLE relationships like mine and the first passes but second fails, but could'nt find a post (or google couldn't understand the search terms, if anyone knows a pre-existing one - please let me know).
Could someone assess what else Im missing?
I've tried these so far to no avail:
Added #Transactional to the parent Service class that calls Combiner above
Made the second failing relationship EAGER. (as i understand it you cant make BOTH EAGER since i get a multiple bags error probably because of Cartesian join)
AM Using SpringBoot (2.2.4) with Hibernate Core {5.4.10.Final}
Approach one:
Make #ManyToMany uni-directional. The exception clearly says it can not initialize the collection of role you have in User class.
As you asked in the comment section Why can't this use case be Bi Directional - You can make this bi-directional as well.
Approach two: make collection of role EAGER or use Hibernate.initialize() to initialize the collection.
Bonus: you can make both collection EAGER by using Set not List.

In many to many get only id instead of the whole object

public class Role {
#ManyToMany
#JoinTable(name = "user_to_role",
joinColumns = #JoinColumn(name = "role_id"),
inverseJoinColumns = #JoinColumn(name = "user_id",referencedColumnName = "id"))
private Set<User> users;
}
public class User {
#ManyToMany
#JoinTable(name = "user_to_role",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
}
I have a many to many relationship between the two classes. When calling role.getUsers(), I want to get only the user ids, the rest of the fields should be ignored, since there will be a lot of data and I don't want to load everything, How can I achieve this?
A straightforward way to do it would be to use a Criteria query, but to use it inside an Entity, you'd have to inject an EntityManager there, which is considered a bad practice. A better solution would be to create this query in a Service.
But if you still want to do it, then your getUsers method would look something like this:
public List<User> getUsers() {
Criteria cr = entityManager.createCriteria(User.class)
.setProjection(Projections.projectionList()
.add(Projections.property("id"), "id")
.setResultTransformer(Transformers.aliasToBean(User.class));
List<User> list = cr.list();
return list;
}
If you want to restrict your list, just use a Restrictions, like so: criteria.add(Restrictions.eq("id", yourRestrictedId))
Since you have mapped the entities User and Role using #ManyToMany relationship, you need to create a DAO/Service class to implement the business logic to filter only userIds and return the same.
This cannot be handled in your Model\Entity classes as it will defy the whole concept of Object-Relational mapping.
I can create the business logic using DAO for your example if you want but you will get 10's of blogs achieving the same.
For your reference,you can check my sample project here.

How to create join table with extra column with JPA annotations?

I need for a project to join 2 SQL tables implemented like this :
I know that I'm not supposed to implement the table IngredientList as an object cause it's only here for SQL structure.
My code goes like this :
#Entity
#Table(name="recipe")
public class Recipe {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id_recipe")
private Long id;
#OneToMany
#JoinTable(name="liste_ingredients", joinColumns = #JoinColumn(name = "id_recette",
referencedColumnName = "id_recette"),
inverseJoinColumns = #JoinColumn(name = "id_ingredient",
referencedColumnName = "id_ingredient"))
List<Ingredient> ingredients;
/* Getter/Setter/Constructor */
}
Which is the classic way but with that I lose the Quantity attribute that I want to associate with ingredient. And I don't get how I can work around this without creating an object IngredientList.
Thanks in advance.
Nevermind that I got my answer gonna edit it soon with code, for anyone with the same question.

hibernate lazy fetch,how to add #ManytoMany associations?

I have two entities: Movie and Cinema, and they have a #ManyToMany association. Cinema is the owning side, in both side, I keep a collection to store fks, and both side is lazy-fetched:
#Table(name="Cinema")
#Entity
public class Cinema {
#ManyToMany(cascade = CascadeType.ALL,
fetch=FetchType.LAZY)
#JoinTable(name = "cinema_movie",
joinColumns = {#JoinColumn(name = "cinema_id", nullable = false, updatable = false)},
inverseJoinColumns = {#JoinColumn(name = "movie_id", nullable = false, updatable = false)})
private #Getter #Setter Set<Movie> movies = new HashSet<Movie>();
...
}
#Entity
#Table(name="Movie")
public class Movie {
#ManyToMany(cascade = CascadeType.ALL,
mappedBy = "movies")
private #Getter #Setter Set<Cinema> cinemas = new HashSet<Cinema>();
...
}
Because I'm using lazy-fetch, If I test the following code, I will get a
"LazyInitializationException":
//mp and cp means MovieRepo and CinemaRepo
Cinema c2 = new Cinema();
c2.setCity(1);
c2.setAddress("chigang");
c2.setName("feiyang");
cp.save(c2);
m = mp.findByName("Zootopia");
//m = mp.fetchCinemas(m.getId());
m.addCinema(c2);
c2.addMovie(m);
cp.save(c);
If I uncomment //m = mp.fetchCinemas(m.getId()); the exception is gone. As you can predict, I execute the following sql query in this method:
#Query(
"SELECT c FROM Cinema c INNER JOIN FETCH c.movies "+
"WHERE c.id = :cid"
)
Cinema fetchMovies(#Param("cid") Long cid);
But when I just want to associate a Movie with a Cinema, it's cumbersome to explicitly fetch All the Cinemas, and then add one to it, and then save them all.
How can I add a Cinema to m's collection without fetching all Cinemas associated with m??
Or maybe I have problem understanding lazy-fetched collections in hibernate. I'm new to hibernate and Spring. Any Help is appreciated. (If you read my question to this line, thank you whatever <3 )
If you don`t want to fetch all the collection items before adding a new element, you have use Bag Collection instead of Set.
Please refer to the post below which explains all various ways of achieving it:
Hibernate - How to persist a new item in a Collection without loading the entire Collection

Resources