Spring Data: how works Pageable together with #Query? (Interface Projection) - jpa

I am working with Spring Data for the 2.0.6.RELEASE version
I am confused about the following about pagination and projection
The domain class is named Persona working with lombok and annotated with JPA (#Entity etc ..)
I have the following about projection:
public interface PersonaProjection {
String getId();
String getNombre();
String getApellido();
Date getFecha();
}
About the repository I have the following:
interface PersonaDataJpaCrudRepository extends PagingAndSortingRepository<Persona, String> {
Page<PersonaProjection> findAllProjectedBy(Pageable pageable);
}
Until here all work fine.
The #Controller has this body:
#GetMapping(path="/complete", produces=MediaType.TEXT_HTML_VALUE)
public String findAllComplete(Pageable pageable, Model model){
logger.info("findAllComplete ...");
Page<PersonaProjection> personasPage = personaService.findAll(pageable);
model.addAttribute("personasPage", personasPage);
return "persona/findAllComplete";
}
The view is based with Thymeleaf and shows the data how is expected
Practically from three days ago I was working with the following:
#Query("SELECT p.id, p.nombre, p.apellido, p.fecha FROM Persona p")
Page<PersonaProjection> findAllProjectedBy(Pageable pageable);
Observe it has a #Query just to visual purposes. It to help to understand quickly what fields are retrieved.
And worked fine, but just until today without any variation practically the view for that fields remains in blank, none exception thrown, through the console through some special variations of the #Controller method I can confirm:
Page request [number: 0, size 10, sort: UNSORTED]
PageNumber: 0, PageSize: 10, Offset: 0
Page 1 of 11 containing com.sun.proxy.$Proxy84 instances
Number: 0, NumberOfElements: 10
Size: 10, TotalElements: 107, TotalPages: 11
PageNumber: 0, PageSize: 10, Offset: 0
The output shown above in the previous three days appears with and without the Query addition.
After to do a research I found the following example for this class:
CustomerRepository.java
Where it has the following method:
/**
* Projection interfaces can be used with manually declared queries, too. Make sure you alias the projects matching
* the projection fields.
*
* #return
*/
#Query("select c.firstname as firstname, c.lastname as lastname from Customer c")
Collection<CustomerProjection> findsByProjectedColumns();
Observe this note:
Make sure you alias the projects matching the projection fields.
Thus if I use:
#Query("SELECT p.id as id, p.nombre as nombre, p.apellido as apellido, p.fecha as fecha FROM Persona p")
Page<PersonaProjection> findAllProjectedBy(Pageable pageable);
Works but observe is redundant, it about that the alias is the same than the field name.
Thus following questions:
Why the first #Query version works some days ago and now no?. Here my big confusion. (I am assuming that com.sun.proxy.$Proxy84 instances is involved)
Why is mandatory use this alias as within the #Query? It is not documented in the 4.3.11. Projections section, even more there is no sample showing an interface projection working together with a #Query

Related

JPA Specification Select returns all columns instead of specific

I am using JPA Specification , need to select specific columns only.
This is the code:
Specification<Item> spec = (root, query, builder) -> {
query.select(root.get("Id"));
Predicate predicate = builder.equal(root.get("Id"), "12345");
return predicate;
};
In log I see that all columns from Item Entity are selected from database.
Is it a bug?
 usage:
interface:
public interface Repo extends PagingAndSortingRepository<Item,String>, JpaSpecificationExecutor<Item> {
}
call:
repo.findAll(spec );
JpaSpecificationExecutor is specifically defined to return the entity type. I suspect it is ignoring the .select(root.get("Id")).
Normally you would use Specifications if you have an extremely dynamic set of conditions you are querying by. If you have just a few parameters you need to search by, I would use a derived query, or a named query.
public interface Repo extends PagingAndSortingRepository<Item,String>, JpaSpecificationExecutor<Item> {
#Query("Select i.id from Item i where name=:name")
Long getIdforName(String name);
}

Is it possible to return custom Java objects combining multiple aggregates in Spring Data JDBC?

I have multiple aggregate classes, such as Request, Scribe, Candidate, and Exam.
Sample schema:
Request (id, scribe_id, candidate_id, exam_id, status)
Scribe (id, name)
Candidate (id, name)
Exam (id, name, schedule)
As you can see, Request table has references to Scribe, Candidate, and Exam tables.
For one of the requirements, I need to return all requests based on a condition by including all the corresponding details of scribe, candidate, and exam.
For this, the query in my repository class will be similar to the following:
SELECT r.id, r.status, c.name, s.name,
e.schedule, e.name
FROM request r
JOIN candidate c ON r.candidate=c.id
JOIN scribe s ON r.scribe=s.id
JOIN exam e ON r.exam=e.id
WHERE <some-condition>
Now, is there a way to map the result of this query directly to a custom Java object and return the same in Spring Data JDBC?
I believe another alternative is to use the Spring JDBC template.
Curious, any out-of-the-box support from Spring Data JDBC?
Thanks.
I am able to return custom Java object by setting rowMapperClass value of org.springframework.data.jdbc.repository.query.Query annotation. For this need to define RowMapper for custom Java object.
Changes look similar to the following:
public class RequestResourceRowMapper implements RowMapper<RequestResource> {
#Override
public RequestResource mapRow(ResultSet resultSet, int rowNumber) throws SQLException { ... }
}
In repository class, need to set rowMapper value.
#Query(value = """
SELECT r.id, r.status, c.name, s.name,
e.schedule, e.name
FROM request r
JOIN candidate c ON r.candidate=c.id
JOIN scribe s ON r.scribe=s.id
JOIN exam e ON r.exam=e.id
WHERE <some-condition>
""",
rowMapperClass = RequestResourceRowMapper.class)
List<RequestResource> searchRequestResources(...);
This could have even been possible without using a custom row mapper as well, but in that case, you will have to assign different names to the columns across the tables. You could have defined a simple class and defined all the fields in there and for mapping the java fields with the corresponding columns in the table, you could have used the #Column attributes example:
public class RequestData {
#Column("id")
private Integer requestId;
#Column("scribe_id")
private String scribeId;
#Column("candidate_id")
private Integer candidateId;
#Column("scribe_name")
private String scribeName;
#Column("candidate_name")
private String candidateName;
#Column("exam_name")
private String examName;
#Column("exam_schedule")
private String examSchedule;
}
However, for such case, you need to have different column names across the schema's which might not be possible in your case as you have same column names in multiple schemas.

SortBy Spring Data MongoDb

I have passed my query from postgres to mongodb, everything is correct. But in the ordering I do not see how to integrate it within #Query as in the example sql
// sql-postgres ( repository )
#Query("select c from ClienteElser c where c.securityDomainId IN (2,10) and c.deleted is null order by c.razonSocial")
//mongodb ( repository )
#Query("{security_domain_id: { $in: [2,10] },'deleted':null}")
public List<Cliente> findAllByOrderByRazonSocialAsc(Sort sort);
//service
Sort sort = new Sort(Sort.Direction.ASC, "razon_social");
List<Cliente> result = clienteRepository.findAllByOrderByRazonSocialAsc(sort);
At the moment I have fixed it like that, but I would prefer it to be inside #Query
And order giving priority to capital letters, I do not know how to avoid that. Example : "ACS" is before "Abalos"
Can someone help me to integrate the sort within #Query with mongodb and that does not differ between uppercase and lowercase
Thank you
This has been adressed in DATAMONGO-1979 for the Lovelace release (aka Spring Data MongoDB 2.1.0).
It will allow to set a default sort for repository query methods via the #Query annotation.
#Query(sort = "{ age : -1 }")
List<Person> findByFirstname(String firstname);
Using an explicit Sort parameter along with the annotated one allows to alter the defaults set via the annotation. The method argument Sort parameters will be added to / or override the annotated defaults depending on the fields used.
#Query(sort = "{ age : -1 }")
List<Person> findByFirstname(String firstname, Sort sort);

Spring data pagination with entity graph

I need to use pagination in conjuntion with a custom query using an Entity Graph. My repository looks like this:
#Repository
public interface MaintenanceRepository extends JpaRepository<Maintenance, Long>, QueryDslPredicateExecutor<Maintenance> {
#EntityGraph(value = "Maintenance.Graph")
#Query("select m from Maintenance m where m.detail.type.company = ?1 ")
Page<Maintenance> findWithCompany(Company company, Pageable pageable);
}
For comparison, I am getting the Maintenances using this method and using findAll method inherited from QueryDslPredicateExecutor:
...
Pageable pageable = new PageRequest(pageNumber, pageSize, Sort.Direction.DESC, "creationTime");
Page<Maintenance> page = repo.findWithCompany(company, pageable);
Page<Maintenance> page2 = repo.findAll(QMaintenance.maintenance.detail.type.company.eq(company), pageable);
LOGGER.debug("Old: {} - {}, New: {} - {}", page.getTotalElements(), page.getContent().size(), page2.getTotalElements(), page2.getContent().size());
...
In the data base there is 3 register of Maintenances and when I call this method using a page with 1, 2 and 50 page size, I get this log.
[28/03/2017 14:14:36] [DEBUG] Old: 3 - 1, New: 3 - 1 //total - content
[28/03/2017 14:15:09] [DEBUG] Old: 3 - 2, New: 3 - 2
[28/03/2017 14:15:27] [DEBUG] Old: 3 - 3, New: 3 - 3
According to the logs the pagination is working fine but the querys are very different when I use my repository method and the inherited findAll method.
//Query for my repository method
Hibernate: select ... where maintenanc0_.detalle=detail1_.id and detail1_.tipo=type2_.id and type2_.compania=? order by maintenanc0_.fecha desc
//Query for inherited `findAll` method
Hibernate: select ... where maintenanc0_.detalle=detail1_.id and detail1_.tipo=type2_.id and type2_.compania=? order by maintenanc0_.fecha desc limit ?
Two logs have been cut for show relevant information, and in the second query the entity graph hints has not been used because I can not provide it with a Predicate object, but I understand and that is not my problem.
Having this results, I understand that with my repository method (first query) I am not getting a real pagination because spring is filling the list with the correct content size, and the query doesn't have limit keyword.
With the second query, I'm getting a real pagination because the data base is doing the work.
According to Spring Data reference I should be able to do pagination with custom query method.
My concern is the performance, because I don't want to have much data loaded in memory and I need get real pagination done at data base level.
Using QueryDslPredicateExecutor is fine but I can't set a hint for entity graph.

order-by-using-search-string - JPA

hey i'm looking for way to search for text pattern (in jpa) and to sort the result, first all the result starting with this string and then all other result.
i have found Mysql order by using search string to get the answer for mysql
most of the answers use union(which does not exist in jpa) and force to query the db or open a view for that. (ordering from code is not that good solution since we use paging to get part of the result as the result size can be really big)
One solution i like from the link above is :
select * from employee where name like '%ani%' order by locate('ani', name) asc, name asc
source
This seems to me very clear but i'm not sure how to convert it to jpa. seems like Order object is not able to get locate output
any ideas will be welcome
Thanks!
Alon
EDIT:
thanks for reply. i'm tring to achive the same with jpa critiera
Iterator<Order> sortingIter = page.getSort().iterator();
ArrayList<javax.persistence.criteria.Order> order = new ArrayList<javax.persistence.criteria.Order>();
String fieldName;
while (sortingIter.hasNext()) {
Order sort = sortingIter.next();
fieldName = sort.getProperty();
order.add(sort.getDirection() == Sort.Direction.ASC ? cb
.asc(keyword.get(fieldName)) : cb.desc(keyword
.get(fieldName)));
}
while the above works well. i cannot add the following line to the code. seems like Order object doesnt like them
Expression<String> fieldValue = keyword.get(fieldName);
order.add(cb.locate(fieldValue,key));
EDIT 2:
tried
order.add(new javax.persistence.criteria.Order() {
#Override
public javax.persistence.criteria.Order reverse() {
// TODO Auto-generated method stub
return null;
}
#Override
public boolean isAscending() {
// TODO Auto-generated method stub
return true;
}
#Override
public Expression<?> getExpression() {
// TODO Auto-generated method stub
return cb.locate(fieldValue,key);
}
});
jpa doesn't complain but the query does not get the right order
EDIT 3:
Found my mistake!
The key value i was passing above already contained "%" and both sides... so locate did not work properly.
now i'm getting some weird behavior on some special chars:if - for example - i have the word Ghurabā the query like %ba% will find it. but, it seems that locate(Ghurabā,ba) will return 0 - meaning as pattern was not found in string any idea how to overcome this issue?
seems like this is not only jpa but also mysql behavior.
SELECT *
FROM `keywords`
WHERE name LIKE '%ba%'
ORDER BY LOCATE( 'ba', name ) , name
LIMIT 0 , 30
will return the next result
Ghurabā'
Khuṭabā'
qabā\
Ribāṭ
ba'urchi (cook)
Baghdad
...
note that it does work for "regular english characters" but there is a mismatch between the like and the locate function
Using Collcation: utf8_general_ci (got the same result with utf_unicode_ci)
This does not makes any complain.
String jpql = "select e from Employee e where e.name like '%ani%' order by locate('ani', e.name) asc, e.name asc";
TypedQuery<Employee> query2 = em.createQuery(jpql ,Employee.class);
And this is the translation that hibernate does.
Hibernate: select employee0_.id as id1_2_, employee0_.address_id as
address5_2_, employee0_.DEPT_ID as DEPT6_2_, employee0_.manager_id as
manager7_2_, employee0_.name as name2_2_, employee0_.salary as
salary3_2_, employee0_.startDate as startDat4_2_ from Employee
employee0_ where employee0_.name like '%ani%' order by locate('ani',
employee0_.name) asc, employee0_.name asc
Using some data as the link you mention.
Employee 10: name: anil, salary: 59000,
Employee 1: name: anirudha, salary: 55000,
Employee 5: name: rani,
Employee 7: name: Stephanie, salary: 54000,
{anil,anirudha,rani, ...}
Same problem using CriteriQuery solution
Ok, you own me some points for this =)
Hibernate: select employee0_.id as id1_2_, employee0_.address_id as address5_2_, employee0_.DEPT_ID as DEPT6_2_, employee0_.manager_id as manager7_2_, employee0_.name as name2_2_, employee0_.salary as salary3_2_, employee0_.startDate as startDat4_2_ from Employee employee0_ where employee0_.name like ? order by locate(?, employee0_.name) asc, employee0_.name asc
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
cq.where(cb.like(root.<String>get("name"), "%ani%"));
cq.orderBy(cb.asc(cb.locate(root.<String>get("name"), "ani")), cb.asc(root.get("name")));
TypedQuery<Employee> query2 = em.createQuery(cq);
printList(query2.getResultList());
Try the above it should work.
Employee 10: name: anil, salary: 59000,
Employee 1: name: anirudha, salary: 55000,
Employee 5: name: rani,
Employee 7: name: Stephanie, salary: 54000,
Check this out if you think ?(question mark) is not correct in the query.
http://webdev.apl.jhu.edu/~jcs/ejava-javaee/coursedocs/605-784-site/docs/content/html/jpa-query-criteria-function.html#jpa-query-criteria-function-string-locate