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 .
Related
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 ..
};
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
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.
I have this entities
ResourceForSearch.java
#Entity
#Table(name = "RESOURCES")
public class ResourceForSearch {
#Id
#Column(name = "IDRESOURCE")
private long id;
private String defaultText;
#OneToOne
#JoinColumn(name = "IDRESOURCE", referencedColumnName = "IDRESOURCE")
private TranslationForSearch translation;
// getters and setters
}
TranslationForSearch.java
#Entity
#Table(name = "TRANSLATIONS")
public class TranslationForSearch {
#Column(name = "IDTRANSLATION")
private long id;
#Column(name = "IDLANGUAGE")
private long languageId;
private String translation;
#Id
private long idResource;
// getters and setters
}
Normally a RESOURCE is in relation with N TRANSLATION(s) and a TRANSLATION is identified by IDTRANSLATION, but given a value for TRANSLATIONS.IDLANGUAGE, this relation becomes OneToOne because in a certain language (identified by IDLANGUAGE) the translation is only one for each resource (a resource is a label I want to translate in different languages).
So only for search purposes I can put the #Id annotation on the TranslationForSearch.idResource field, not on the TranslationForSearch.id field.
Here is the Criteria query I wrote:
CriteriaQuery<ResourceForSearch> searchQuery = criteriaBuilder.createQuery(ResourceForSearch.class);
Root<ResourceForSearch> searchRoot = searchQuery.from(ResourceForSearch.class);
searchQuery.select(searchRoot);
Join<ResourceForSearch, TranslationForSearch> translationJoin = resourceRoot.join("translation", JoinType.LEFT);
// Here I filter the JOIN clause by language id, having the OneToOne relationship
translationJoin.on(criteriaBuilder.equal(translationJoin.get("languageId"), 22));
// Here I want to fetch the TRANSLATIONS included in the join
resourceRoot.fetch("translation");
List<ResourceForSearch> resultList = em.createQuery(searchQuery).getResultList();
The problem here is that the statement
for (ResourceForSearch resourceForSearch : resultList) {
if (resourceForSearch.getTranslation() != null)
System.out.println("Resource id: " + resourceForSearch.getId() + " Language id: " + resourceForSearch.getTranslation().getLanguageId());
}
Prints
Resource id: 81 Language id 22
Resource id: 85 Language id 30
Resource id: 86 Language id 30
Resource id: 87 Language id 30
Resource id: 88 Language id 30
It seems that the fetch in the query doesn't work because the query is returning entities which doesn't respect the join clause.
EDIT 1:
This is the SQL query generated by Hibernate
SELECT resourcefo0_.IDRESOURCE AS IDRESOURCE1_2_,
resourcefo0_.defaultText AS defaultText6_2_
FROM WIN_RESOURCES resourcefo0_
LEFT OUTER JOIN WIN_TRANSLATIONS translatio1_
ON resourcefo0_.IDRESOURCE =translatio1_.idResource
AND (translatio1_.IDLANGUAGE =22)
Which returns a number of rows equal to the numbero of rows of WIN_RESOURCES. Moreover if I edit the query adding the select field translatio1_.IDLANGUAGE, it returns me a value of 22 of null (not 30).
SELECT resourcefo0_.IDRESOURCE AS IDRESOURCE1_2_,
resourcefo0_.defaultText AS defaultText6_2_,
translatio1_.IDLANGUAGE
FROM WIN_RESOURCES resourcefo0_
LEFT OUTER JOIN WIN_TRANSLATIONS translatio1_
ON resourcefo0_.IDRESOURCE =translatio1_.idResource
AND (translatio1_.IDLANGUAGE =22)
Where can be the problem?
Thank you
I want to fetch records from database without the primary key, for a table that i have created. Here is the model class for it.
#Entity
#Table(name = "DOCTORS_INFORMATION")
public class DoctorInformation {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "DOCTOR_ID")
private int ID;
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "LAST_NAME")
private String lastName;
#Column(name = "ADDRESS_LINE1")
private String addressLine1;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "CITY")
private Location location;
//along with setters and getters
It is very difficult to remember the Doctor_Id for every doctor, and logically First_Name or anything cannot be made a primary key. All the tutorials and lecture videos that i have gone through fetch records using session.get where session is an object of class Session. In the source code of Session class all the overloaded methods of get mandatory take id as a parameter...
Is their a workaround for the above problem?
There are multiple options. All of the following examples search for a doctor whose lastName contains Johnson.
Criteria
String lastNameToSearchFor = "Johnson";
List doctors = session.createCriteria(DoctorInformation.class)
.add( Restrictions.like("lastName", "%"+lastNameToSearchFor+"%") ) // the leading wild card can become a problem since it cannot be indexed by the DB!
.addOrder( Order.asc("lastName") )
.list();
http://docs.jboss.org/hibernate/orm/3.5/javadocs/org/hibernate/Criteria.html
HQL
String lastNameToSearchFor = "Johnson";
String hql = "FROM " + DoctorInformation.class.getName() + " di WHERE di.lastName LIKE :lastName ORDER BY di.lastName ASC";
Query query = session.createQuery(hql);
query.setParameter("lastName", "%" + lastNameToSearchFor + "%"); // again, the leading wild card may be a problem
List doctors = query.list();
http://www.tutorialspoint.com/hibernate/hibernate_query_language.htm
Native SQL
String lastNameToSearchFor = "Johnson";
String sql = "SELECT * FROM DOCTORS_INFORMATION WHERE lastName LIKE :lastName ORDER BY lastName ASC";
Query query = session.createSQLQuery(sql).addEntity(DoctorInformation.class);
query.setString("lastName", "%" + lastNameToSearchFor + "%"); // again, the leading wild card may be a problem
List doctors = query.list();
If you want to add capability to search with, let's say firstName, then you might want to look into Hibernate's Disjunction: Creating a hibernate disjunction query programatically