I am using Spring Data JpaRepository and I find it extremely easy to use. I actually need all those features - paging, sorting, filtering. Unfortunately there is one little nasty thing that seems to force me to fall back to use of plain JPA.
I need to order by a size of associated collection. For instance I have:
#Entity
public class A{
#Id
private long id;
#OneToMany
private List<B> bes;
//boilerplate
}
and I have to sort by bes.size()
Is there a way to somehow customize the ordering still taking the advantage of pagination, filtering and other Spring Data great features?
I've solved the puzzle using hints and inspirations from:
Limiting resultset using #Query anotations by Koitoer
How to order by count() in JPA by MicSim
Exhaustive experiments on my own
The first and most important thing I've not been aware of about spring-data is that even using #Query custom methods one can still create paging queries by simply passing the Pageable object as parameter. This is something that could have been explicitely stated by spring-data documentation as it is definitely not obvious though very powerful feature.
Great, now the second problem - how do I actually sort the results by size of associated collection in JPA? I've managed to come to a following JPQL:
select new package.AwithBCount(count(b.id) as bCount,c) from A a join a.bes b group by a
where AwithBCount is a class that the query results are actually mapped to:
public class AwithBCount{
private Long bCount;
private A a;
public AwithBCount(Long bCount, A a){
this.bCount = bCount;
this.a = a;
}
//getters
}
Excited that I can now simply define my repository like the one below
public interface ARepository extends JpaRepository<A, Long> {
#Query(
value = "select new package.AwithBCount(count(b.id) as bCount,c) from A a join a.bes b group by a",
countQuery = "select count(a) from A a"
)
Page<AwithBCount> findAllWithBCount(Pageable pageable);
}
I hurried to try my solution out. Perfect - the page is returned but when I tried to sort by bCount I got disappointed. It turned out that since this is a ARepository (not AwithBCount repository) spring-data will try to look for a bCount property in A instead of AwithBCount. So finally I ended up with three custom methods:
public interface ARepository extends JpaRepository<A, Long> {
#Query(
value = "select new package.AwithBCount(count(b.id) as bCount,c) from A a join a.bes b group by a",
countQuery = "select count(a) from A a"
)
Page<AwithBCount> findAllWithBCount(Pageable pageable);
#Query(
value = "select new package.AwithBCount(count(b.id) as bCount,c) from A a join a.bes b group by a order by bCount asc",
countQuery = "select count(a) from A a"
)
Page<AwithBCount> findAllWithBCountOrderByCountAsc(Pageable pageable);
#Query(
value = "select new package.AwithBCount(count(b.id) as bCount,c) from A a join a.bes b group by a order by bCount desc",
countQuery = "select count(a) from A a"
)
Page<AwithBCount> findAllWithBCountOrderByCountDesc(Pageable pageable);
}
...and some additional conditional logic on service level (which could be probably encapsulated with an abstract repository implementation). So, although not extremely elegant, that made the trick - this way (having more complex entities) I can sort by other properties, do the filtering and pagination.
One option, which is much simpler than the original solution and which also has additional benefits, is to create a database view of aggregate data and link your Entity to this by means of a #SecondaryTable or #OneToOne.
For example:
create view a_summary_view as
select
a_id as id,
count(*) as b_count,
sum(value) as b_total,
max(some_date) as last_b_date
from b
Using #SecondaryTable
#Entity
#Table
#SecondaryTable(name = "a_summary_view",
pkJoinColumns = {#PrimaryKeyJoinColumn(name = "id", referencedColumnName= "id")})
public class A{
#Column(table = "a_summary_view")
private Integer bCount;
#Column(table = "a_summary_view")
private BigDecimal bTotal;
#Column(table = "a_summary_view")
private Date lastBDate;
}
You can now then sort, filer, query etc purely with reference to entity A.
As an additional advantage you have within your domain model data that may be expensive to compute in-memory e.g. the total value of all orders for a customer without having to load all orders or revert to a separate query.
Thank you #Alan Hay, this solution worked fine for me. I just had to set the foreignKey attribute of the #SecondaryTable annotation and everything worked fine (otherwise Spring Boot tried to add a foreignkey constraint to the id, which raise an error for a sql View).
Result:
#SecondaryTable(name = "product_view",
pkJoinColumns = {#PrimaryKeyJoinColumn(name = "id", referencedColumnName = "id")},
foreignKey = #javax.persistence.ForeignKey(ConstraintMode.NO_CONSTRAINT))
I don't know much about Spring Data but for JPQL, to sort the objects by size of associated collection, you can use the query
Select a from A a order by a.bes.size desc
You can use the name of an attribute found in the select clause as a sort property:
#Query(value = "select a, count(b) as besCount from A a join a.bes b group by a", countQuery = "select count(a) from A a")
Page<Tuple> findAllWithBesCount(Pageable pageable);
You can now sort on property besCount :
findAllWithBesCount(PageRequest.of(1, 10, Sort.Direction.ASC, "besCount"));
I used nativeQuery to arrange sorting by number of records from another table, pagable works.
#Query(value = "SELECT * FROM posts where posts.is_active = 1 and posts.moderation_status = 'ACCEPTED' " +
"group by posts.id order by (SELECT count(post_id) FROM post_comments where post_id = posts.id) desc",
countQuery = "SELECT count(*) FROM posts",
nativeQuery = true)
Page <Post> findPostsWithPagination(Pageable pageable);
For SpringBoot v2.6.6, accepted answer isn't working if you need to use pageable with child's side field especially when using #ManyToOne.
For the accepted answer:
You can return new object with static query method, which have to include order by count(b.id)
And also order by bCount isn't working.
Please use #AlanHay solution, it is working, but you can't use primitive field and change foreign key constraint. For instance, change long with Long. Because:
When saving a new entity Hibernate does think a record has to be written to the secondary table with a value of zero. (if you use primitive type)
Otherwise you will get an exception:
Caused by: org.postgresql.util.PSQLException: ERROR: cannot insert into view "....view"
Here is the example:
#Entity
#Table(name = "...")
#SecondaryTable(name = "a_summary_view,
pkJoinColumns = {#PrimaryKeyJoinColumn(name = "id",
referencedColumnName= "id")},
foreignKey = #javax.persistence.ForeignKey(name = "none"))
public class UserEntity {
#Id
private String id;
#NotEmpty
private String password;
#Column(table = "a_summary_view",
name = "b_count")
private Integer bCount;
}
Related
I get following error when doing a rather complicated query: for SELECT DISTINCT, ORDER BY expressions must appear in select list
In the query I need to find all distinct Requests that have an ExploitationSite that contains a search term in their dutch or french name. The result has to be ordered by the Activity's dutch name and limited to the first 10 for pagination.
To do this query I use the Page <T> findAll(Specification<T> spec, Pageable pageable) method of JpaSpecificationExecutor.
This will result in a SELECT DISTINCT query which has to be ORDERed BY a property that is not in SELECT. (details below)
I tried to fetch the activities eagerly in the hope it would place those differently in the SELECT. I did my best trying to get the DISTINCT in a subquery and then have the ORDER BY + LIMIT around that, but I did not succeed in that.
Has someone an idea how I can get this query to work?
The (simplified) Request entity
#Entity
#Table(name = "request_requests")
#History("Request")
public class Request extends EqualByStateObject {
#GeneratedValue
#Id
private int id;
#Embedded
private RequestNumber requestNumber;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "fk_request")
private List<ExploitationSite> exploitationSites = new ArrayList<>();
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(unique = true, name = "fk_activity")
private Activity activity;
...
}
The Specification (I have to use distinct here because since a Request contains a List of ExploitationSites it was possible I got the same request multiple times back if multiple ExploitationSites contained the search term)
public class ExploitationSiteSpecification extends EqualByStateObject implements Specification<Request> {
private final String exploitationSiteName;
protected ExploitationSiteSpecification(String exploitationSiteName) {
this.exploitationSiteName = exploitationSiteName;
}
#Override
public Predicate toPredicate(Root<Request> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
query.distinct(true);
ListJoin<Object, Object> exploitationSites = root.joinList("exploitationSites");
return criteriaBuilder.or(
criteriaBuilder.like(
criteriaBuilder.lower(exploitationSites.get("name").get("dutchName")), "%" + exploitationSiteName.toLowerCase() + "%"),
criteriaBuilder.like(
criteriaBuilder.lower(exploitationSites.get("name").get("frenchName")), "%" + exploitationSiteName.toLowerCase() + "%")
);
}
}
The Pageable
public Pageable getPageable() {
Sort sort = Sort.by(Sort.Order.asc("activity.name.dutchName"));
PageRequest.of(0, 10, sort);
}
This results in a generated query like this one
select distinct request0_.id as id1_23_,
request0_.fk_activity as fk_acti15_23_,
request0_.request_number as request12_23_
from request_requests request0_
inner join request_exploitation_sites exploitati1_ on request0_.id=exploitati1_.fk_request
left outer join request_activity activity2_ on request0_.fk_activity=activity2_.id
where lower(exploitati1_.dutch_name) like $1
or lower(exploitati1_.french_name) like $2
order by activity2_.dutch_name asc limit $3
which then gives the for SELECT DISTINCT, ORDER BY expressions must appear in select list error
Assuming you put the distinct because the join with exploitationSites would return multiple rows, the following two options would work without using distinct.
right after the join you could do an additional fetch
ListJoin<Object, Object> exploitationSites = root.joinList("exploitationSites");
root.fetch("exploitationSites")
this would result in hibernate to create an additional join of ExploitationSites as well as selecting additional columns
select request0_.id as id1_23_,
request0_.fk_activity as fk_acti15_23_,
request0_.request_number as request12_23_,
exploitati3_.id as exploitati3_id,
exploitati3_.name as exploitati3_name,
...
from request_requests request0_
inner join request_exploitation_sites exploitati1_ on request0_.id=exploitati1_.fk_request
left outer join request_activity activity2_ on request0_.fk_activity=activity2_.id
inner join request_exploitation_sites exploitati3_ on request0_.id=exploitati3_.fk_request
where lower(exploitati1_.dutch_name) like $1
or lower(exploitati1_.french_name) like $2
order by activity2_.dutch_name asc limit $3
use fetch in the first place and cast it to Join
Join<Object, Object> exploitationSites = (Join<Object, Object>) root.fetch("exploitationSites");
By casting the Fetch to a Join you can still use where clauses.
Note that this will also select additional columns, but won't do an additional join in the resulting query.
In both cases the fetch will result in a join fetch which hibernate internally will remove duplicates from the parent entity (see https://stackoverflow.com/a/51177569)
I am having difficulty writing a HQL query to select ONLY the caseid, title, and caseStatus fields from my Cases entity. The cases returned have to be distinct based on caseid. I do not want the name and userid fields to be included. I also do not want to use Lazy fetching for caseid, title, and caseStatus fields. Note that the caseStatus field is a one-to-many List. Below are the entities. The getters/setters are omitted to save space.
#Entity
#Table(name = "Cases")
public class Cases {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "caseid", nullable = false)
private Integer caseid;
private Integer userid;
private String name;
private String title;
#OrderBy("caseStatusId DESC")
#OneToMany(mappedBy = "cases", fetch = FetchType.EAGER)
private List<CaseStatus> caseStatus;
}
#Entity
#Table(name = "CaseStatus")
public class CaseStatus {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "caseStatusId", nullable = false)
private Integer caseStatusId;
private String info;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "caseid")
private Cases cases;
}
My goal is to retrieve a distinct List<Cases> or List<Object[]> of the Cases entity containing only caseid, title, and a List<CaseStatus>. The List<CaseStatus> will contain CaseStatus objects with all of its fields populated.
public List<Object[]> getCases(String title) {
TypedQuery<Object[]> q = em.createQuery("select distinct c.caseid, c.title, cs "
+ "FROM Cases c join c.caseStatus cs "
+ "where c.title like :title", Object[].class);
q.setParameter("title", "%" + title + "%");
List<Object[]> results = q.getResultList();
return results;
}
The above method is close, but not correct because rather than returning a List<CaseStatus> in one of the indexes, it is only returning a single CaseStatus entity.
For example, if my DB contains a single Case with a List<CaseStatus> having a size of n for example, the results will be similar to the example below:
Example of results I'm getting now. Not correct:
List<Object[]> index 0:
Contains an Object[] where:
Object[0] = {some caseid}
Object[1] = {some title}
Object[2] = {1st CaseStatus}
List<Object[]> index 1:
Contains an Object[] where:
Object[0] = {same caseid as the one found in index 0 above}
Object[1] = {same title as the one found in index 0 above}
Object[2] = {2nd CaseStatus}
...
List<Object[]> index n-1:
Contains an Object[] where:
Object[0] = {same caseid as all the previous}
Object[1] = {same title as all the previous}
Object[2] = {nth CaseStatus}
Example of results I hope to achieve:
List<Object[]> index 0:
Contains an Object[] where:
Object[0] = {unique caseid}
Object[1] = {some title}
Object[2] = List<CaseStatus> with size of n
Updated the question. Instead of name, title, and List<CaseStatus>, the fields I want to retrieve are caseid, title, and List<CaseStatus>. caseid is the primary key of Cases.
I found various threads Select Collections with HQL - hibernate forum and Select collections with HQL - stackoverflow. It's pretty much the problem I ran into. Looks like no one found a solution in these threads.
Hibernates a bit confused about the query; in HQL do your join like this (apologies, I've not been able to test before posting due to wonky computer, but you should get the idea)
select distinct c from Cases c left join fetch c.caseStatus cs where....
the "fetch" makes it eager. Note that this will return an array of type Cases. You where clauses look about right.
In fact HQL is fully object-oriented and uses your classes structure in the Query, so by writing c.caseStatus HQL expects that your Cases class has a caseStatus property, which is wrong because it's a collection.
If you take a look at Hibernate HQL documentation you can see that:
Compared with SQL, however, HQL is fully object-oriented and understands notions like inheritance, polymorphism and association.
I think what you need to do here is to change your query so it matches your classes structures:
Query q = em.createQuery("select distinct c.name, c.title, cs.caseStatus FROM Cases c left join c.caseStatus where "
+ "c.name like :name and "
+ "c.title like :title");
Correct syntax should be
TypedQuery<Object[]> q = em.createQuery("select c.name, c.title, cs FROM Cases c "
+ "join c.caseStatus cs where "
+ "c.name = :name and "
+ "c.title = :title", Object[].class);
Return type will be List<Object[]>, where in first index of Object[] is c.name, second is c.title and third is associated caseStatus entity. It is possible to query for multiple instances (rows).
We need JOIN because relationship between CaseStatus and Case is mapped via collection.
SELECT cs
FROM Case c JOIN c.cases cs;
Why don't you just use
Query q = em.createQuery("select distinct c from Cases c where "
+ "c.name like :name and "
+ "c.title like :title");
Just try this. This may be a naive approach but should be able to solve the problem. You may be getting more fields than you required but the return type would be list of Cases.
I have a database table Communications with type, value and a foreign key as index that maps back to a Person table declared as follows:
#Table(name = 'communication', schema = 'schema')
#org.hibernate.annotations.Table(appliesTo = 'communication', indexes = {
#Index(name = "idx_communication_person_id", columnNames = { "person_id" })
}
)
And the Person object maps to this as:
#OneToMany(fetch = LAZY, cascade = ALL, orphanRemoval = true)
#JoinColumn(name = "person_id")
#OrderColumn
#Index(name = "idx_communication_person_id")
private final List<Communication> communications
Now I want to create a HQL query with Hibernate, that selects based on this index colum, like:
WHERE person.id in ( SELECT c.person_id FROM Communication c WHERE c.type = 3 AND c.value = 'john.doe#server.com' )
That doesn't work, because HQL doesn't know c.person_id at this point, because index columns are in general unknown to HQL.
How do I properly address the index in HQL, or if that is not possible: how do I write the statement to archive the same as the native-like query above?
EDIT: For performance reasons there must not be a JOIN in any form.
I think you need something like this:
SELECT p.* FROM person p
JOIN p.communication c
WHERE c.type = 3 AND c.value = 'john.doe#server.com'
That doesn't work, because HQL doesn't know c.person_id at this point, because index columns are in general unknown to HQL.
This doesn't make much sense to me.
If you want to have an HQL statement that returns a list of identifiers for Person based on some criteria, you can easily do it much like how your SQL statement is written.
SELECT p.id
FROM Communication c JOIN FETCH c.person p
WHERE c.type = :communicationType
AND c.value = :emailAddress
If you actually want persons, just write the query to select c.person rather than p.id in order to hydrate all Persons. In the following, the query allows you to specify a person identifier on the predicate if needed.
SELECT c.person
FROM Communication c JOIN FETCH c.person p
WHERE c.person.id = :personId
AND c.type = :communicationType
AND c.value = :emailAddress
UPDATE
If you don't want to use any joins, then simply expose the personId as a numeric value on your Communication entity without any association mappings.
public class Communication {
#Column(name = "personId", nullable = false, insertable = false, updatable = false)
private Long personId;
}
You should then be able to issue a query such as:
SELECT c.personId
FROM Communcation c
WHERE c.type = :communicationType
AND c.value = :emailAddress
I'm using #NamedEntityGraph annotation to load a graph from database.
#NamedEntityGraph(
name = "Firma.uredjivanje",
attributeNodes = {
#NamedAttributeNode(value="prevodi", subgraph = "prevodi")
},
subgraphs = {
#NamedSubgraph(
name = "prevodi",
attributeNodes = {
#NamedAttributeNode(value = "jezik", subgraph = "jezik")
}
)
}
)
In the Spring Data JPA repository, I'm using annotation:
#EntityGraph(value="Firma.uredjivanje", type = EntityGraph.EntityGraphType.LOAD)
List<Firma> getByAktivna(boolean aktivna);
Everything works as expected, expect that all relations are joined, and I get duplicate Firma entities (because of JOIN). Instead of a List with entity id's {1,2,3}, I get {1,1,1,2,2,3}.
What is the best way to get distinct entities (if this is not a bug ofcourse).
Found the answer... Since NamedEntityGraph does JOIN in database, it selects all entities without DISTINCT. So the solution is to use Distinct in method name...
#EntityGraph(value="Firma.uredjivanje", type = EntityGraph.EntityGraphType.LOAD)
List<Firma> getDistinctByAktivna(boolean aktivna);
After adding dictinct I have error logs during server starup (maybe because there is no such thing as findDistinctAll() .
For me help a hint in https://jira.spring.io/browse/DATAJPA-680.
I added #Query annotation to the method, so my method looks like below
#EntityGraph(value = "User.detail", type = EntityGraph.EntityGraphType.LOAD)
#Query(value = "SELECT DISTINCT u FROM User u")
List<User> findAll();
We have a DB table that is mapped into a hibernate entity. So far everything goes well...
However what we want is to only map enentitys that satisty a specific criteria, like ' distinct(fieldA,fieldB) '...
Is it possible to map with hibernate and hibernate annotations? How can we do it? With #Filter?
I would recommend that you use #Where annotation. This annotation can be used on the element Entity or target entity of a collection. You provide a clause attribute written in sql that will be applied to any select that hibernate performs on that entity. It is very easy to use and very readable.
Here is an example.
#Entity
//I am only interested in Donuts that have NOT been eaten
#Where(clause = "EATEN_YN = 'N'")
public class Donut {
#Column(name = "FILLING")
private String filling;
#Column(name = "GLAZED")
private boolean glazed = true;
#Column(name = "EATEN_YN")
private boolean eaten = false;
...
}
You could create a view and then map that view to entity:
create view my_data as
select ... from ...
#Entity(table="my_data")
public class MyData { ... }
One option is to map the table normally, then you could fetch your always entities through a query or a filter.
You could also make a native SQL query and map the entity on the results:
Query q = sess.createSQLQuery("SELECT DISTINCT fieldA, fieldB FROM some_table")
.addEntity(MyEntity.class);
List<MyEntity> cats = q.list();
It might be also possible to add DISTINCT to this type of HQL query:
select new Family(mother, mate, offspr)
from DomesticCat as mother
join mother.mate as mate
left join mother.kittens as offspr
Methods 1, 3 and 4 will make a read-only mapping.
Could you be more specific about the criteria you are using? The view approach is more generic since you can't do everything with a hibernate query or filter.
perhaps you could create a new Pojo that encapsulates the fields and the condition that they should statisy . And then then make that class a 'custom user defined type', such that Hibernate will have to use the mapping class that you provide, for mapping that 'type'..
In addition to the options mentioned by Juha, you can also create an object directly out of a SQL query using the NamedNativeQuery and SqlResultSetMapping annotations.
#Entity
#SqlResultSetMapping(name = "compositekey", entities =
#EntityResult(entityClass = MiniBar.class,
fields = { #FieldResult(name = "miniBar", column = "BAR_ID"), })
)
#NamedNativeQuery(name = "compositekey",
query = "select BAR_ID from BAR", resultSetMapping = "compositekey")
#Table(name = "BAR")
public class Bar {
Flavor the SQL query to your taste