How to use LIMIT 25 in #Query in Spring Data JPA - spring-data-jpa

I am trying to fetch 25 records from employee table but I am getting error (java.lang.IllegalArgumentException). And I want to sort it by insertedTimeStamp below is code
#Query("SELECT e FROM Employee e where e.name = :name AND e.address = :address order by e.insertedTimeStamp LIMIT 25")
public List<Employee> findByNameAndAddress(#Param("name") String name, #Param("address") String address);

You should add Pageable parameter to your query and change return type to Page. It makes the query a bit shorter
#Query("from Employee e where e.name = :name AND e.address = :address")
Page<Employee> findByNameAndAddress(#Param("name") String name, #Param("address") String address, Pageable pageable);
Usage
int pageIndex = 0;
int pageSize = 25;
String propertyToOrderBy = "insertedTimeStamp";
Pageable pageable = PageRequest.of(pageIndex, pageSize, Sort.Direction.ASC, propertyToOrderBy);
Page<Employee> page = repository.findByNameAndAddress(name, address, pageable);
List<Employee> list = page.getContent();

Related

JPA Native Query to map One to Many

I have a complex Oracle query which for simplicity's sake looks like this;
SQL> SELECT d.id AS dept_id,
2 d.name AS dept_name,
3 e.id AS emp_id,
4 e.name AS emp_name,
5 e.dept_id AS emp_dept_id
6 FROM drs2_dept d, drs2_emp e
7 WHERE d.id = e.dept_id (+)
8 /
DEPT_ID DEPT_NAME EMP_ID EMP_NAME EMP_DEPT_ID
---------- ------------------- ---------- -------------- -----------
1 SALES 101 JOHN 1
1 SALES 102 JANE 1
2 ADMIN
My Department class is;
#SqlResultSetMapping(
name = "Department.employeeMapping",
classes = {
#ConstructorResult(
targetClass = Department.class,
columns = {
#ColumnResult(name = "DEPT_ID", type = Integer.class),
#ColumnResult(name = "DEPT_NAME")
}
),
#ConstructorResult(
targetClass = Employee.class,
columns = {
#ColumnResult(name = "EMP_ID", type = Integer.class),
#ColumnResult(name = "EMP_NAME"),
#ColumnResult(name = "EMP_DEPT_ID", type = Integer.class)
}
)
}
)
#NamedNativeQuery(
name = "Department.findAllEmployees",
query = "SELECT d.id AS dept_id, " +
" d.name AS dept_name, " +
" e.id AS emp_id, " +
" e.name AS emp_name " +
" e.dept_id AS emp_dept_id, " +
"FROM drs2_dept d, drs2_emp e " +
"WHERE d.id = e.dept_id (+)",
resultSetMapping = "Department.employeeMapping"
)
#Entity
public class Department {
#Id // JPA will not start without it.
Integer id;
String name;
#OneToMany // JPA will not start without it.
List<Employee> employees = new ArrayList<>();
public Department(Integer id, String name) {
this.id = id;
this.name = name;
}
public Department() {}
// getters and setters
}
My Employee class is;
#Entity
public class Employee {
#Id Integer id;
Integer departmentId;
String name;
public Employee(Integer id, String name, Integer departmentId) {
this.id = id;
this.name = name;
this.departmentId = departmentId;
}
public Employee() {}
// getters and setters
}
Because I am using #ConstructorResult I am able to get the data, but it still in a flat structure, that is to say a List<Object[]> with three entries, each containing [Department, Employee]. So I have to do the following to move the Employee records within their respective Department;
#Component
public class DepartmentDAO {
#PersistenceContext EntityManager entityManager;
public Collection<Department> getAllDepartments() {
Query query = entityManager.createNamedQuery("Department.findAllEmployees");
Map<Integer, Department> map = new HashMap<>();
List<Object[]> list = query.getResultList();
for (Object[] tuple: list) {
Department d = (Department) tuple[0];
if (! map.containsKey(d.getId())) {
map.put(d.getId(), d);
}
d = map.get(d.getId());
Employee e = (Employee) tuple[1];
if (e.getId() != null) {
d.getEmployees().add(e);
}
}
return map.values();
}
}
Whenever I add any additional properties to the #OneToMany I seem to get spurious SQL generated in the Hibernate logs which is incorrect (i.e. non-existent column or table names), but as I stated at the start of this question, I want the native SQL only - I don't want Hibernate to figure out what I am trying to do.
Is there any way to get JPA/Hibernate to put the Employee objects into the Department's list for me?
(
As a sub-note, I have seen this question asked here, but either never answered or answered back in 2011, by which time JPA and Hibernate may have progressed.
I should also add that elsewhere in my project I already have Department and Employee fully mapped for CrudRepository use with #Table and #Column, however their #OneToMany definitions do not depict what I am doing in the above query, hence their absence in my example code.
)
This query does not have any clause that forces it to be implemented with a native.
In fact it is considered a bad practice.
Try this:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Departament> cq = cb.createQuery(Departament.class);
Root<Departament> rootDepartament = cq.from(Departament.class);
Join<Departament,Employee> joinEmployee = rootDepartament.join(Departament_.employees,JoinType.Left);
cq.select(rootDepartament);
List<Departament> result = entityManager.createQuery(cq).getResultList();

JPA Create Criteria with GROUP COUNT and HAVING on nested objects

Given this SQL query
SELECT
ug.lookup_key,
count(ug.id) as count
FROM user u
INNER JOIN user_group ug on ug.id = u.id
WHERE
u.age >= 11 AND
u.age <= 20 AND
ug.lookup_key in('12345')
GROUP BY ug.lookup_key
HAVING count(ug.id) < 7
I have written this
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Object[]> query = criteriaBuilder.createQuery(Object[].class);
Root<UserGroup> d = query.from(UserGroup.class);
Join<UserGroup, User> join = d.join("users");
Predicate pred1 = criteriaBuilder.between(join.get("age"), ageFrom, ageTo);
Expression<String> exp = d.get("lookupKey");
Predicate pred2 = exp.in(lookupKeys);
query.where(pred1, pred2);
query.multiselect(d.get("lookupKey"), criteriaBuilder.count(d.get("id"))).groupBy(d.get("lookupKey"));
List<Object[]> results = entityManager.createQuery(query).getResultList();
for(Object[] object : results){
System.out.println(object[0] + " " + object[1]);
}
The SQL returns {"12345",4} whereas the code returns {"12345", 37}. The SQL is the correct result. There are 37 users in the database for groups with that lookup key, so I understand where the numbers are coming from but I do not understand how to do the JOIN, GROUP BY, HAVING with the CreateCriteria query so that I get the results. I don't want to use JPQL.
The entities...
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
private int age;
private double salary;
#ManyToOne(optional=false,cascade=CascadeType.ALL, targetEntity=UserGroup.class)
#JsonBackReference
private UserGroup group;
// Getters and Setters //
}
#Entity
public class UserGroup {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
private String lookupKey;
#OneToMany(mappedBy="group",targetEntity=User.class, fetch=FetchType.EAGER)
#JsonManagedReference
private Collection users;
// Getters and Setters //
}
And also, here is the method in which it is implemented
public void summarizeGroupsByLookupKey(long ageFrom, long ageTo, List<String> lookupKeys, long numUsers){
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Object[]> query = criteriaBuilder.createQuery(Object[].class);
Root<UserGroup> d = query.from(UserGroup.class);
Join<UserGroup, User> join = d.join("users");
Predicate pred1 = criteriaBuilder.between(join.get("age"), ageFrom, ageTo);
Expression<String> exp = d.get("lookupKey");
Predicate pred2 = exp.in(lookupKeys);
query.where(pred1, pred2);
query.multiselect(d.get("lookupKey"), criteriaBuilder.count(d.get("id")));
query.groupBy(d.get("lookupKey"));
query.having(criteriaBuilder.<Long>lessThan(criteriaBuilder.count(d.get("id")), numUsers));
List<Object[]> results = entityManager.createQuery(query).getResultList();
for(Object[] object : results){
System.out.println(object[0] + " " + object[1]);
}
}
By way of info...using Spring Boot 1.5.1 and all the default JPA, Hibernate, etc. from there.
Can a JPA expert offer some help? Thanks!
Change the multiselect part to use countDistinct(..)
query.multiselect(d.get("lookupKey")
,criteriaBuilder.countDistinct(d.get("id")));
and also having(..)
query.having(criteriaBuilder.<Long>lessThan(
criteriaBuilder.countDistinct(d.get("id")), numUsers)
);
Original query returned row per matching user in which rows userGroup.id was then multiplied.

JPA Criteria Query: how to automatically create LEFT JOIN instead of WHERE conditions

I have two entity:
public class public class Person implements Serializable {
private static final long serialVersionUID = -8729624892493146858L;
#Column(name="name")
private String name;
...
#JoinColumn(name = "idcity",referencedColumnName = "id",nullable = true)
#ManyToOne(targetEntity = City.class, fetch = FetchType.EAGER)
private City city
...
}
and the related entity (extract):
public class City{
Long id;
String name;
...
}
Now i'm creating a criteria query in a standard way, querying the Person class:
CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
CriteriaQuery query = cb.createQuery(selectClass);
Root<T> root = query.from(this.entityClass);
Selection selezioni[] = new Selection[selections.length];
for(int i=0; i< selections.length; i++){
selezioni[i] = CriteriaHelper.getField(selections[i], cb, root);
}
query.select(cb.construct(selectClass, selezioni));
where entityClass is Person and selection and selectClass are used to compile the SELECT clause. In the select i've person.city.name field.
This system create a query with where clause:
select person.name, ..., city.name from person, city WHERE person.idcity = city.id...
but city is not required, so the records without city are not fetched.
Without changing all my automatic system, does exists a simpler way to force the use on LEFT JOIN for the relationship than adding a system to create root.join("field",LEFT)?
Note: the method CriteriaHelper.getField() return a Path starting from the root object

NamedQuery selectively define attributes

I have namedQuery in Entity class defined as
#NamedQuery(name = "Emp.findAll",
query = " select new test.entity.Emp(o.empNo, o.salary, o.project) from Emp o ")
Constructor
public Emp(String empNo, String salary, Project project) {
this.empNo = empNo;
this.salary= salary;
this.project = project;
}
and generated SQL is
SELECT t0.emp_no, t0.salary, t1.project_id, t1.project_name, t1.project_desc
FROM EMP t0, PROJECTS t1 WHERE (t1.project_id (+) = t0.project_id)
In namedQuery how do I selectively declare projectId and projectName instead of all attributes from Project class? I wouldn't like to display all attributes of Project class in namedQuery.
How can I achieve this?
Update 1
public Emp(String empNo, String salary, Long projectId, String projectName) {
Project pr = new Project();
this.empNo = empNo;
this.salary= salary;
pr.setProjectId = projectId;
pr.setProjectName = projectName;
}
Try this (and update the constructor accordingly)
#NamedQuery(name = "Emp.findAll",
query = " select new test.entity.Emp(o.empNo, o.salary, p.projectId, p.projectName) from Emp o inner join o.project p ")
Constructor will be something like this
public Emp(String empNo, String salary, Long projectId, String projectName) {
this.empNo = empNo;
this.salary= salary;
Project pr = new Project();
pr.setProjectId(projectId);
pr.setProjectName(projectName);
this.project = pr;
}

How to get an interesect query using CritieraQuery?

Given
#Entity
public class Document {
#Id
#Column(name = "DOCUMENT_ID")
private Long id;
#ElementCollection
#CollectionTable(
name="TAG",
joinColumns=#JoinColumn(name="DOCUMENT_ID")
)
#Column(name="TAG")
private Set<String> tags;
}
find all documents tagged with a specific collection of tags. Essentially, an EclipseLink equivalent of:
SELECT d FROM Document d WHERE :tag1 MEMBER OF d.tags
INTERSECT
SELECT d FROM Document d WHERE :tag2 MEMBER OF d.tags
...
SELECT d FROM Document d WHERE :tagn MEMBER OF d.tags
but using a JPA CritieraQuery.
Use an aggregate query with a having clause to select rows that matched all the required tags:
CriteriaBuilder cb = entityManager().getCriteriaBuilder();
CriteriaQuery<Long> q = cb.createQuery(Long.class);
Root<Document> from = q.from(Document.class);
List<Predicate> predicates = new ArrayList<Predicate>();
Expression<Long> id = from.get("id");
Expression<Collection<String>> documentTags = from.get("tags");
for (String tag : searchedTags) {
Expression<String> param = cb.literal(tag);
Predicate predicate = cb.isMember(param, documentTags);
predicates.add(predicate);
}
q.multiselect(id).where(
cb.or(predicates.toArray(new Predicate[0])));
q.distinct(true);
q.groupBy(id);
q.having(cb.equal(cb.count(id), searchedTags.size()));
TypedQuery<Long> query = entityManager().createQuery(q);