Querydsl: how to downcast a join entity? - java

I'm looking for a way to make a join on a one-to-many relation where the many-side is defined through inheritance and the right part of the join is restricted to a specific subclass (downcast).
Say I have the entities below (example taken from here):
#Entity
public class Project {
#Id
#GeneratedValue
private long id;
private String name;
#OneToMany(cascade = CascadeType.ALL)
private List<Employee> employees;
.............
}
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#Entity
#DiscriminatorColumn(name = "EMP_TYPE")
public class Employee {
#Id
#GeneratedValue
private long id;
private String name;
.............
}
#Entity
#DiscriminatorValue("F")
public class FullTimeEmployee extends Employee {
private int annualSalary;
.............
}
#Entity
#DiscriminatorValue("P")
public class PartTimeEmployee extends Employee {
private int weeklySalary;
.............
}
#Entity
#DiscriminatorValue("C")
public class ContractEmployee extends Employee {
private int hourlyRate;
.............
}
I can easily build join queries that involve properties defined in the superclass Employee, like:
JPAQuery query = ...
QProject project = new QProject("p");
QEmployee employee = new QEmployee("e");
query.join(project.employees, employee);
query.where(employee.name.startsWith("A"));
But if I want to access a property of subclass, say FullTimeEmployee.annualSalary, and hence restrict the join to that sub-type, how do I do that?
How do I build the the equivalent of following JPQL:
SELECT DISTINCT p FROM Project p JOIN TREAT(p.employees AS FullTimeEmployee) e WHERE e.annualSalary > 100000

You can do it like this:
EntityManager em = ...;
QProject p = QProject.project;
QFullTimeEmployee e = QFullTimeEmployee.fullTimeEmployee;
List<FullTimeEmployee> emps = new JPAQuery<>(em)
.select(p)
.distinct()
.from(p)
.innerJoin(p.employees, e._super)
.where(e.annualSalary.gt(100000))
.fetch();
See also this post on the Querydsl forum: https://groups.google.com/d/msg/querydsl/4G_ea_mQJgY/JKD5lRamAQAJ

Related

Java entity modeling with table that relates to several others

I have a doubt about how the modeling of my entity would be. Come on, I have a table in the database that serves to save documents from my system, this table has the columns id, fk_id (element foreign key), fk_table (entity name) and file_name (stores the name of my file) .
I did a lot of research before posting my question here, but I didn't find anything related to it, what would my entities, user, patient and doctor?
DB:
id
fk_id
fk_table
file_name
1
21
user
test1.jpg
2
32
doctor
test2.pdf
3
61
user
test10.pdf
4
100
patient
test5.jpg
Class:
public class User{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String LastName;
// What would a one-to-many relationship look like?
}
public class patient{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
// What would a one-to-many relationship look like?
}
You can use #Where. But be aware that #Where is a Hibernate annotation. It's not in the JPA standard.
For example in the User entity: (I assume that your table is mapped to an entity called Document)
#Where( clause = "fk_table = 'user'")
#JoinColumn(name = "fk_id")
#OneToMany
private List<Document> documents = new ArrayList<>( );
The following is based only on standard JPA annotations. The idea is to create an inheritance hierarchy for the documents table. The base is:
#Entity
#Table(name = "XX_DOCUMENT")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "fk_table")
public abstract class BaseDocument {
#Id
#GeneratedValue(strategy=SEQUENCE)
private Long id;
#Column(name = "file_name")
private String fileName;
}
Here we define that all entities extending this will go to the same table, with the fk_table column to discriminate. The entities extending it are defined as follows:
#Entity
#DiscriminatorValue("doctor")
public class DoctorDocument extends BaseDocument {
#ManyToOne
#JoinColumn(name = "fk_id")
private Doctor doctor;
}
#Entity
#DiscriminatorValue("patient")
public class PatientDocument extends BaseDocument {
#ManyToOne
#JoinColumn(name = "fk_id")
private Patient patient;
}
// and so on
The interesting thing is that we are reusing the column fk_id to point to the right table. From a small experiment, Hibernate seems to not have problems with it. I would suggest that you manage the DB creation another way just to be safe.
The Doctor, Patient etc need not have a common base class, e.g.:
#Entity
#Table(name = "XX_DOCTOR")
public class Doctor {
#Id
#GeneratedValue(strategy=SEQUENCE)
private Long id;
#OneToMany(mappedBy = "doctor")
private Collection<DoctorDocument> documents = new ArrayList<>();
// any doctor-specific fields
}
#Entity
#Table(name = "XX_PATIENT")
public class Patient {
#Id
#GeneratedValue(strategy=SEQUENCE)
private Long id;
#OneToMany(mappedBy = "patient")
private Collection<PatientDocument> documents = new ArrayList<>();
// any patient-specific fields
}
// and so on
You can read a (doctor, patient, ...)'s documents from the relevant collection. You can even query BaseDocument instances based on any criteria.
You can even go ahead and do more fabcy stuff with the Java code. E.g. define an interface HasDocuments:
public interface HasDocuments<D extends BaseDocument> {
Collection<D> getDocuments();
}
Doctor, Patient, ..., implements this, so they can all be treated the same way.

JPA - How to populate a DTO using criteriaBuilder.construct from an Entity Inheritance

i have these (minimal and partial) JPA Entities:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "type")
public abstract class Employee implements Serializable {
#Id
protected Long id;
...
}
#Entity
...
public class FullTimeEmployee extends Employee implements Serializable {
private BigDecimal salary;
...
}
#Entity
...
public class PartTimeEmployee extends Employee implements Serializable {
private BigDecimal hourlyWage;
private BigDecimal maxHoursWeek;
...
}
Currently we are using spring-data-jpa to query, like this:
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
List<Employee> findAll();
}
But, this way we have 'N+1' problems and lot of selects, so, i decide to use Criteria API and select it into a DTO, like this:
public List<EmployeeDTO> findAll() {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<EmployeeDTO> criteriaQuery = criteriaBuilder.createQuery(EmployeeDTO.class);
Root<Employee> root = criteriaQuery.from(Employee.class);
Root<FullTimeEmployee> fullTimeEmployeeRoot = criteriaBuilder.treat(root, FullTimeEmployee.class);
Root<PartTimeEmployee> partTimeEmployeeRoot = criteriaBuilder.treat(root, PartTimeEmployee.class);
criteriaQuery.select(criteriaBuilder.construct(EmployeeDTO.class,
root.get("id"), root.get("name"),
fullTimeEmployeeRoot.get("salary"),
partTimeEmployeeRoot.get("hourlyWage"))
);
return this.entityManager
.createQuery(criteriaQuery).getResultList();
}
And this is our (example) DTO
#Getter
#Setter
#AllArgsConstructor
public class EmployeeDTO {
private Long id;
private String name;
private BigDecimal fullTimeEmployeeSalary;
private BigDecimal partTimeEmployeeHourlyWage;
private BigDecimal partTimeEmployeeMaxHoursWeek;
...
}
But, we got 0 results.
Our hibernate output looks like this:
SELECT employee.id, employee.name, fullTimeEmployee.salary, partTimeEmployee.hourlyWage partTimeEmployee.maxHoursWeek ... FROM employees employee INNER JOIN fullTimeEmployees fullTimeEmployee on fullTimeEmployee.id = employee.id INNER JOIN partTimeEmployees partTimeEmployee on partTimeEmployee.id = employee.id
My question is: What is the best way to do that? How i transform these INNER JOINs to LEFT JOINs? Exists a better way?
Thanks. :)
First let me say thank you for the very nicely formatted question -- you did a great job of making a minimal, complete, and verifiable example. I don't think you want to project the result into such a class as you describe. Having a class with either a salary or hourlyWage value means you have keep checking for null all the time and that's a pretty bad design decision. Better is to get the list of different types from the employeeRepository and use object oriented principles to handle the mixed types. This is exactly what OOP was invented for.
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "type")
public abstract class Employee implements Serializable {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
protected Long id;
public abstract BigDecimal getPay();
#Entity
public class FullTimeEmployee extends Employee {
private BigDecimal salary;
private int daysWorked;
#Override
public BigDecimal getPay() {
return salary
.multiply(BigDecimal.valueOf(daysWorked))
.divide(BigDecimal.valueOf(Year.now().length()), RoundingMode.HALF_DOWN);
}
#Entity
public class PartTimeEmployee extends Employee {
private BigDecimal hourlyWage;
private int hoursWorked;
#Override
public BigDecimal getPay() {
return hourlyWage.multiply(BigDecimal.valueOf(hoursWorked));
}
and then
BigDecimal sum = employeeRepo.findAll()
.stream()
.map(e->e.getPay())
.reduce(BigDecimal.ZERO, BigDecimal::add);

Spring data, get entity from list with unidirectional

I have
#Entity
public class Person{
...
#OneToMany
#JoinColumn(name = "person_id")
private List<Email> emailList = new ArrayList<>();
...
}
#Entity
public class Email{
...
private String emailAddress;
...
}
I need to get Person by emailAddress. I saw this answer: dont like.
But i wonder - can i do this with only spring data, i mean - without query.
This should work:
Person findPersonByEmailList_EmailAddress(String email);
And this as well:
#Query("select p from Person p join p.emailList l where l.emailAddress = ?1")
Person getPersonByEmail(String email);

hibernate joined strategy inheritance and polymorphism

In hibernate, when I use the joined strategy. does hibernate support polymorphism?
for example:
#Entity
#Table(name = "PERSON")
#Inheritance(strategy=InheritanceType.JOINED)
public class Person {
#Id
#GeneratedValue
#Column(name = "PERSON_ID")
private Long personId;
#Column(name = "FIRSTNAME")
private String Fullname;
public Person() {
}
public Person(String fullname) {
this.Fullname= fullname
}
}
and the derived class:
#Entity
#Table(name="EMPLOYEE")
#PrimaryKeyJoinColumn(name="PERSON_ID")
public class Employee extends Person {
#Column(name="department_name")
private String departmentName;
public Employee() {
}
public Employee(String fullname, String departmentName,) {
super(fullname);
this.departmentName = departmentName;
}
}
also all the fields include the getter and setters.
so in my main, when I'll do this:
session.beginTransaction();
person e = new Employee();
e.setFullname("james");
e.setdepartmentName("R&D");
session.getTransaction().commit();
I know for a fact that if e was of Employee type, hibernate would have created a row for both Employee and Person tables.
but for this example will hibernate generate queries for person and employee?
in other words, will hibernate support the polymorphic behavior?
When I understand you question correctly than you want to know if hibernate does in that case automtically return Employees when you query for all Persons. Additionally if it inserts an Employee when the object is declared as Person p = new Employee().
Short answer to both yes. More detailed.
The insert operation is based on the actual type of the object and not on exactly the type written in your sourcecode.
Related to querying of Persons. Hibernate does left outer join all subtypes so you will get also Employess back when you do the following:
Query query = session.createQuery("From Person ");
List<Person> persons = query.getResultList();

Polymorphic CriteriaQuery without inverse relationship

I have the following EJB structure. Don't wonder about Animal and Inventory, these classes are only here to demonstrate the structure in a simplified way (Update: I have revised the class names to construct a better understandable example. Another implementation of IdTag might be a BarcodeId). Note that there is no inverse relationship from IdTag to Animal or Inventory, and let's assume the RfidTag.code is unique. I read Retrieving Polymorphic Hibernate Objects Using a Criteria Query and Hibernate polymorphic query but these discussions does not seem to answer my question.
public interface ItemWithIdTag
{
IdTag getIdTag();
void setIdTag(IdTag idTag);
}
#Entity public class Animal implements ItemWithIdTag,Serializable
{
#Id #GeneratedValue(strategy=GenerationType.AUTO) private long id;
#OneToOne(cascade = CascadeType.ALL)
private IdTag idTag;
}
#Entity public class Inventory implements ItemWithIdTag,Serializable
{
#Id #GeneratedValue(strategy=GenerationType.AUTO) private long id;
#OneToOne(cascade = CascadeType.ALL)
private IdTag idTag;
}
#Entity #Table(name = "IdTag") #Inheritance(strategy= InheritanceType.JOINED)
public class IdTag implements Serializable
{
#Id #GeneratedValue(strategy=GenerationType.AUTO) private long id;
private Date created;
}
#Entity #Table(name = "RfidTag")
public class RfidTag extends IdTag implements Serializable
{
private String code;
}
Now I want to query either Animal or Inventory for a given RfidTag.code like Animal ejb = bean.fEntityWithRfidTag(Animal.class,"myRfIdCode");
public <T extends ItemWithIdTag> T fOwner(Class<T> type, String catName)
{
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(type);
Root<T> from = criteriaQuery.from(type);
Path<Object> path = from.join("idTag").get("code");
CriteriaQuery<T> select = criteriaQuery.select(from);
select.where(criteriaBuilder.equal(path, catName));
TypedQuery<T> q = em.createQuery(select);
T result = (T)q.getSingleResult();}
return result;
}
Unfortuately I get the following errror:
javax.ejb.EJBException: java.lang.IllegalArgumentException:
Unable to resolve attribute [code] against path [null]
I assume that this is related to the inheritance IdTag -> RfidTag and Animal only knows about IdTag and not the RfidTag.code. Are queries like this possible?
If you are using EclipseLink, solution is simple. Modify the Path criteria to cast to RfIdTag:
Path<Object> path = ((Path) from.join("idTag").as(RfIdTag.class)).get("code");
If you are using Hibernate, replace your method with:
public static <T extends ItemWithIdTag> T fOwner(Class<T> type, String catName) {
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(type);
Root<T> fromType = criteriaQuery.from(type);
Root<RfIdTag> fromRfId = criteriaQuery.from(RfIdTag.class);
Path<Object> pathCode = fromRfId.get("code");
Path<Object> pathIdTagType = fromType.get("idTag");
Path<Object> pathIdTagRfId = fromRfId.get("id");
CriteriaQuery<T> select = criteriaQuery.select(fromType);
select.where(
criteriaBuilder.equal(pathCode, catName),
criteriaBuilder.equal(pathIdTagType, pathIdTagRfId));
TypedQuery<T> q = em.createQuery(select);
return q.getSingleResult();
}
This makes a "join" ("a filtered cartesian product") between "T" and "RfIdTag".

Categories

Resources