I have 3 entities. Customer, Process and Document.
A Customer has many processes and a process has many documents.
I want to sort customers by document's updateDate.
My entities are like below;
Customer-
#Entity
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Process> processes = new ArrayList<>();
// getter, setter etc.
}
Process-
#Entity
public class Process {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String type;
#ManyToOne(fetch = FetchType.LAZY)
private Customer customer;
#OneToMany(mappedBy = "process", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Document> documents = new ArrayList<>();
//getter, setter etc.
}
Document-
#Entity
public class Document {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String note;
private LocalDateTime updateDate;
#ManyToOne(fetch = FetchType.LAZY)
private Process process;
}
I have tried the following specification-
public static Specification<Customer> orderByDocumentUploadDate() {
return (root, query, criteriaBuilder) -> {
ListJoin<Customer, Process> processJoin = root.join(Customer_.processes);
ListJoin<Process, Document> documentJoin = processJoin.join(Process_.documents);
query.orderBy(criteriaBuilder.desc(documentJoin.get(Document_.updateDate)));
query.distinct(true);
return null;
};
}
It gives following error-
ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select
list
Generated SQL-
select distinct customer0_.id as id1_0_,
customer0_.name as name2_0_
from customer customer0_
inner join
process processes1_ on customer0_.id = processes1_.customer_id
inner join
document documents2_ on processes1_.id = documents2_.process_id
order by documents2_.update_date desc
limit ?
I have also tried by grouping, like below-
public static Specification<Customer> orderByDocumentUploadDate() {
return (root, query, criteriaBuilder) -> {
ListJoin<Customer, Process> processJoin = root.join(Customer_.processes);
ListJoin<Process, Document> documentJoin = processJoin.join(Process_.documents);
query.orderBy(criteriaBuilder.desc(documentJoin.get(Document_.updateDate)));
query.groupBy(root.get(Customer_.id));
return null;
};
}
Then it gave a different error-
ERROR: column "documents2_.update_date" must appear in the GROUP BY
clause or be used in an aggregate function
Generated SQL-
select
customer0_.id as id1_0_,
customer0_.name as name2_0_
from
customer customer0_
inner join
process processes1_
on customer0_.id=processes1_.customer_id
inner join
document documents2_
on processes1_.id=documents2_.process_id
group by
customer0_.id
order by
documents2_.update_date desc limit ?
I could do it by the following sql; max() solved it in below sql-
select customer.* from customer
inner join process p on customer.id = p.customer_id
inner join document d on p.id = d.process_id
group by customer.id
order by max(d.update_date);
But I can't do the same, using the criteria API.
Do you have any suggestion?
This is a conceptual misunderstanding.
First, you have to understand how does inner join works. And this portion is okay in this case: [join process table with document table based on document.process_id = process.id]
Second, you need to sort customers based on the document's update date
Unfortunately, you used group by here. GROUP BY only returns column in which it is grouped by. In this case, it will return only customer_id.
You can use aggregate functions like count(), sum() etc. on grouped data.
When you tried to access update_date, it will throw below error:
ERROR: column "documents2_.update_date" must appear in the GROUP BY clause or be used in an aggregate function
Now, how can we get rid of this?
So first we need to do join to get customer id. After getting customer id, we should group the data by the customer id and then use max() to get max_date of each group(if necessary then minimum)
SELECT
customer_id,
max(date) AS max_date
FROM
document
JOIN process ON process.id = document.process_id
GROUP BY customer_id
It will return a temporary table, that looks something like below:
customer_id
max_date
1
2020-10-24
2
2021-03-15
3
2020-09-24
4
2020-03-15
Using the temporary table, you can now sort customer_id by date
SELECT
customer_id,
max_date
FROM
(SELECT
customer_id,
max(date) AS max_date
FROM
document
JOIN process ON process.id = document.process_id
GROUP BY customer_id) AS pd
ORDER BY max_date DESC
Hope this helps.
I have 2 tables:
First one has id, column1, column2, column3.
Second one has id, column1, column2, column4.
Respective entity for the first one:
#Entity
public class FirstEntity {
#Id
private Integer id;
#OneToMany
#JoinColumns({
#JoinColumn(name = "column1", referencedColumnName = "column1"),
#JoinColumn(name = "column2", referencedColumnName = "column2")
})
private List<SecondEntity> secondEntity;
}
So join works fine with one exception: column2 may be null in both tables. And in that case rows with same column1 values where column2 is null should be joined as well.
With SQL I succeed in achieving this by updating join condition to
table1.column1 = table2.column1 and (coalesce(table1.column2, '') = coalesce(table2.column2, '')).
What can be done with Jpa (or Hibernate in particular) in order to get secondEntity field set for null column as well?
Initially I had requirement to write code using JPA CriteraiBuilder for following SQL:
SELECT ve.col_1,
(SELECT vm.col_4
FROM table2 vm
WHERE vm.col_2 = ve.col_2
AND vm.col_3 = ve.col_3
) as col_a
FROM table1 ve;
But I learnt that, it is not possible to add subquery in select clause. So I changed my query to use left outer join like this.
SELECT ve.col_1,
vm.col_4 as col_a
FROM table1 ve,
table2 vm
WHERE
vm.col_2 (+) = ve.col_2
AND vm.col_3 (+) = ve.col_3;
Now table1 and table2 do not have direct relations using foreign keys. Corresponding JPA entities look like:
Table1.java ->
#Column(name = "COL_1")
private String col_1;
#Column(name = "COL_2")
private String col_2;
#Column(name = "COL_3")
private String col_3;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name="COL_2"),
#JoinColumn(name="COL_3")
})
private Table2 table2;
Table2.java ->
#Column(name = "COL_4")
private String col_4;
#Column(name = "COL_2")
private String col_2;
#Column(name = "COL_3")
private String col_3;
My code looks like:
final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
final CriteriaQuery<SearchTO> query = criteriaBuilder.createQuery(
SearchTO.class);
Root<Table1> root = query.from(Table1.class);
final Join<Table1, Table2> joinTable2 = root.join(Table1_.table2,
JoinType.LEFT);
Then I am trying to fetch value using:
joinTable2.get(Table2_.col_4)
Then now I am getting error as:
A Foreign key refering com.Table2 from com.Table1 has the wrong number of column
Table2 has around 6 columns with annotation #Id and I can not change change it to have only two columns with #Id annotation.
Please let me know:
If it is possible to write code using CriteriaBuilder for my approach 1 (subquery in select clause).
If thats not possible, how can I implement this left outer join as mentioned for approach 2. Please note that Table2 does not have any references of Table1.
Please note that I am using plain JPA APIs. DB is Oracle11g. JDK version is 1.7.
One solution is to create view and query against it using criteria builder.
You can see my answer in Joining tables without relation using JPA criteria
Only change you need to do is use left join in your view definition.
Hope it solves your use case.
For Approach 1: You can write subquery for criteria query
Subquery<Entity1> subquery = cq.subquery( Entity1.class );
Root fromSubQuery = subquery.from( Entity1.class );
subquery.select( cb.max( fromSubQuery.get( "startDate" ) ) );
subquery.where( cb.equal( fromSubQuery.get( "xyzId" ), fromRootOfParentQuery.get( "xyzId" ) ) );
Use it as :
Root<Entity2> entity2 = cq.from( Entity2.class );
Predicate maxDatePredicate = cb.and( cb.equal( entyty2.get( "startDate" ), subquery ) );
For Approach 2:
There is no other way than having relationship between two entities for left join. You can define private variable for relationship without getter & setter and use that variable for setting left join.
Then add the predicate to criteriaBuilder
I have table A which have relation with table B as one-to-many.
What I want to do is to do select with іщьу limit from table A and after join to selected results table B.
So I have faced with a problem how do that in the right way bu the hibernate?
Typical Criteria.selectMaxResults it is not that I need, cause limit will accept after join and instead of getting 10 different rows from table A I will get, for example, just 1 row from table 'A' with joined 10 different rows from table B.
(1) So what is the best way to do that? Select just unique rows from A in one query and in the other query do select from A with join B?
In general, on the native SQL language, I expect to execute the next query:
String sQuery = SELECT a.*,b* FROM (SELECT * FROM parentTable WHERE c.id=777 LIMIT 0,10) AS a, b.* WHERE a.id=b.a_id;
So, according to hibernate tutorial and docs I've tried to use session.createSQLQuery(sQuery). My method looks in the next way:
#Override
#Transactional
public List<VocabularyWord> getWords(int vocId, int firstResult, int pageSize) {
Session s = this.template.getSessionFactory().getCurrentSession();
SQLQuery query = s.createSQLQuery("SELECT {vW.*}, m.* FROM (SELECT * FROM vocabulary_words AS vw WHERE vw.vocabulary_id=:id LIMIT :from,:size) AS vW, meaning AS m WHERE vW.id=m.vocabulary_words_id;");
query.setInteger("id", vocId);
query.setInteger("from", firstResult);
query.setInteger("size", pageSize);
query.addEntity("vW", VocabularyWord.class);
query.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
// If unccoment the next word I get error: Column 'id1_1_1_' not found. But as I'have understood I don't need this.
// query.addEntity("m", Meaning.class);
List l = query.list();
return l;
}
And this method returns exactly what I want. BUT to return this result hibernate executes a huge amount of queries. (He try to do something like (1)?). Being exactly Hibernate executes 5 next queries:
Hibernate:
SELECT
vW.id as id1_7_0_,
vW.original_text as original2_7_0_,
vW.transcription as transcri3_7_0_,
vW.vocabulary_id as vocabula4_7_0_,
m.*
FROM
(SELECT
*
FROM
vocabulary_words AS vw
WHERE
vw.vocabulary_id=? LIMIT ?,?) AS vW,
meaning AS m
WHERE
vW.id=m.vocabulary_words_id;
Hibernate:
select
meaning0_.vocabulary_words_id as vocabula5_1_0_,
meaning0_.id as id1_1_0_,
meaning0_.id as id1_1_1_,
meaning0_.definition as definiti2_1_1_,
meaning0_.example as example3_1_1_,
meaning0_.translation as translat4_1_1_,
meaning0_.vocabulary_words_id as vocabula5_1_1_,
meaning0_.w_type as w_type6_1_1_
from
meaning meaning0_
where
meaning0_.vocabulary_words_id=?
Hibernate:
select
meaning0_.vocabulary_words_id as vocabula5_1_0_,
meaning0_.id as id1_1_0_,
meaning0_.id as id1_1_1_,
meaning0_.definition as definiti2_1_1_,
meaning0_.example as example3_1_1_,
meaning0_.translation as translat4_1_1_,
meaning0_.vocabulary_words_id as vocabula5_1_1_,
meaning0_.w_type as w_type6_1_1_
from
meaning meaning0_
where
meaning0_.vocabulary_words_id=?
Hibernate:
select
meaning0_.vocabulary_words_id as vocabula5_1_0_,
meaning0_.id as id1_1_0_,
meaning0_.id as id1_1_1_,
meaning0_.definition as definiti2_1_1_,
meaning0_.example as example3_1_1_,
meaning0_.translation as translat4_1_1_,
meaning0_.vocabulary_words_id as vocabula5_1_1_,
meaning0_.w_type as w_type6_1_1_
from
meaning meaning0_
where
meaning0_.vocabulary_words_id=?
Hibernate:
select
meaning0_.vocabulary_words_id as vocabula5_1_0_,
meaning0_.id as id1_1_0_,
meaning0_.id as id1_1_1_,
meaning0_.definition as definiti2_1_1_,
meaning0_.example as example3_1_1_,
meaning0_.translation as translat4_1_1_,
meaning0_.vocabulary_words_id as vocabula5_1_1_,
meaning0_.w_type as w_type6_1_1_
from
meaning meaning0_
where
meaning0_.vocabulary_words_id=?
My VocabularyWord entity:
public class VocabularyWord implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", updatable = false, nullable = false)
private int id;
#Column(name = "vocabulary_id", updatable = false, nullable = false)
private int vocId;
#Column(name = "original_text", nullable = false)
private String originalText;
#Column(name="transcription", nullable = true)
private String transcription;
#OneToMany (mappedBy = "vocWord", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Set<Meaning> meaning;
// + getters and setters
}
My Meaning entity:
public class Meaning implements Serializable {
#Id
#Column(name = "id", updatable = false, nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "vocabulary_words_id", nullable = false, updatable = false)
private VocabularyWord vocWord;
#JsonIgnore
#Column(name = "vocabulary_words_id", updatable = false, insertable = false)
private int vocWordId;
#Column(name = "w_type", nullable = true, unique = false)
private String wType;
#Column(name = "example", nullable = true, unique = false)
private String example;
#Column(name = "definition", nullable = true, unique = false)
private String definition;
#Column(name = "translation", nullable = true, unique = false)
private String translation;
//+ getters and setters
}
How can I fix this? Or, probably, have I choose the other way to realize that query?
I will appreciate any help, propositions, ideas and related links. Thanks in advance.
I have resolved my problem by changing my query and adding .addJoin() and .addRootEntity(). So now my method getWords looks next:
#Override
#Transactional
public List<VocabularyWord> getWords(int vocId, int firstResult, int pageSize) {
Session s = this.template.getSessionFactory().getCurrentSession();
SQLQuery query = s.createSQLQuery("SELECT {vW.*}, {m.*} FROM (SELECT * FROM vocabulary_words AS vw WHERE vw.vocabulary_id=:id LIMIT :from,:size) AS vW LEFT JOIN meaning AS m ON (m.vocabulary_words_id = vW.id);");
query.addEntity("vW", VocabularyWord.class)
.addJoin("m", "vW.meaning")
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.setInteger("id", vocId)
.setInteger("from", firstResult)
.setInteger("size", pageSize);
query.addRoot("vW", VocabularyWord.class);
List l = query.list();
return l;
}
This method executes exactly one query:
Hibernate:
SELECT
vW.id as id1_7_0_,
vW.original_text as original2_7_0_,
vW.transcription as transcri3_7_0_,
vW.vocabulary_id as vocabula4_7_0_,
m.vocabulary_words_id as vocabula6_1_0__,
m.id as id1_1_0__,
m.id as id1_1_1_,
m.definition as definiti2_1_1_,
m.example as example3_1_1_,
m.translation as translat4_1_1_,
m.vocabulary_words_id as vocabula6_1_1_,
m.w_type as w_type5_1_1_
FROM
(SELECT
*
FROM
vocabulary_words AS vw
WHERE
vw.vocabulary_id=? LIMIT ?,?) AS vW
LEFT JOIN
meaning AS m
ON (
m.vocabulary_words_id = vW.id
);
I thought I understood hibernate's fetching strategies, but it seems I was wrong.
So, I have an namedNativeQuery:
#NamedNativeQueries({
#NamedNativeQuery(
name = "getTest",
resultClass = ArticleOnDate.class,
query = "SELECT `a`.`id` AS `article_id`, `a`.`name` AS `name`, `b`.`price` AS `price` FROM article a LEFT JOIN price b ON (a.id = b.article_id) WHERE a.date <= :date"
)
})
#Entity()
#Immutable
public class ArtikelOnDate implements Serializable {
#Id
#OneToOne
#JoinColumn(name = "article_id")
private Article article;
...
}
Then I call it:
Query query = session.getNamedQuery("getTest").setDate("date", date);
List<ArticleOnDate> list = (List<ArticleOnDate>) query.list();
The query returns thousand of entities... Well, ok, but after that query hibernate queries thousand other queries:
Hibernate:
select
article0_.id as id1_0_0_,
article0_.bereich as name2_0_0_,
price1_.price as price1_14_1_
from
article artikel0_
where
artikel0_.id=?
Ok, that's logic, because the #OneToOne relation is fetched eagerly. I don't want to fetch it lazy, so I want a batch fetching strategy.
I tried to annotate the Article property but it didn't work:
#Id
#OneToOne
#JoinColumn(name = "article_id")
#BatchSize(size=100)
private Article article;
So what can I do to fetch the relation in a batch?