HQL joins repeats records on Hibernate 5.2/Spring 4.3 (no eager fetched, mapped to Set, only inner joins) - spring

Hello.
It may seem that this question is made before but I cannot see any answer in internet to this concrete case.
This is my starting point:
HQL joins repeats records on Hibernate 5.2/Spring 4.3. This is a simple Many to One/One to many mapping: I have my Root Entity (Usuarios) and my Child entity (Perfiles).
I tried the simple example with Oracle 11, and MySQL 5, and it always repeats the root Entity results. I dont if it has to be taht way... I doubt it (I am not new to Hibernate)
The configuration is OK I already revidsed it a bunch of times.
The problem with this simple HQL, is that it repeats the object Usuarios for each one of its children (Perfiles), so if I have one Usuario with 3 Perfiles, it appears 3 times.
No left joins, no EAGER fetching, Usuarios is mapping to a Set of Perfiles. Everything is alrigth!!!!! I cant believe it is repeating the records. Of course if i put a distinct there, I get one simple object Usuarios (thats what i want of course), perfectly populated with its sets and subObjetc.. like Perfiles. But i Cannot find why it is acting that way, something must be wrong if I have to write the distinc in the HQL.
Here are my two objects:
<pre><code>
select u from Usuarios u
inner join u.perfiles p
#Entity
#Table(name = "usuarios")
public class Usuarios implements java.io.Serializable {
private int usuarioId;
private TcTiposDocumentos tcTiposDocumentos;
private Usuarios usuarioAlta;
private Usuarios usuarioUltModif;
private Usuarios usuarioBaja;
private String nombreApellidos;
private String email;
private String numTelefono;
private Set<Perfiles> perfiles = new HashSet<Perfiles>(0);
public Usuarios() {
}
#Id
#Column(name = "USUARIO_ID", unique = true, nullable = false)
public int getUsuarioId() {
return this.usuarioId;
}
public void setUsuarioId(int usuarioId) {
this.usuarioId = usuarioId;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "COD_TIPO_DOCUMENTO", nullable = false)
public TcTiposDocumentos getTcTiposDocumentos() {
return this.tcTiposDocumentos;
}
public void setTcTiposDocumentos(TcTiposDocumentos tcTiposDocumentos) {
this.tcTiposDocumentos = tcTiposDocumentos;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "COD_USUARIO_BAJA")
public Usuarios getUsuarioBaja() {
return this.usuarioBaja;
}
public void setUsuarioBaja(Usuarios ub) {
this.usuarioBaja = ub;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "usuarios")
public Set<Perfiles> getPerfiles() {
return this.perfiles;
}
public void setPerfiles(Set<Perfiles> perfileses) {
this.perfiles = perfileses;
}
}
//CHILD ENTITY
Entity
#Table(name = "perfiles")
public class Perfiles implements java.io.Serializable {
private int perfilId;
private ComunidadesRegante comunidadesRegante;
private TcRoles tcRoles;
private Usuarios usuarios;
public Perfiles() {
}
public Perfiles(int perfilId, TcRoles tcRoles, Usuarios usuarios) {
this.perfilId = perfilId;
this.tcRoles = tcRoles;
this.usuarios = usuarios;
}
public Perfiles(int perfilId, ComunidadesRegante comunidadesRegante, TcRoles
tcRoles, Usuarios usuarios) {
this.perfilId = perfilId;
this.comunidadesRegante = comunidadesRegante;
this.tcRoles = tcRoles;
this.usuarios = usuarios;
}
#Id
#Column(name = "PERFIL_ID", unique = true, nullable = false)
public int getPerfilId() {
return this.perfilId;
}
public void setPerfilId(int perfilId) {
this.perfilId = perfilId;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "COD_USUARIO", nullable = false)
public Usuarios getUsuarios() {
return this.usuarios;
}
public void setUsuarios(Usuarios usuarios) {
this.usuarios = usuarios;
}
}
</code></pre>

I guess thats the way Hibernate 5 works. If you are using HQL, you'll have to use the "distinct" keyword in all the querys in order to get not repeated results (it works OK, no major problems happened).

Related

How to load a full graph of 2 entities that are in relationship #OneToMany each other with a Join Table

I'm using Spring Boot and Spring Data and I have a problem when trying to load entities using JPA and EntityGraph.
I have a Patient and Insurance entities. Each Patient can have many Insurances and each Insurance can be assigned to many patients. I decided to use a Join Table PatientInsurance because I need to store extra fields like 'active', and also the relation code (a Patient can be a Member, Spouse, or Child for that specific insurance).
Using Spring Data repositories I annotated the method to find a patient, with an EntityGraph, to have ready the list of PatientInsurances (and Insurances) for that patient in one query.
This is the code (I removed the non-necessary parts in the scope)
Patient class
#Entity
#Table(name = "patient")
public class Patient {
#NotNull
#NotEmpty
#Column(length = 60, nullable = false)
private String patientFirstName;
#NotNull
#NotEmpty
#Column(length = 60, nullable = false)
private String patientLastName;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "patient", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
List<PatientInsurance> patientsInsurances = new ArrayList<>();
public void addPatientInsurance(PatientInsurance patientIns) {
if (!patientsInsurances.contains(patientIns)) {
patientsInsurances.add(patientIns);
}
}
//other properties...
Insurance class
#Entity
#Table(name = "insurance")
public class Insurance {
#Column(name = "policy_id", length = 20)
private String policyId;
#OneToMany(mappedBy = "insurance", fetch = FetchType.LAZY,cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
private List<PatientInsurance> patientsInsurances = new ArrayList<PatientInsurance>();
public void addPatientInsurance(PatientInsurance patientIns) {
if (!patientsInsurances.contains(patientIns)) {
patientsInsurances.add(patientIns);
}
}
//other properties
Entity for the join table between patient and insurance (needed a join table for extra field in this entity like active and relCode
#Entity
#IdClass(PatientInsurance.PatientInsurancePK.class)
#Table(name = "patient_insurance")
public class PatientInsurance implements Serializable {
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "patient_id")
private Patient patient;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "insurance_id")
private Insurance insurance;
#Column(name = "active")
private boolean active;
#Column(length = 1)
private String relCode;
public PatientInsurance() {
insurance = new Insurance();
patient = new Patient();
}
public PatientInsurance(Patient p, Insurance i, boolean active, String relCode) {
this.patient = p;
this.insurance = i;
this.active = active;
this.relCode = relCode;
p.addPatientInsurance(this);
i.addPatientInsurance(this);
}
public Patient getPatient() {
return patient;
}
public Insurance getInsurance() {
return insurance;
}
public void setInsurance(Insurance insurance) {
this.insurance = insurance;
insurance.addPatientInsurance(this);
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
public void setPatient(Patient patient) {
this.patient = patient;
patient.addPatientInsurance(this);
}
public String getRelCode() {
return relCode;
}
public void setRelCode(String relCode) {
this.relCode = relCode;
}
static public class PatientInsurancePK implements Serializable {
protected Patient patient;
protected Insurance insurance;
public PatientInsurancePK() {
}
public PatientInsurancePK(Patient patient, Insurance insurance) {
this.patient = patient;
this.insurance = insurance;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PatientInsurancePK)) return false;
PatientInsurancePK that = (PatientInsurancePK) o;
if (!patient.equals(that.patient)) return false;
return insurance.equals(that.insurance);
}
#Override
public int hashCode() {
int result = (patient != null) ? patient.hashCode() : 0;
result = 31 * result + ((insurance != null) ? insurance.hashCode() : 0);
return result;
}
}
}
Implementation of the PatientService
#Transactional
#Service("patientService")
public class PatientServiceImpl implements PatientService {
#Autowired
PatientRepository patientRepository;
#Override
public Optional<Patient> findByIdFull(Long id) {
Optional<Patient> patient = patientRepository.findById(id);
return patient;
}
//other methods...
Patient Repository
public interface PatientRepository extends JpaRepository<Patient, Long> {
#EntityGraph(
attributePaths = {
"patientsInsurances",
"patientsInsurances.patient",
"patientsInsurances.insurance"},
type = EntityGraph.EntityGraphType.LOAD)
Optional<Patient> findById(Long id);
A snippet of code that calls the method in PatientService
Optional<Patient> patientOptional = patientService.findByIdFull(p.getId());
if (patientOptional.isPresent()) {
Patient patient1 = patientOptional.get();
List<PatientInsurance> patientInsurances = patient1.getPatientInsurances();
PatientInsurances patientInsurance = patientInsurances.get(0);
Patient patient2 = patientInsurance.getPatient(); //and this is same istance of patient1, it's ok
Insurance insurance = patientInsurance.getInsurance();
//here is the problem!!!
insurance.getPatientInsurances();
//Unable to evaluate the expression Method threw 'org.hibernate.LazyInitializationException' exception.
So the problem seems that when I go inside the patient side, I can loop into his Insurances without problems, but when I try to do the same starting from the Insurance instance, I cannot loop into its patients cause they are lazily loaded.
So how to make jpa download the full graph in the correct way?

Spring JPA EntityGraph fetches all lazy loaded properties

I've worked with Spring and Hibernate. Now having a look at Spring Data JPA (2.0.3) with JPA 2.2
AgencyTicketType
#Entity
#Table(name = "agency_ticket_type", catalog = "test")
public class AgencyTicketType implements java.io.Serializable {
private Long id;
private String name;
private Agency agency;
private Set<AgencyTicketCategory> agencyTicketCategories = new HashSet<AgencyTicketCategory>(0);
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name = "name", nullable = false, length = 100)
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "agency_id", nullable = false)
public Agency getAgency() {
return this.agency;
}
public void setAgency(Agency agency) {
this.agency = agency;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "agencyTicketType")
public Set<AgencyTicketCategory> getAgencyTicketCategories() {
return this.agencyTicketCategories;
}
public void setAgencyTicketCategories(Set<AgencyTicketCategory> agencyTicketCategories) {
this.agencyTicketCategories = agencyTicketCategories;
}
}
AgencyTicketCategory
#Entity
#Table(name = "agency_ticket_category", catalog = "waytest")
public class AgencyTicketCategory implements java.io.Serializable {
private Long id;
private AgencyTicketType agencyTicketType;
private String name;
private BigDecimal price;
private Set<TripTicket> tripTickets = new HashSet<TripTicket>(0);
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "agency_ticket_type_id", nullable = false)
public AgencyTicketType getAgencyTicketType() {
return this.agencyTicketType;
}
public void setAgencyTicketType(AgencyTicketType agencyTicketType) {
this.agencyTicketType = agencyTicketType;
}
#Column(name = "name", nullable = false, length = 100)
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
#Column(name = "price", nullable = false, precision = 8)
public BigDecimal getPrice() {
return this.price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "agencyTicketCategory")
public Set<TripTicket> getTripTickets() {
return this.tripTickets;
}
public void setTripTickets(Set<TripTicket> tripTickets) {
this.tripTickets = tripTickets;
}
}
Repository
public interface TicketTypeRepository extends JpaRepository<AgencyTicketType, Long> {
#EntityGraph(attributePaths={ "agencyTicketCategories" }, type=EntityGraphType.LOAD)
#Query("select type from AgencyTicketType type where type.agency.code=?1")
List<AgencyTicketType> findByAgency(String agencyCode);
}
Service
#Service
public class TicketServiceImpl implements TicketService {
#Autowired private TicketTypeRepository ticketType;
#Transactional(readOnly=true)
#Override
public List<AgencyTicketType> findByName(String code) {
return ticketType.findByAgency(code);
}
}
When debugged on Service, it seems, the query eagerly fetches all the lazy loaded properties - agency, agencyTicketCategories - and all their inner lazy loaded properties, which leads to JSON serilization error.
Need to fetch only these
AgencyTicketTypes [
{
id, name,
agencyTicketCategories [
{id,name,price},....
]
},.....
]
Can I do this with #EntityGraph? What I am missing?
Specifying lazy loading is only a hint for the JPA provider. Depending on the provider you use (Hibernate, EclipseLink etc.) it may be completely ignored and the dependencies may be eagerly fetched.
What you need to do is configure how your classes are mapped to json. Assuming you are using Jackson you may need to use annotations like #JsonIgnore or #JsonView. You may also map your class that only has the fields you need.
You can use Jackson annotations #JsonBackReference/#JsonManagedReference. They address problem of infinite recursion with bidirectional links in object model. As far as I understand it is your case.
See http://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion for more information.
One thing to point is that debugging while the transaction is open (touching the collection) will cause it to be loaded even if at real time it doesn't .. the other thing is that as #Apokralipsa mentioned , LAZY loading is just a hint that can be totally ignored and should never be relied upon whatever technique you are using

Hibernate join two entities

i really don't know what actually my problem is.
I have two models in my Project.
model-package
Ansprechpartner
Lieferant
Ansprechpartner.java
#Entity
#Table(name = "ANSPRECHPARTNER")
#EntityListeners(AuditingEntityListener.class)
#JsonIgnoreProperties(value = {"anlageAm", "updatedAt"}, allowGetters = true)
public class Ansprechpartner {
...
#NotNull
#ManyToOne
#JoinColumn(name = "lief_code", foreignKey=#ForeignKey(name = "APART_LIEF_FK"))
private Lieferanten liefCode;
public Lieferanten getLiefCode() {
return liefCode;
}
public void setLiefCode(Lieferanten liefCode) {
this.liefCode = liefCode;
}
...
}
Lieferant.java
#Entity
#Table(name = "LIEFERANTEN")
#EntityListeners(AuditingEntityListener.class)
#JsonIgnoreProperties(value = {"anlageAm"}, allowGetters = true)
public class Lieferanten {
...
#Id
private String code;
#OneToMany(mappedBy = "liefCode")
private Set<Ansprechpartner> apart;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Set<Ansprechpartner> getApart() {
return apart;
}
public void setApart(Set<Ansprechpartner> apart) {
this.apart = apart;
}
...
}
My Controller:
#RestController
#RequestMapping("/apart")
public class AnsprechpartnerController {
...
#GetMapping("/all/{id}")
public Ansprechpartner getApartWithId(#PathVariable("id") long id) {
Ansprechpartner apart = apartRepository.findOne(id);
return apartRepository.findOne(id);
}
}
When i try to get the json data i get the following problem. Ansprechpartner gets data from Lieferant (because of that join). But then Lieferant again shows data from Ansprechpartner and so on.
Maybe better described with the following picture:
Image with explanation
EDIT:
I finally solved it with the #JsonIgnoreProperties annotation:
In my Ansprechpartner.java i did it this way:
#NotNull
#JsonIgnoreProperties("apart")
// #JsonManagedReference
#ManyToOne
#JoinColumn(
name = "lief_code",
foreignKey=#ForeignKey(name = "APART_LIEF_FK")
)
private Lieferanten liefCode;
And in my Lieferanten.java i did it this way:
// #JsonBackReference
#JsonIgnoreProperties("liefCode")
#OneToMany(mappedBy = "liefCode", fetch = FetchType.LAZY)
private Set<Ansprechpartner> apart;
To avoid infinite recursions you can use #JsonManagedReference & #JsonBackReference
Json Infinite Recursion is one of the most common problems when we serialize Java objects which having Bidirectional-Relationships.
#JsonManagedReference: a part with the annotation will be serialized normally.
#JsonBackReference: a part with the annotation will be omitted from serialization.
like:
#JsonBackReference
private Set<Ansprechpartner> apart;
You can check details in solution-2
Strange behaviour. Possibly you could try:
1) Make sure in the Lieferanten entity, in the equals / hashCode you do not use the Set<Ansprechpartner> apart.
2) You can explicitly detach the entities from the persistence context:
#NotNull
#ManyToOne
#JoinColumn(name = "lief_code"
, foreignKey=#ForeignKey(name = "APART_LIEF_FK")
, cascade={CascadeType.DETACH})
private Lieferanten liefCode;
and then in the controller:
#GetMapping("/all/{id}")
public Ansprechpartner getApartWithId(#PathVariable("id") long id) {
Ansprechpartner apart = apartRepository.findOne(id);
apartRepository.detach(apart);
return apart;
}
you would need to implement a bit -> link, in repository in order to have that available.
3) explicitly add lazy loading: #OneToMany(mappedBy = "liefCode", fetch = FetchType.LAZY).
The root cause is jackson trying to serialize object when object has Bidirectional-Relationships.
You can fixed it by this way
Short way
Better way :
Returning entities directly to view layer is not a good practice.
You should convert entities to DTOs (Data Transfer Object) and pass the DTOs to view

Basic CRUD operation with composite Id (spring + hibernate)

Im trying to make a basic create operation with hibernate and spring, but i keep getting the message that a id is empty when it is not.So im thinking that it might be because, the entity uses a composite id, fun fact at least for me is that i don't have any problem deleting the entities.
The method im using
#RequestMapping(value="addPatientFamilyRelative",method = RequestMethod.POST)
public #ResponseBody String addPatientFamilyRelative(#RequestParam(value="idPatient")int idPatient,
#RequestParam(value="idRelative")int idRelative,
#RequestParam(value="idRelationship")int idRelationship)
{
Patient_Relative patientRelative = new Patient_Relative();
patientRelative.setIdRelationship(relationshipService.getById(idRelationship));
patientRelative.setPatient(patientService.getById(idPatient));
patientRelative.setRelative(relativeService.getRelative(idRelative));
prService.create(patientRelative);
return "$('#tblPatientFamilyPatientRelatives').ajax.reload();$('#tblPatientRelativesList').ajax.reload()";
}
Patient_Relative class
#Entity
#Table(name="Patient_Relative")
public class Patient_Relative implements Serializable{
/**
*
*/
private static final long serialVersionUID = -2670460334767266076L;
#EmbeddedId
#JoinColumn(name = "idRelative", referencedColumnName = "idRelative", insertable = false, updatable = false)
#ManyToOne(optional = false)
#JsonIgnore
private Relative relative;
#JoinColumn(name = "idRelationship", referencedColumnName = "idRelationship")
#ManyToOne
private Relationship idRelationship;
#JoinColumn(name = "idPatient", referencedColumnName = "idPatient", insertable = false, updatable = false)
#ManyToOne(optional = false)
#JsonIgnore
private Patient patient;
public Relative getRelative() {
return relative;
}
public void setRelative(Relative relative) {
this.relative = relative;
}
public Relationship getIdRelationship() {
return idRelationship;
}
public void setIdRelationship(Relationship idRelationship) {
this.idRelationship = idRelationship;
}
public Patient getPatient() {
return patient;
}
public void setPatient(Patient patient) {
this.patient = patient;
}
}
PatientRelativeId
#Embeddable
public class PatientRelativeId implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 2719758608242901070L;
#Column(name = "idPatient")
private int patientId;
#Column(name = "idRelative")
private int relativeId;
public PatientRelativeId() {
}
public PatientRelativeId(int patientId, int relativeId) {
this.patientId = patientId;
this.relativeId = relativeId;
}
public int getPatientId() {
return patientId;
}
public void setPatientId(int patientId) {
this.patientId = patientId;
}
public int getRelativeId() {
return relativeId;
}
public void setRelativeId(int relativeId) {
this.relativeId = relativeId;
}
}
i hope this is enough to get some ideas, i would have liked to add a column just for the id but i think im not able to do that anymore.
Thanks in advance
I hope this helps someone.
First , my Patient_Relative class was short 1 variable , the one that would store the composite id, so i added the variable PatientRelativeId compositeId.
Second, at the controller method all i had to do was set the values of the composite id , the patient and the relative , and then call the service to create the object.
#RequestMapping(value="addPatientFamilyRelative",method = RequestMethod.POST)
public #ResponseBody String addPatientFamilyRelative(#RequestParam(value="idPatient")int idPatient,
#RequestParam(value="idRelative")int idRelative,
#RequestParam(value="idRelationship")int idRelationship)
{
Patient_Relative patientRelative = new Patient_Relative();
PatientRelativeId id = new PatientRelativeId(idPatient, idRelative);
patientRelative.setPatienRelativeId(id);
patientRelative.setIdRelationship(relationshipService.getById(idRelationship));
patientRelative.setPatient(patientService.getById(idPatient));
patientRelative.setRelative(relativeService.getRelative(idRelative));
prService.create(patientRelative);
return "addRelative";
}

JPA2 Criteria-API: select... in (select from where)

I have the following database model:
A
aId
AB
aId
bId
B
bId
status
In a Spring data Specification, I want to return the instances of A when B.status is 'X'.
The JPQL code is the following:
select a from A a where a in
(select ab.id.a from AB ab where ab.id.b.status= :status)
These are the model classes:
#Entity
public class A {
private Long aId;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "id.a")
private Set<AB> ab;
}
#Entity
public class B {
private Long bId;
private String Status;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "id.b")
private Set<AB> ab;
}
#Entity
public class AB {
private ABPK id;
}
public class ABPK {
#ManyToOne
#JoinColumn(name="aId")
private A a;
#ManyToOne
#JoinColumn(name="bId")
private B b;
}
How would be the JPA Criteria in the Spring Specification?
public class ASpecifications {
public static Specification<A> test(final String status) {
return new Specification<Party>() {
#Override
public Predicate toPredicate(Root<A> a, CriteriaQuery<?> query, CriteriaBuilder cb) {
return null;
}
};
}
}
The Specification that returns instances of A using Criteria API is the following:
public class ASpecifications {
public static Specification<A> test(final String status) {
return new Specification<Party>() {
#Override
public Predicate toPredicate(Root<A> a, CriteriaQuery<?> query, CriteriaBuilder cb) {
Subquery<A> sq = query.subquery(A.class);
Root<AB> ab = sq.from(AB.class);
sq.select(ab.get(AB_.id).get(ABPK_.a));
sq.where(cb.equal(ab.get(AB_.id).get(ABPK_.b).get(B_.status), status));
Predicate p = cb.in(a).value(sq);
return cb.and(p);
}
};
}
}
There are some good examples included on a previous post that address exactly what you're trying to accomplish here: jpa-2-0-criteria-api-subqueries-in-expressions.
I suppose you wanted to select "A entities from AB entities where B is of provided status":
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<A> cq = cb.createQuery(A.class);
Root<AB> ab = cq.from(AB.class);
cq.select(ab.get("id").get("a"));
cq.where(cb.equal(ab.get("id").get("b.status"), status));
Select In - is an option but you could achieve same result with double join. And its easier to make such a jpa specification:
SELECT A.ID FROM A LEFT JOIN AB ON A.ID = AB.A_ID LEFT JOIN B ON AB.B_ID = B.ID WHERE B.STATUS = 'STATUS'
Method would look like this:
public static Specification<A> findB(String input) {
return new Specification<A>() {
#Override
public Predicate toPredicate(Root<A> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
Join<A,AB> AjoinAB = root.joinList(A_.AB_LIST,JoinType.LEFT);
Join<AB,B> ABjoinB = AjoinAB.join(AB_.B,JoinType.LEFT);
return cb.equal(ABjoinB.get(B_.NAME),input);
}
};
}
Or shorter
public static Specification<A> findB(String input) {
return (Specification<A>) (root, cq, cb) -> {
Join<A,AB> AjoinAB = root.joinList(A_.AB_LIST,JoinType.LEFT);
Join<AB,B> ABjoinB = AjoinAB.join(AB_.B,JoinType.LEFT);
return cb.equal(ABjoinB.get(B_.NAME),input);
};
}
I know it's been a long time since this question arose, but I came across it when I tried to do the same. This is my solution that works and I hope it helps someone.
Entities below
#Entity
public class A {
#Id
private Long id;
private String name;
#OneToMany(mappedBy = "a")
private List<AB> abList;
}
#Entity
public class B {
#Id
private Long id;
private String status;
#OneToMany(mappedBy = "b")
private List<AB> abList;
}
#Entity
public class AB {
#Id
private Long id;
private String name;
#ManyToOne
#JoinColumn(name = "a_id")
private A a;
#ManyToOne
#JoinColumn(name = "b_id")
private B b;
}

Resources