Joining an column with conditions to JPA entity - java

I have two tables:
person:
- person_id
- name
meeting:
- meeting_id
- person_id
- date
One person can have many meetings. My JPA entity for person looks like:
#Entity
#Table(name = "person")
#Getter
public class Person {
#Id
private Integer personId;
private String name;
#Formula("(SELECT m.date FROM meeting m WHERE ROWNUM <= 1 AND m.person_id = person_id ORDER BY m.date DESC)")
private Date lastMeetingDate;
The problem is: How to include last meeting date into Person object. Code above generates ORA-00936 error (I'm using Oracle database).
SQL geneated by findPersonById():
select person0_.person_id as person_id1_10, person0_.name as name2_10, (SELECT m.date FROM meeting m WHERE ROWNUM <= 1 AND m.person_id = person0_.person_id ORDER BY m.date DESC) as formula0_ form person person0_ where person0_.person_id=?

You should try:
SELECT max(m.date) FROM meeting m WHERE m.person_id = person_id

Related

How to retrieve data from entity with composed foreign key?

I can't retrieve data from an Entity from my Oracle database.
Config :
Java 8
Hibernate 5.4.20
Jpa 2.2
Oracle11g
My situation :
A JPA Entity Reparation with "classic" data, and an ID from a Vehicule.
A JPA Entity Vehicule stored without a unique ID column because in my table, each vehicule has a period (identify by startDate and endDate) with unique properties.
Example :
id
situation
startDate
endDate
1
EN_USINE
01/01/2020
14/01/2020
1
TRANSFERT
15/01/2020
17/01/2020
1
EN_CONCESSION
18/01/2020
01/03/2020
The primary key of Vehicule is defined by 3 columns : id/startDate/endDate.
I want to get data from Vehicule in the entity Reparation, i got the id of the Vehicule in the table Raparation, and the jointure is done with the date of Reparation that has to be between startDate and endDate in the table Vehicule.
How can i do that ?
#Entity
Public class Vehicule {
#Column(name="id")
String id
#Column(name="startDate")
LocalDate startDate
#Column(name="endDate")
LocalDate endDate
}
#Entity
Public class Reparation {
#Column(name="id_reparation")
String idReparation
#Column(name="date_of_reparation")
LocalDate dateOfReparation
Vehicule vehicule
}
I tried the #joinFormula, but I didn't get it
How would you do that ?
Edit :
I tried a lot of things, like :
#ManyToOne
#JoinColumnsOrFormulas({
#JoinColumnOrFormula(formula = #JoinFormula(value = "select id from table_vehicule", referencedColumnName = "ID")),
#JoinColumnOrFormula(formula = #JoinFormula(value = "select startDate from table_vehicule where dateOfReparation > startDate", referencedColumnName = "startDate")),
#JoinColumnOrFormula(formula = #JoinFormula(value = "select endDate from table_vehicule where dateOfReparation < endDate", referencedColumnName = "endDate"))
})
Vehicule vehicule;
First thing is ,you can't have an #Entity without providing any #Id to that class ,second if you have an ID composed of multiple fields ,like in your case here ,you make it Embeddable ,and then you declare it in your class as a class member ,here's an example :
#Embeddable
public class MyComposedID {
//Here ,I personaly don't see the usage of id it's neither a surrogate key nor
//used to have any impact on the uniqueness of the Composed Id since you said
//it's uniquely identified by the start and end date
#Column(name="id")
String id;
#Column(name="start_date")
LocalDate startDate;
#Column(name="end_date")
LocalDate endDate;
}
Now you declare it in your Vehicle Entity as an EmbeddedID as follows :
#Entity
Public class Vehicule {
#EmbeddedId
MyComposedID id;
}
As for the query goes , from what i understood ,you want to get the vehicle where the Reparation.reparationDate is between the Vehicle.startDate and Vehicle.endDate,
here's an example with a JPQL query :
Query query = em.createQuery("SELECT v FROM Vehicle v "+
"INNER JOIN Reparation r ON r.reparationDate>= v.id.startDate "+
"AND r.reparationDate<=v.id.endDate");
Hope this helps you .

How to use CriteriaBuilder to join on selection

How to join a table with a selection using CriteriaBuilder?
Suppose I have this query:
SELECT
tbl1.*,
tbl2.total
FROM
table_1 tbl1
LEFT JOIN
(SELECT col_id AS id, SUM(value) AS total FROM table_2 WHERE active = 1 GROUP BY col_id) tbl2
ON
tbl1.id = tbl2.id;
Where the definition of table_1 is:
CREATE TABLE table_1(
id NUMBER(19, 0),
-- other columns
)
... and table_2 ...
CREATE TABLE table_2(
id NUMBER(19, 0),
col_id NUMBER(19, 0),
value NUMBER(14, 2),
-- other columns
FOREING KEY (col_id) REFERENCES table_1(id);
)
This is not possible with plain JPA or Hibernate. With Hibernate you can model this query if the subquery is static, but for dynamic subqueries you will need something like Blaze-Persistence which works on top of JPA/Hibernate and provides support for these things.
For the static query solution you can do this:
#Entity
#Table(name = "table1")
public class Table1 {
#Column(name = "id")
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id", insertable = false, updatable = false)
private Table2 table2;
}
#Entity
#Subselect("SELECT col_id AS id, SUM(value) AS total FROM table_2 WHERE active = 1 GROUP BY col_id")
public class Table2Query {
#Column(name = "id")
private Integer id;
#Column(name = "total")
private BigDecimal total;
}
Here is a nice article by Vlad Mihalcea about Blaze-Persistence if you want a dynamic solution i.e. where the query structure isn't fixed: https://vladmihalcea.com/blaze-persistence-jpa-criteria-queries/
Use the join method in root and use it to get the values from the other table.
Note: you need to add the relation in the entity depending on the relationship of these tables (onetone, manytoone or onetomany). Something like this:
Entity code Table1:
#OneToOne
private Table2 table2;
Search code example:
(Root<Table1> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> {
Join<Table1, Table2> joinTable2 = root.join("table2");
cb.equal(joinTable2.get("active"), 1);
.. other filters ..
};

Fetching and filtering data from multiple JOIN by JPA

I have 3 entities which bound with each other.
Group
#Entity
#Table(name = "`group`")
public class Group implements Serializable {
#OneToMany(mappedBy = "group",fetch = FetchType.EAGER)
#Fetch(FetchMode.SUBSELECT)
private List<Student> students;
}
Student
#Entity
#Table(name = "student")
public class Student implements Serializable {
#OneToMany(mappedBy = "student",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#Fetch(FetchMode.SUBSELECT)
private List<Rating> ratings;
}
Rating
#Entity
#Table(name = "rating")
public class Rating implements Serializable {
#Temporal(TemporalType.DATE)
#Column
private Date date;
#ManyToOne
#JoinColumn(name = "id_student")
private Student student;
}
I need to get the group by id with all students with their rating which have a date between.
I trying these cases to solve the problem
#Query(value = "select g from Group g INNER JOIN g.students s ON g.id = :id INNER JOIN s.ratings r ON r.student.id = s.id and r.date between :dateStart and :dateEnd GROUP BY g.id")
#Query(value = "SELECT * FROM Group g INNER JOIN student ON g.id_group = :id RIGHT JOIN rating on student.id_student = rating.id_student and rating.`date` between :dateStart and :dateEnd GROUP BY g.id_group",nativeQuery = true)
#Query(value = "select g from Group g WHERE g.id = :id and g.students IN (select s from g.students s JOIN s.ratings r where r.date between :dateStart and :dateEnd) GROUP BY g.id")
#Query(value = "SELECT g from Group g JOIN g.students s ON g.id = :id JOIN s.ratings r on s.id = r.student.id and r.date between :startMonth and :endMonth")
But none of these not filtering ratings as must.
Locally with this query all works
SELECT * from `group` JOIN student ON `group`.id_group = 1 LEFT JOIN rating on student.id_student = rating.id_student and rating.`date` between '2019-02-01' and '2019-02-28';
Anyone have idea whats wrong or how to conver local query to JPQL?
UPD
To avoid the questions about the date I use the same logic with the date to get all rating between date.
Repository
#Query(value = "Select r from Rating r where r.student.id = :studentId and r.stageOfApprove = :stageOfApprove and r.date between :startMonth and :endMonth order by r.paragraph.id asc ")
List<Rating> findAllRatingsByIdStudentAndStageOfApproveGreaterThan(#Param("studentId") Long studentId, #Param("stageOfApprove") Integer stageOfApprove,
#Param("startMonth") Date startMonth, #Param("endMonth") Date endMonth);
Service
DateTime currentDate = new DateTime();
DateTime minDays = currentDate.dayOfMonth().withMinimumValue().withTimeAtStartOfDay();
DateTime maxDays = currentDate.dayOfMonth().withMaximumValue().withTimeAtStartOfDay();
ratings = ratingRepository.findAllRatingsByIdStudentAndStageOfApproveGreaterThan(studentId,1,minDays.toDate(),maxDays.toDate());
return ratings;

how to left join one to many relationship to get count of child table with criteria builder in JPA

Here Orders has customer, I want to get customer table along with count of orders for each record.
how to left join order table here? if customer has one to many relationship with order.
'*
Class customer{
#Id
#Column(name = "id")
int id;
#Column(name = "id")
String name;
#oneToMany
List<Order> orders;
}
*'
' *
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<CustomerView> q = cb.createQuery(CustomerView.class);
Root<Customer> root = q.from(Customer.class);
q.select((Selection<? extends CustomerView>)
Root<Order> orderRoot = q.from(Geofence.class) cb.tuple(root,cb.count(orderRoot.get("customer")).alias("orderCount")));
q.where(cb.equal(root.get("id"), orderRoot.get("customer")));
q.groupBy(root.get("id"));
TypedQuery<CustomerView> query = em.createQuery(q);
List<CustomerView> results = query.getResultList();
*'

JPA Discriminator using Spring-Data Repositories and JPQL Query

I have two entities in MySQL as below. The primary key of nnm_tran is a composite of id and source. The primary key of bargains is actually a foreign key link to the nnm_tran table
I'm trying to use JPA inheritance to represent these.
nnm_tran entity
#Entity
#Table(name = "nnm_tran")
#IdClass(CommonTransactionKey.class)
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "bargain_flag", discriminatorType = DiscriminatorType.CHAR)
#DiscriminatorValue("N")
public class CommonTransaction {
#Id
#Column(name = "id", nullable = false)
private String transactionId;
#Column(name = "plan_number", nullable = false)
private String planNumber;
#Column(name = "tran_date")
private LocalDateTime transactionDatetime;
#Column(name = "bargain_flag")
private String bargainFlag;
...
}
bargains entity
#Data
#EqualsAndHashCode(callSuper = true)
#Entity
#Table(name = "bargains")
#DiscriminatorValue("B")
#PrimaryKeyJoinColumns({ #PrimaryKeyJoinColumn(name = "nnm_tran_id", referencedColumnName = "id"), #PrimaryKeyJoinColumn(name = "nnm_tran_source", referencedColumnName = "source") })
public class Bargain extends CommonTransaction implements Serializable {
#Column(name = "unit_price")
private BigDecimal unitPrice;
#Column(name = "client_price")
private BigDecimal clientPrice;
...
}
I think so far this is all hooked up correctly. My problem comes when I attach a spring-data repository with a custom query.
Repository
public interface CommonTransactionRepository extends CrudRepository<CommonTransaction, CommonTransactionKey> {
#Query("select t from CommonTransaction t left join IoPlan p ON t.planNumber = p.planNumber "
+ "where (p.planNumber is NULL or p.planNumber = '') "
+ "and t.transactionDatetime between ?1 and ?2 "
+ "and t.cancelled = false")
public Iterable<CommonTransaction> findOrphanedTransactionsByTranDate(LocalDateTime fromDate, LocalDateTime toDate);
...
}
When this gets proxied and the method is executed it generates the SQL statement
SELECT DISTINCT nnm_tran.bargain_flag FROM nnm_tran t1 LEFT OUTER JOIN io_plan t0 ON (t1.plan_number = t0.plan_number) WHERE ((((t0.plan_number IS NULL) OR (t0.plan_number = ?)) AND (t1.tran_date BETWEEN ? AND ?)) AND (t1.CANCELLED = ?))
The issue with this is that the nnm_tran table is aliased to t1 but the discriminator column is referencing the full table name nnm_tran.bargain_flag The result is a lovely
UnitOfWork(17171249)--Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'nnm_tran.bargain_flag' in 'field list'
Question here is, am I doing something wrong or is this a bug in spring-data and/or eclipselink?
Versions: spring-data 1.7.2, Eclipselink 2.5.2, MySQL 5.6.28
Using #manish's sample app as a starting point I started layering back on the complexity that was missing and quickly stumbled across the thing causing the rogue SQL. It was down to the join I had performed in the JPQL
NOTE: If you've come here from the future then ignore the remainder of this answer and instead use #Chris's comment instead.
Most of the time I don't need to look at or even think about the IoPlan table that can be seen in the #Query
#Query("select t from CommonTransaction t left join IoPlan p ON t.planNumber = p.planNumber "
+ "where (p.planNumber is NULL or p.planNumber = '') "
+ "and t.transactionDatetime between ?1 and ?2 "
+ "and t.cancelled = false")
and so this table is not a part of the CommonTransaction entity as a field. Even the result of this query doesn't really care because it's looking only as a one off for CommonTransaction with no associated join in the IoPlan table.
When I added the join back in to the sample app from #manish it all broke in the same way my app has in EclipseLink, but broke in a different way for Hibernate. Hibernate requires a field for you to join with, which if you ask me defeats the purpose of writing the join in the #Query. In fact in Hibernate you have to define the join purely in JPA so you might as well then use dot notation to access it in the JPQL.
Anyway, going along with this idea I tried adding a dummy field to hold an IoPlan in my CommonTransaction entity and it almost worked. It defaulted some of the join logic but it was closer
SELECT DISTINCT t1.bargain_flag FROM nnm_tran t1 LEFT OUTER JOIN io_plan t0 ON ((t0.ID = t1.IOPLAN_ID) AND (t1.plan_number = t0.plan_number)) WHERE ((((t0.plan_number IS NULL) OR (t0.plan_number = ?)) AND (t1.tran_date BETWEEN ? AND ?)) AND (t1.CANCELLED = ?))
In this case t1.IOPLAN_ID and t0.ID don't exist. So I ended up defining the entire join in my CommonTransaction entity
#OneToOne
#JoinColumn(insertable = false, updatable = false, name = "plan_number", referencedColumnName = "plan_number")
private IoPlan ioPlan;
and voila, it started working. It's not pretty and now I have a redundant join condition
LEFT OUTER JOIN io_plan t1
ON ((t1.plan_number = t0.plan_number) AND (t0.plan_number = t1.plan_number))
but I can fix that. It's still annoying that I have to define a field for it whatsoever, I don't actually want or need it there, not to mention that the result from this query is returning CommonTransaction entities that have no IoPlan so the field will be permanently null.

Categories