Manytoone lazy property not loaded using join fetch jpql in Spring Boot - spring

I'm using spring boot 2.3.0, hibernate 5 and MySQL 8.0
I'm trying to initialize a ManyToOne relationship using join fetch in my JQPL query but it doesn't work because the entity is not loaded.
In the entity i have:
#Entity
public class Academic_Record {
#JoinColumn(name = "product_id", nullable = false)
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private Product product;
.....
In the repository:
#Query(value = "select ar from Academic_Record ar " +
"join fetch ar.product p " +
"where ar.enrollmentStudent.id = :enrollmentStudentId " +
"and ar.product.id = :productId")
Academic_Record findByEnrollmentStudentIdAndProductIdWithProduct(Long enrollmentStudentId, Integer productId);
But if try:
Academic_Record academicRecord = academicRecordDao.findByEnrollmentStudentIdAndProductIdWithProduct(1,2);
when i watch product property of academicRecord in debug mode, I only see a proxy object and not the product loaded.
What's wrong?
Thanks a lot

This is the default behavior for FetchType.LAZY, the proxy will be resolved when you try to get the product. Use FetchType.EAGER instead if you want to load the product at the same time.
#JoinColumn(name = "product_id", nullable = false)
#ManyToOne(fetch = FetchType.EAGER, optional = false)
private Product product;

Related

Hibernate JoinTableFilter annotation

I'm using Hibernate to map my entities and I'm having a problem. I want my entity to retrieve a list of entity from another table, linked with a join table. I also want to filter on the join table to only retrieve objets whose boolean_value is set to false.
It currently works without filtering, using #JoinTable annotation. I'm facing difficulties when it comes about #FilterJoinTable which seems not to be working.
Here is what I tried to do:
#Entity
#Table(name = "table_a")
#FilterDef(name="checkValue")
#Filter(name = "checkValue")
public class AEntity {
// id ...
#ManyToMany
#JoinTable(name = "my_join_table",
joinColumns = #JoinColumn(name = "a_id"),
inverseJoinColumns = #JoinColumn(name = "b_id"))
#FilterJoinTable(name = "checkValue", condition = "boolean_value = FALSE")
private List<BEntity> objets;
}
Currently it returns all the objects from table_b without filtering.
Any idea of what I'm doing wrong ?
You have to use the #Where annotation, as #FilterJoinTable allows defining Hibernate filters which have to be enabled explicitly with Session#enableFilter(String):
#ManyToMany
#JoinTable(name = "my_join_table",
joinColumns = #JoinColumn(name = "a_id"),
inverseJoinColumns = #JoinColumn(name = "b_id"))
#Where(clause = "boolean_value = FALSE")
private List<BEntity> objets;

#Batchsize annotation not working for OneToMany

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.

"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.

How to add a where clause to a Hibernate #ManyToMany relationship?

Given two entities:
PurchaseProductGroup
PurchaseProduct, which has a status column that can be 'A' (active) or 'D' (deleted)
and a many-to-many relationship defined in PurchaseProductGroup:
/** The purchase products linked to this group. */
#ManyToMany
#JoinTable(name = "purchaseprodgrp_purchaseprod",
joinColumns = #JoinColumn(name = "ppg_id"),
inverseJoinColumns = #JoinColumn(name = "ppr_id"))
private List<PurchaseProduct> purchaseProducts;
How can I restrict this so that purchaseProducts with a status of 'D' are excluded?
Things I've tried
Have tried adding the following just below the #ManyToMany annotation but both failed with an "invalid identifier" exception for the column:
#Where(clause = "status <> 'D'")
#WhereJoinTable(clause = "purchaseProduct.status <> 'D'")
Also tried using adding #Where(clause = "status <> 'D'") at the entity level but this doesn't seem to affect the contents of relationship collections - as backed up by this question.
Please try the following code:
#ManyToMany
#JoinTable(name = "purchaseprodgrp_purchaseprod",
joinColumns = #JoinColumn(name = "ppg_id"),
inverseJoinColumns = #JoinColumn(name = "ppr_id"))
#Where(clause = "ppr_status <> 'D'")
private List<PurchaseProduct> purchaseProducts;
This may be similar to something you have tried before, but a critical point to get this working is that ppr_status is the actual column name. Hence the PurchaseProduct entity should have the following:
#Column(name="ppr_status")
public String getStatus() {
return status;
}
If you had named the field ppr_status then the #Column may not be necessary. But based on your comments above we need to tell Hibernate how to map this column.
Reference: Hibernate annotations. #Where vs #WhereJoinTable

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