Spring Data: how to create a repository query that only returns the rows that have greater than 5 in #ManyToMany.size() property - spring

Consider the following Entity
#Entity
public class Team {
private String title;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "teams")
#OrderBy("firstName ASC")
private Set<User> users = new HashSet<>();
}
I want to create a repository findBy query, that returns all entries of team, with a users.count() above 5.
SQL I'm expecting is:
SELECT t.title, count(*) as total_users FROM team t
LEFT JOIN team_users tu on t.id=tu.team_id
GROUP BY t.id HAVING total_users > 5;
From what I researched there is a few ways to do this, but I cant figure out any of them.
JpaSpecificationExecutor
#Query / NativeQuery
NamedNativeQuery

Take JpaSpecificationExecutor as an example
You need your repository interface to implement it
In general, your repository should also be an interface
The JpaSpecificationExecutor interface then provides several methods, and we'll use findAll as an example
Specification<DemoEntity> sp = (root, query, cb) -> {
// where begin
Predicate fp = cb.conjunction();
// xxx = 'some value'
Predicate p1 = cb.equal(root.get("xxx").as(String.class), "some Value");
// Add to the where
fp = cb.and(fp, p1);
Predicate gt = cb.gt(root.get("yyy").as(Integer.class), 5);
// Finally
return query.where(fp).groupBy(root.get("xxx")).having(gt).getRestriction();
};

Related

Order by #oneToMany field using JPA Specification

Consider the entities below -
#Entity
public class Employee {
#Id
#GeneratedValue
private long id;
private String name;
#OneToMany(mappedBy = "employee", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Phone> phones; //contains both "active" & "inactive" phones
}
#Entity
public class Phone {
#Id
#GeneratedValue
private long id;
private boolean active;
private String number;
#ManyToOne(fetch = FetchType.LAZY)
private Employee employee;
}
I need to pull all the employees and sort them depending on the count of "active" phones they have.
Please note that the employee can have active as well as inactive phones. So the query I am trying to achieve is
ORDER BY (SELECT
COUNT(phone4_.employee_id)
FROM
phone phone4_
WHERE
employee4_.id = phone4_.employee_id
AND phone4_.active = true
) DESC
I am stuck with specification here because of some reason and below is the code I have used -
List<Order> orders = new ArrayList<>();
orders.add(cb.desc(cb.size(employee.get("phones"))));
cq.orderBy(orders);
When I run the code the query that's get generated is
ORDER BY (SELECT
COUNT(phone4_.employee_id)
FROM
phone phone4_
WHERE
employee4_.id = phone4_.employee_id) DESC
I am unable to add an extra AND condition to the logic. Please suggest
As specified in the Persistence API specification:
4.6.16 Subqueries
Subqueries may be used in the WHERE or HAVING clause.
JPA doesn't support subqueries in the order by clause, nor in the select clause.
Hibernate ORM, though, supports them in the SELECT and WHERE clauses.
So you cannot write that query and being JPA compliant.
This HQL should work though and it's covered by Hibernate ORM:
SELECT e1, (SELECT count(p)
FROM Phone p
WHERE p.active = true AND p.employee = e1) as activeCount
FROM Employee e1
ORDER BY activeCount DESC
Surprisingly, writing this query with criteria doesn't work:
CriteriaBuilder builder = ormSession.getCriteriaBuilder();
CriteriaQuery<Object> criteria = builder.createQuery();
Root<Employee> root = criteria.from( Employee.class );
Subquery<Long> activePhonesQuery = criteria.subquery( Long.class );
Root<Phone> phoneRoot = activePhonesQuery.from( Phone.class );
Subquery<Long> phonesCount = activePhonesQuery
.select( builder.count( phoneRoot ) )
.where( builder.and( builder.isTrue( phoneRoot.get( "active" ) ), builder.equal( phoneRoot.get( "employee" ), root ) ) );
criteria.multiselect( root, phonesCount )
.orderBy( builder.desc( phonesCount ) );
The reason is that, Hibernate ORM tries to expand the subquery in the order by clause instead to refer to an alias. And as I mentioned before, this is not supported.
I think the HQL is the easiest option if you don't want to use native queries.

Select latest record for each id spring jpa

I have an entity like this:
#Entity
class Point{
#EmbeddedId
private PointIdentity pointIdentity;
private float latitude;
private float longitude;
#Embeddable
public static class PointIdentity implements Serializable {
private Long id;
private ZonedDateTime timestamp;
}
}
There is EmbeddedId, so in "id" column can be multiple records with the same ids.
And I need to get latest record for each id, using CriteriaQuery and JPA specifications I think, but don't know how.
In SQL, this would be something like this:
SELECT id, MAX(timestamp)
FROM geodata
GROUP BY id
Is there any way to do it?
Any help, thanks.
You can easily write a JPQL query:
TypedQuery<Object[]> query = entityManager.createQuery(
"select p.pointIdentity.id, max(p.pointIdentity.timestamp) from Point p group by p.pointIdentity.id",
Object[].class);
List<Object[]> results = query.getResultList();
which translates to:
select
point0_.id as col_0_0_,
max(point0_.timestamp) as col_1_0_
from
point point0_
group by
point0_.id
Alternatively, you can use criteria query:
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Object[]> query = criteriaBuilder.createQuery(Object[].class);
Root<Point> point = query.from(Point.class);
query.groupBy(point.get("pointIdentity").get("id"));
query.multiselect(
point.get("pointIdentity").get("id"),
criteriaBuilder.max(point.get("pointIdentity").get("timestamp"))
);
TypedQuery<Object[]> typedQuery = entityManager.createQuery(query);
List<Object[]> results = typedQuery.getResultList();
which produces identical SQL.

Pagination error in SpringBoot "Incorrect syntax near '#P0'" [duplicate]

I'm using Spring Data JPA, and when I use #Query to to define a query WITHOUT Pageable, it works:
public interface UrnMappingRepository extends JpaRepository<UrnMapping, Long> {
#Query(value = "select * from internal_uddi where urn like %?1% or contact like %?1%",
nativeQuery = true)
List<UrnMapping> fullTextSearch(String text);
}
But if I add the second param Pageable, the #Query will NOT work, and Spring will parse the method's name, then throw the exception No property full found. Is this a bug?
public interface UrnMappingRepository extends JpaRepository<UrnMapping, Long> {
#Query(value = "select * from internal_uddi where urn like %?1% or contact like %?1%",
nativeQuery = true)
Page<UrnMapping> fullTextSearch(String text, Pageable pageable);
}
You can use pagination with a native query. It is documented here: Spring Data JPA - Reference Documentation
"You can however use native queries for pagination by specifying the count query yourself:
Example 59. Declare native count queries for pagination at the query method using #Query"
public interface UserRepository extends JpaRepository<User, Long> {
#Query(value = "SELECT * FROM USERS WHERE LASTNAME = ?1",
countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1",
nativeQuery = true)
Page<User> findByLastname(String lastname, Pageable pageable);
}
A similar question was asked on the Spring forums, where it was pointed out that to apply pagination, a second subquery must be derived. Because the subquery is referring to the same fields, you need to ensure that your query uses aliases for the entities/tables it refers to. This means that where you wrote:
select * from internal_uddi where urn like
You should instead have:
select * from internal_uddi iu where iu.urn like ...
Considering that the UrnMapping class is mapped to the internal_uddi table, I would suggest this:
#Repository
public interface UrnMappingRepository extends JpaRepository<UrnMapping, Long> {
#Query(value = "select iu from UrnMapping iu where iu.urn like %:text% or iu.contact like %:text%")
Page<UrnMapping> fullTextSearch(#Param("text") String text, Pageable pageable);
}
Please note that you might have to turn off native queries with dynamic requests.
With #Query , we can use pagination as well where you need to pass object of Pageable class at end of JPA method
For example:
Pageable pageableRequest = new PageRequest(page, size, Sort.Direction.DESC, rollNo);
Where,
page = index of page (index start from zero)
size = No. of records
Sort.Direction = Sorting as per rollNo
rollNo = Field in User class
UserRepository repo
repo.findByFirstname("John", pageableRequest);
public interface UserRepository extends JpaRepository<User, Long> {
#Query(value = "SELECT * FROM USER WHERE FIRSTNAME = :firstname)
Page<User> findByLastname(#Param("firstname") String firstname, Pageable pageable);
}
Please reference :Spring Data JPA #Query, if you are using Spring Data JPA version 2.0.4 and later. Sample like below:
#Query(value = "SELECT u FROM User u ORDER BY id")
Page<User> findAllUsersWithPagination(Pageable pageable);
Declare native count queries for pagination at the query method by using #Query
public interface UserRepository extends JpaRepository<User, Long> {
#Query(value = "SELECT * FROM USERS WHERE LASTNAME = ?1",
countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1",
nativeQuery = true)
Page<User> findByLastname(String lastname, Pageable pageable);
}
Hope this helps
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods
Rewrite your query to:
select iu from internal_uddi iu where iu.urn....
description: http://forum.spring.io/forum/spring-projects/data/126415-is-it-possible-to-use-query-and-pageable?p=611398#post611398
I found it works different among different jpa versions, for debug, you'd better add this configurations to show generated sql, it will save your time a lot !
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
for spring boot 2.1.6.RELEASE, it works good!
Sort sort = new Sort(Sort.Direction.DESC, "column_name");
int pageNumber = 3, pageSize = 5;
Pageable pageable = PageRequest.of(pageNumber - 1, pageSize, sort);
#Query(value = "select * from integrity_score_view " +
"where (?1 is null or data_hour >= ?1 ) " +
"and (?2 is null or data_hour <= ?2 ) " +
"and (?3 is null or ?3 = '' or park_no = ?3 ) " +
"group by park_name, data_hour ",
countQuery = "select count(*) from integrity_score_view " +
"where (?1 is null or data_hour >= ?1 ) " +
"and (?2 is null or data_hour <= ?2 ) " +
"and (?3 is null or ?3 = '' or park_no = ?3 ) " +
"group by park_name, data_hour",
nativeQuery = true
)
Page<IntegrityScoreView> queryParkView(Date from, Date to, String parkNo, Pageable pageable);
you DO NOT write order by and limit, it generates the right sql
I had the same issue - without Pageable method works fine.
When added as method parameter - doesn't work.
After playing with DB console and native query support came up to decision that method works like it should. However, only for upper case letters.
Logic of my application was that all names of entity starts from upper case letters.
Playing a little bit with it. And discover that IgnoreCase at method name do the "magic" and here is working solution:
public interface EmployeeRepository
extends PagingAndSortingRepository<Employee, Integer> {
Page<Employee> findAllByNameIgnoreCaseStartsWith(String name, Pageable pageable);
}
Where entity looks like:
#Data
#Entity
#Table(name = "tblEmployees")
public class Employee {
#Id
#Column(name = "empID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#NotEmpty
#Size(min = 2, max = 20)
#Column(name = "empName", length = 25)
private String name;
#Column(name = "empActive")
private Boolean active;
#ManyToOne
#JoinColumn(name = "emp_dpID")
private Department department;
}
When using nativeQuery that is having (nativeQuery = true), you may do the pagination yourself in the query by adding (LIMIT :sizeValue OFFSET :page)
Note:
Your page value passed to this method should be offset * size
Example
#Query(value = "SELECT * FROM person " +
"LIMIT ?1 OFFSET ?2", nativeQuery = true)
Optional<List<TDriverJob>> findPersons(int size, int page);
I tried all above solution and non worked , finally I removed the Sorting from Pagination and it worked
the following tutorial helped me
-> https://www.baeldung.com/spring-data-jpa-query
At this point 4.3. Spring Data JPA Versions Prior to 2.0.4
VERY IMPORTANT to add \ n-- #pageable \ n
Without this I was wrong
Also the pagination setting must be without ordering
PageRequest paginaConf = new PageRequest ((param1 - 1)
, param2);
Finally to convert the Page <Object []>
Page <Object []> list = myQueryofRepo ();
List <XXXModel> lstReturn = myConversor (list.getContent ());
Page <XXXModel> ret = new PageImpl <XXXModel> (lstReturn, pageConf, param2);
This bugged me for a while but I managed with a very smooth solution.
The challenge is JPA did not automatically detect the count query so I resolved to use the countName which according JPA docs Returns the name of the javax.persistence.NamedQuery to be used to execute count queries when pagination is used. Will default to the named query name configured suffixed by .count.
So I created a named query
#NamedNativeQuery(
name = "[queryName].count",
query = [your count query],
resultSetMapping = "[query resultSetMapping name]"
)
}
As indicated, the count query should be suffixed with .count
Count query returns Long so add the resultSetMapping as
#SqlResultSetMapping(
name="[query resultSetMapping name]",
columns={#ColumnResult(name="count", type = Long.class)})
Then in your repository, use the count query as indicated below
#Query(countName ="[queryName].count" , nativeQuery = true)
Page<Object> [mainQuery](...params)
Hope this helps!

Spring boot JPA: search entities by field that represent list of embedded objects

I have an entity Order with List of options inside, like this:
#Entity
#Table(name = "orders")
public class OrderEntity extends AuditableEntity {
private Long passengerId;
private OrderType type;
private OrderStatus status;
#ElementCollection()
#CollectionTable(name = "options", joinColumns = #JoinColumn(name = "order_id"))
private List<OrderOptionEntity> options = new ArrayList<>(0);
...
And I want to find all orders, matches specified list of options. I'm using JpaRepository<OrderEntity, Long> for CRUD operations. Unfortunately, when I add method findByOptions, like this:
public interface OrderRepository extends JpaRepository<OrderEntity, Long> {
List<OrderEntity> findAllByOptions(List<OrderOptionEntity> options);
}
In tests it throws
SqlNode's text did not reference expected number of columns;
So now I just do findAll() and filter all orders manually. Is there any more elegant way for obtain entities, matching by all elements of list inside it?
UPDATE:
When I run
#Query("SELECT ord FROM OrderEntity ord WHERE :options MEMBER OF ord.options")
List<OrderEntity> findAllByOptions(#Param(value = "options") List<OrderOptionEntity> options);
It works fine, but only if ord.options and options in query has size 1, if more - it throws
o.h.engine.jdbc.spi.SqlExceptionHelper : Invalid argument in JDBC
call: parameter index out of range: 3
Generated SQL is
/* SELECT
ord
FROM
OrderEntity ord
WHERE
:options MEMBER OF ord.options */ select
orderentit0_.id as id1_3_,
orderentit0_.version as version2_3_,
orderentit0_.create_time as create_t3_3_,
orderentit0_.update_time as update_t4_3_,
orderentit0_.comment as comment5_3_,
orderentit0_.distance_to_order as distance6_3_,
orderentit0_.passenger_id as passenge7_3_,
orderentit0_.price as price8_3_,
orderentit0_.route_distance as route_di9_3_,
orderentit0_.status as status10_3_,
orderentit0_.type as type11_3_
from
orders orderentit0_
where
(
? , ?
) in (
select
options1_.key,
options1_.value
from
options options1_
where
orderentit0_.id=options1_.order_id
)
All what I want - get all Orders, containing some subset of options.
You probably forgot the In keyword in your query method.
Try this
public interface OrderRepository extends JpaRepository<OrderEntity, Long> {
List<OrderEntity> findAllByOptionsIn(List<OrderOptionEntity> options);
}
Take a look at the docs.

Select Count using Criteria builder in a OneToMany Relationship

I'm building a Spring web app and i'm new to JPA and I need to get the number of users in a specific group in my database.
Here is the sample code :
public long countAllUsersByGroup(int groupId) {
CriteriaBuilder qb = em.getCriteriaBuilder();
CriteriaQuery<Long> cq = qb.createQuery(Long.class);
cq.select(qb.count(cq.from(User.class)));
//cq.where(qb.equal(cq.from(User.class).get("userGroup").get("id"),groupId));
return em.createQuery(cq).getSingleResult();
}
This code is working, it allows me to retrieve the number of users I have in the database which is pretty trivial.
This is my user Model :
public class User {
private String userFirstName;
private String userLastName;
/* some stuff */
#ManyToOne
private Group userGroup;
}
And my group model has an int attribute annotated with #Id and named id. How Can I get the number of user by group id in this case ?
P.S : I've tried and commented my try in the code above, unfortunately it was a failure ...
You can try the code below
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Long> q = cb.createQuery(Long.class);
ParameterExpression<Integer> p = cb.parameter(Integer.class);
q.select(q.count(q.from(User.class))).where(cb.gt(c.get("userGroup"), someId));

Resources