Hibernate JoinTableFilter annotation - spring

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;

Related

Hibernate: lazy loading vs dtos

Imagine having an JPA entity with different lazy loaded fields
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "organisation_teams",
joinColumns = #JoinColumn(name = "id", referencedColumnName = "id", table = "usr_organisation"),
inverseJoinColumns = #JoinColumn(name = "team_id", referencedColumnName = "id", table = "usr_teams")
)
private Set<Team> teams = new LinkedHashSet<>();
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "organisation_associations",
joinColumns = #JoinColumn(name = "parent_id", referencedColumnName = "id", table = "usr_organisation"),
inverseJoinColumns = #JoinColumn(name = "child_id", referencedColumnName = "id", table = "organisation")
)
private Set<Organisation> children = new LinkedHashSet<>();
This entity is now requested by a second service, so we need to convert into a DTO and return via REST for example. Now there can be different scenarios, where for example only the team field is required, only the children field or both.
One could map them always into the DTO, in this case using lazy loading is pointless in my opinion. Are there different elegant options aside of creating different DTOs (containing only the team or only the children fields) and different REST endpoints? I don`t think this would be an elegant solution as well, because you would end up with a bunch of endpoints and dtos.
Additionally Team and Organisation again have lazy loaded fields and the same issue applies again.
I think that what you are looking for might be https://graphql.org/. But for plain REST endpoints I would say different DTOs is the way to go, use inheritance to avoid code duplication.
If you want to improve performance and not lazy load related entities you can specify different named entity graphs and depending on the DTO select the one that eagre loads the required data. See the documentation here: https://docs.oracle.com/javaee/7/tutorial/persistence-entitygraphs001.htm.
Any lazy loaded field that is not mapped to a DTO won't be loaded. So basically you use the same entity with different graphs and different DTOs.

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

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;

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