hibernate lazy fetch,how to add #ManytoMany associations? - spring

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

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.

Hibernate #OneToMany don't remove child from list when updating parent

I have create mapping between Paper and Mcq question as below.
public class Paper {
#OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "paper_mcq",
joinColumns = {#JoinColumn(name = "paper_id")},
inverseJoinColumns = {#JoinColumn(name = "mcq_id")})
#JsonIgnore
private Set<Mcq> mcqs = new HashSet<>();
}
When I'm updating Paper entity it's deletes all MCQ.
SQL Output:
Hibernate: delete from paper_mcq where paper_id=?
I believe your paper object in paperRepo.save(paper) don't have mcqs at this time, and the cascading sees that as a deletion. I'm just assuming that you're receiving your object from json and the #JsonIgnore simply ignores the deserialization.
So there are multiple options to solve that:
- Query the mcqs and set them before updating
- remove #JsonIgnore and add those in your json
- remove the cascading and set it manually

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.

Resources