How to join four tables with hibernate - java

I have a four tables which I need to join. These four tables are:
product
service
service_type
service_duration
A product has a service, and a service has a type and duration.
The user chooses the product, the type, the service, and the duration of the service.
In the case that a service is not available for the chosen type and duration I would like to be able to get the service which has a type of lower priority (priority is attribute in the service_type table).
In php(Laravel) I am able to join like so:
DB::table('product')
->join('product_service', 'product_service.product_id', '=', 'product.id')
->join('service', 'service.id', '=', 'product_service.service_id')
->join('service_type', 'service.type_id', '=', 'service_type.id')
->join('service_duration', 'service.duration_id', '=', 'service_duration.id')
->where('product.id', id)
->where('service_type.priority', '<', priority)
->where('service_duration.duration', duration)
->where('maintenance.active', 1)
->orderBy('service_type.priority', 'desc')
->select('service.*')
->first();
How is it possible to do this with Hibernate Entity Criteria ? I want to start of by joining from the product side but in the end select the service.
I have my relations defined like so:
Product class
#Entity
#Table(name = "product")
public class product implements Serializable {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "id.product", cascade=CascadeType.ALL)
private Set<ProductService> services = new HashSet<ProductService>();
}
ProductService Class:
#Entity
#Table(name = "product_service")
#AssociationOverrides({ #AssociationOverride(name = "id.service", joinColumns = #JoinColumn(name = "service_id")),
#AssociationOverride(name = "id.product", joinColumns = #JoinColumn(name = "product_id"))})
public class ProductService implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private ProductServicePK id = new ProductServicePK();
#Column(name = "price")
private Float price;
#Column(name = "code")
private String code;
}
ProductServicePK Class:
#Embeddable
public class ProductServicePK implements Serializable {
private static final long serialVersionUID = 1L;
#ManyToOne
private Product product;
#ManyToOne
private Service service;
}
Service class:
#Entity
#Table(name = "service")
public class Service implements Serializable {
#Id
#Column(name = "id")
private Long id;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "type_id")
private ServiceType type;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "duration_id")
private ServiceDuration duration;
}
So the object that 'holds' reference to the other is the product object. So I am not sure how to get the service for that product which has a priority lower than the one selected.
I want to do this with criteria or HQL.

Your Hibernate Detached Criteria will like:
DetachedCriteria criteria = DetachedCriteria.forClass(Product.class, "product")
.criteria.createAlias("product.product_service", "productService")
.createAlias("productService.service","service")
.createAlias("service.service_type","serviceType")
.createAlias("service_duration","serviceDuration")
.add(Restrictions.eq("product.id", id))
.add(Restrictions.gt("serviceType.priority", priority))
.add(Restrictions.eq("serviceDuration.duration", duration))
.setProjection(add your productService projection here)
getExecutableCriteria(session).SetMaxResults(1) ;
But i am not getting your point, why you are using from query on product instead of product_service?, because you need product_service details.
you can you use querycriteria.html, here is the doc of it->https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/querycriteria.html
or you can use hql ->https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/queryhql.html for achieve your result.

Apparently the JPA Criteria API does not support what I need when using the composite PK pattern. I found this mentioned somewhere here. So I found the best solution was using HQL:
Query q = session.createQuery("select s "
+ "from Service s "
+ "join s.productService as ps "
+ "join s.type as t "
+ "where s.duration = :durationId "
+ "and ps.id.product.id = :productId "
+ "and t.priority <= :priorityValue "
+ "order by t.priority desc");
q.setLong("durationId", durationId);
q.setInteger("priorityValue", priorityValue);
q.setLong("productId", productId);
return (Service) q.setMaxResults(1).uniqueResult();

Related

Spring Data findAll with filtered nested objects

I am currently developing a Spring Boot 2.5.5 app which needs to integrate some games. Each Game entity contains multiple GameProfile entities. Since the app and games are multilingual, we store all the generic fields in the GameProfile entity and all the others which are tied to the current language, are stored in an extra relation called GameProfileTranslation which references a Language entity.
Following are the three entities:
#Entity
#Table(name = "GAME_PROFILE")
#Getter
#Setter
public class GameProfile {
#Id
#Column(name = "GAME_PROFILE_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "game_profile_seq")
#SequenceGenerator(name = "game_profile_seq", allocationSize = 1, sequenceName="game_profile_sequence")
private Long id;
#Column(name = "IMAGE")
private String image;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "GAME_ID")
private Game game;
#Column(name = "DIFFICULTY")
private GameDifficulty difficulty;
#OneToMany(mappedBy = "gameProfile", fetch = FetchType.LAZY)
private Set<GameProfileTranslation> translations = new HashSet<>();
}
#Entity
#Table(name = "GAME_PROFILE_TRANSLATION")
#Getter
#Setter
public class GameProfileTranslation {
#Id
#Column(name = "GAME_PROFILE_TRANSLATION_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "game_profile_translation_seq")
#SequenceGenerator(name = "game_profile_translation_seq", allocationSize = 1, sequenceName="game_profile_translation_sequence")
private Long id;
#Column(name = "TITLE")
private String title;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "GAME_PROFILE_ID")
private GameProfile gameProfile;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "LANGUAGE_ID")
private Language language;
}
#Entity
#Table(name = "LANGUAGE")
#Getter
#Setter
#NoArgsConstructor
public class Language {
#Id
#Column(name = "LANGUAGE_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "CODE")
private String code;
#Column(name = "VALUE")
private String value;
#OneToMany(mappedBy = "language", fetch = FetchType.LAZY)
private Set<User> users = new HashSet<>();
#OneToMany(mappedBy = "language", fetch = FetchType.LAZY)
private Set<GameProfileTranslation> gameProfileTranslations = new HashSet<>();
}
For the past two days I have been trying to create a repository method which returns all the GameProfile entities based on a languageId and a gameId given as #RequestParam to my Controller. To be more precise, I want to find all the GameProfiles and filter the nested translations object based on the language given.
I have tried two approaches, which are presented below. Both of these methods actually return all the GameProfile entities but the nested translations object contains all the GameProfileTranslation entities in the database. It is like the translations cannot be filtered by the language.
If I copy & paste the native query below in a Query Tool in pgAdmin I get the result I desire. However, using it through Spring Data returns all the translations.
My two approaches:
#Query(
nativeQuery = true,
value = "SELECT * " +
"FROM game_profile AS gp " +
" INNER JOIN game_profile_translation AS gpt ON gp.game_profile_id = gpt.game_profile_id " +
" INNER JOIN language AS l ON gpt.language_id = l.language_id " +
"WHERE l.language_id = :languageId AND gp.game_id = :gameId ")
List<GameProfile> findAllByGameIdAndLanguageId(#Param("gameId") Long gameId, #Param("languageId") Long languageId);
and
#Query(value = "SELECT gp " +
"FROM GameProfile AS gp JOIN FETCH gp.translations AS gpt " +
"WHERE gp.game.id = :gameId AND gpt.language.id = :languageId ")
List<GameProfile> findAllByGameIdAndLanguageId(#Param("gameId") Long gameId, #Param("languageId") Long languageId);
TLDR: Both of these methods return all the GameProfile entites without filtering the nested translations object. To be more exact, the translations field contains all the translations available in the database regardless of the languageId.
Is there a way to return a List<GameProfile> entities with the translations object filtered by the languageId?
What I have tried:
Filter child object in Spring Data Query
Filtering out nested objects in JPA query
Filter child object in Spring Data Query

JPQL TypedQuery - setParamer does not work

I am trying to fetch an entity which has a one-to-one relation using a named Query. My "where" condition is on the relation entity. I am giving a named parameter. When I execute the query it ignores the parameter passed and giving me all the records.
I tried with positional parameter, it too didn't work.
Query
#NamedQuery(
name = "country.by.region",
query = " select c from Country c join Region r on r.id = :regid"
)
Country Entity
public class Country {
#Id
#Column(name = "COUNTRY_ID")
private String id;
#Column(name = "COUNTRY_NAME")
private String name;
#OneToOne(targetEntity = Region.class, cascade = CascadeType.ALL)
#JoinColumn(name = "REGION_ID")
private Region region;
// ...
}
Region Entity
public class Region {
#Id
#Column(name = "REGION_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "regSeq")
private int id;
#Column(name = "REGION_NAME")
private String name;
// ...
}
DAO Impl
#Override
public List<Country> findBy(Region region) {
TypedQuery<Country> query = getEntityManager().createNamedQuery("country.by.region", Country.class);
query.setParameter("regid", region.getId());
query.setMaxResults(30);
return query.getResultList();
}
Try to correct your query in this way:
select c from Country c join c.region r where r.id = :regid
See also this section of the documentation.

Select from table using parameters from related tables

I have 3 tables (Entities) (postgreSQL):
#Entity
#Table(name = "application", schema = "applications")
public class Application implements Serializable {
#Id
#GeneratedValue
#Column(name = "application_id")
private long applicationId;
#Column(length = 400)
#Size(min=50, max=400)
private String applicationDescribing;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "customerId")
private Customer Customer;
.....
Also I have the abstract class User with child class Customer, which has its own table in DB.
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
private Long userId;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "localitiesId")
private Localities userLocality;
.....
#Entity
#AttributeOverride(name="userId", column=#Column(name="customerId"))
public class Customer extends User {
#Column(length = 8)
private String userType;
.....
User and also Customer table has many to one related table Locality, connected via id.
#Entity
#Table(name = "localities", schema = "resource")
public class Localities implements Serializable {
#Id
#GeneratedValue
#Column(name = "id")
private long localitiesId;
#Column(name = "region", length = 50)
private String region;
....
I try to realize seaching via Application, using (String) keyWord and locality_id (long) locality.
First approach:
#Query(value = "SELECT u FROM Application u WHERE u.applicationDescribing LIKE %:keyWord% " +
"AND userLocality IN (SELECT c FROM User c WHERE c.userLocality IN " +
"(SELECT l FROM Localities l WHERE l.localitiesId = :locality))")
List<Application> getApplicationsByKeyWordsAndRegion(#Param("keyWord") String keyWord, #Param("locality") long locality);
Second approach (recomended by richyen):
#Query(value = "SELECT a FROM Application a " +
"JOIN Customer c ON a.customerid = c.userid " +
"JOIN Localities L ON c.localitiesid = L.id " +
"WHERE a.applicationDescribing LIKE %:keyWord% AND L.id = :locality")
List<Application> getApplicationsByKeyWordsAndRegion(#Param("keyWord") String keyWord, #Param("locality") long locality);
Both dont work. As a result I need List <Application>, which have "keyWord" in describing field and locality in Application.Customer.locality_id. Please help
Is this what you're looking for?
SELECT a.*
FROM application a
JOIN (SELECT * FROM customer c UNION ALL SELECT * FROM executor e) as u on a.customer_id=u.id
JOIN locality l on u.locality_id=l.id
WHERE a.description like '%<keyWord>%'
AND l.region = <region>;
I guess one question I have for you is: how do you differentiation between Customer ID and Executor ID?
Correct answer:
SELECT app FROM Application app
INNER JOIN app.Customer customer
INNER JOIN customer.userLocality
locality WHERE customer.userLocality.id
= :locality AND app.applicationDescribing
LIKE CONCAT('%',:keyWord,'%')

JPQL Query error with Spring JPA and Hibernate

I am trying to fetch all the Skills relating to a specific Person.
Getting the following error when trying to fetch data from MySQL DB using JPQL in my Spring Boot application:
org.hibernate.QueryException: could not resolve property: person_skill of: com.skilltrack.app.Domain.Skill [SELECT s FROM com.skilltrack.app.Domain.Skill s JOIN s.person_skill ps WHERE ps.fk_person = ?1 ]
Here is my code:
Person.java
#Data
#EqualsAndHashCode(exclude = "personCourses")
#Entity(name = "person")
#Table(name = "person")
public class Person implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer person_id;
#NotBlank
private String name;
#NotBlank
private String surname;
#NotBlank
private String email;
#NotBlank
private String password;
#NotBlank
private String personType; //admin or user
#JsonIgnore
#ManyToMany // this mapping is referred to in the Course class
#JoinTable(name = "person_course",
joinColumns = #JoinColumn(name = "fk_person", referencedColumnName = "person_id"),
inverseJoinColumns = #JoinColumn(name = "fk_course", referencedColumnName = "course_id"))
private List<Course> personCourses;
#JsonIgnore
#ManyToMany // this mapping is referred to in the Skill class
#JoinTable(name = "person_skill",
joinColumns = #JoinColumn(name = "fk_person", referencedColumnName = "person_id"),
inverseJoinColumns = #JoinColumn(name = "fk_skill", referencedColumnName = "skill_id"))
private List<Skill> personSkills;
Course.java
#Data
#EqualsAndHashCode(exclude = "skills")
#Entity(name = "course")
#Table(name = "course")
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer course_id;
#NotBlank
private String name;
#NotBlank
private String description;
#JsonIgnore
#ManyToMany(mappedBy = "courses") // mapping defined in Skill already
private List<Skill> skills;
#NotBlank
private Boolean completed; // 0 no 1 yes
#JsonIgnore
#ManyToMany(mappedBy = "personCourses")
private List<Person> coursePerson;
Skill.java
#Data
#EqualsAndHashCode(exclude = "courses")
#Entity(name = "skill")
#Table(name = "skill")
public class Skill {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer skill_id;
#NotBlank
private String name;
#NotBlank
private String description;
#JsonIgnore
#ManyToMany(cascade = CascadeType.ALL) // this mapping is referred to in the Course class
#JoinTable(name = "course_skills",
joinColumns = #JoinColumn(name = "fk_skill", referencedColumnName = "skill_id"),
inverseJoinColumns = #JoinColumn(name = "fk_course", referencedColumnName = "course_id"))
private List<Course> courses;
#JsonIgnore
#ManyToMany(mappedBy = "personSkills")
private List<Person> skill_person;
PersonRepository.java
#Repository
public interface PersonRepository extends JpaRepository<Person, Integer> {
#Query(value =
"SELECT s " +
"FROM skill s JOIN s.person_skill ps " +
"WHERE ps.fk_person = ?1 ")
List<Skill> getPersonSkills(Integer personID);
}
The issue is with the JPQL statement:
"SELECT s FROM skill s JOIN s.person_skill ps WHERE ps.fk_person = ?1 "
I have tried the following variations:
"SELECT s FROM skill s INNER JOIN s.person_skill ps WHERE ps.fk_person = ?1"
"SELECT s FROM skill s JOIN FETCH s.person p WHERE ps.fk_person = ?1"
"SELECT s FROM skill s JOIN s.person p JOIN s.person_skill ps WHERE ps.fk_person = ?1"
"SELECT s FROM skill s JOIN person p JOIN person_skill ps WHERE ps.fk_person = ?1"
How should I change my JPQL query to return a list of Skills relating to a specific Person?
Change your wrongly typed entity name from person_skill to skill_person defined in Skill.java
You have this error org.hibernate.QueryException because you have wrong name of field in JPQL.
Skill entity has field with name: skill_person.
You have another name of field in your JPQL query: person_skill.
It is possible to acieve the result without writing a custom query.
Spring builds endpoints like "skills" and "persons" by default, when used, they show other endpoints like "http://localhost:8080/skills/{id}/skill_person"
Playing around with those will lead you to find any combination of results.
This is possible since the many to many mappings are defined bidirectionally the way thy are in the current project and spring is awesome.

JPA : Many to Many query help needed

I have four entities that are involved in a query that I'm having a little trouble with. The relationship is as follows : Exchange----*Contract*----*Combo----*Trade and the (simplified) entities are as follows:
#Entity
public class Exchange implements Serializable {
#Id(name="EXCHANGE_ID")
private long exchangeId;
#Column
private String exchangeShortName;
}
#Entity
public class Contract implements Serializable {
#Id
private long contractId;
#Column
private String contractName;
#ManyToOne
#JoinColumn(name="EXCHANGE_ID")
private Exchange exchange;
#ManyToMany
#JoinTable(name="CONTRACT_COMBO",
joinColumns = { #JoinColumn(name="CONTRACT_ID") },
inverseJoinColumns = {#JoinColumn(name="COMBO_ID")})
private Set<Combo> combos;
#Column(name = "ACTIVE_FLAG")
private String activeFlag;
}
#Entity
public class Combo implements Serializable {
#Id
#Column(name="COMBO_ID")
private Integer id;
#ManyToMany
#JoinTable(name="CONTRACT_COMBO",
joinColumns = { #JoinColumn(name="COMBO_ID") },
inverseJoinColumns = {#JoinColumn(name="CONTRACT_ID")})
private Set<Contract> legs;
#OneToMany(mappedBy = "combo")
private Set<Trade> trades;
}
#Entity
public class Trade implements Serializable {
#Id
#Column(name="TRADE_ID")
private long tradeId;
#Column(name="REFERENCE")
private String reference;
#ManyToOne
#JoinColumn(name="COMBO_ID")
private Combo combo;
}
I want to get a list of all trades for a particular exchange which I can't quite get to work with MEMBER OF. Any help would be appreciated.
Try this
select distinct t
from Trade t
join t.combo c
join c.legs l
join l.exchange e
where e.exchangeShortName = 'whatever'
Not really optimized, but I think this should do the trick:
Long exchangeId = Long.valueOf(5324623L);
List<Trade> trades = em.createQuery("select T from Trade T where T in " +
"(select distinct C from Combo c where c member of " +
"(select e.combos from Exchange e where e.id = :id) " +
")").setParameter("id", exchangeId).getResultList();

Categories