From one query findAllByName(personName) I am getting back List Objects. I want to sum their age. So I make something like this:
int sum =
listPerson
.stream()
.mapToInt(PersonEntity::getAge)
.sum();
I know that I can use for example EntityManager and Query:
Query query = em.createQuery(
"SELECT SUM(p.age) FROM PErson p");
But it will sum age from all entities.
How can I write a query instead of this, to sum Person age from list, based on argument?
You can write a function inside your repository which will return a Long or int (depends on your database size) type and is annotated with #Query
#Query(value = "SELECT SUM(age) FROM Person", nativeQuery = true)
Long getAgeSum();
EDIT: After your edit, the current method could be modified like this for
1) List arguments
#Query(value = "SELECT SUM(p.age) FROM Person p WHERE p.id IN :idList", nativeQuery = true)
Long getAgeSum(#Param("idList") List<String> idList);
2) Simple arguments
#Query(value = "SELECT SUM(p.age) FROM Person p WHERE p.jobName=:jobNameParam", nativeQuery = true)
Long getAgeSum(#Param("jobNameParam") String jobNameParam);
Related
At the moment I have the following code which iterates a list of parameter entities and updates each of their names in the database:
public class test {
#Autowired
private ParameterJpaRepository parameterJpaRepository;
public updateParameters(List<Parameter> parameters) {
for (Parameter parameter : parameters) {
parameterJpaRepository.setNameById(parameter.getId(), parameter.getName());
}
}
}
public interface ParameterJpaRepository extends JpaRepository<Parameter, Long> {
#Modifying
#Query("UPDATE Parameter p SET p.name = :name WHERE p.id = :id")
void setNameById(#Param("id") long id, #Param("name") String name);
}
Obviously, this results in N queries:
Hibernate: update parameter set name=? where id=?
Hibernate: update parameter set name=? where id=?
Hibernate: update parameter set name=? where id=?
Hibernate: update parameter set name=? where id=?
I would like to combine then into a single query equivalent to this attempt:
public interface ParameterJpaRepository extends JpaRepository<Parameter, Long> {
#Modifying
#Query("UPDATE Parameter p SET p.name = (:names) WHERE p.id = (:ids)")
void setNameById(#Param("ids") List<Long> ids, #Param("names") List<String> names);
}
Which should yield something like:
Hibernate: UPDATE parameter
SET name = (case when id = ? then ?
when id = ? then ?
when id = ? then ?
when id = ? then ?
end)
WHERE id in (?, ?, ?, ?);
Is this possible?
You probably want something like this
#Modifying
#Query(value = "UPDATE Parameter p SET p.name = (CASE " +
"WHEN p.id IN (:ids) THEN (CASE " +
"WHEN p.id = :ids[0] THEN :names[0] " +
"WHEN p.id = :ids[1] THEN :names[1] " +
"WHEN p.id = :ids[2] THEN :names[2] " +
// ...
"ELSE p.name " +
"END) " +
"ELSE p.name " +
"END) " +
"WHERE p.id IN (:ids)", nativeQuery = true)
void setNameById(#Param("ids") List<Long> ids, #Param("names") List<String> names);
This is a bad approach. Don't try to do that.
It's very bad for large lists, since the query will become very long and very difficult to maintain.
It does not work if the ids and names lists are not in the same order.
If you need to update a big number of rows, or if the order of the ids and names lists is not fixed, you might want to consider using a different approach, such as executing a separate update statement for each row or using a temporary table .
(Un)fortunately, spring-data-jpa functionality is not so flexible as you would like to see, however it does allow to create Custom Implementations for Spring Data Repositories and thus you may write any update query you want, some examples:
https://thorben-janssen.com/composite-repositories-spring-data-jpa/
https://vladmihalcea.com/custom-spring-data-repository/
https://www.baeldung.com/spring-data-composable-repositories
by the way, you need to keep in mind that in general that is not a good idea to update hibernate entities via direct update, the reasoning is following:
it is not cache friendly - if you are using second level cache, hibernate needs to completely cleanup those cache, cause it has no chance to get know what entities have been updated
if you are using auditing solution like envers, direct update bypasses that solution
so, sometimes it is much better to enable batch updates and write something like:
#Transactional
default void setNameById(List<Long> ids, List<String> names) {
Map<Long, Parameter> data = StreamSupport.stream(findAllById(ids).spliterator(), false)
.collect(Collectors.toMap(
Customer::getId,
Function.identity()
));
for (int i = 0, n = ids.size(); i < n; i++) {
Parameter parameter = data.get(ids.get(i));
if (parameter != null) {
parameter.setName(names.get(i));
}
}
saveAll(data.values());
}
I have a very strange problem.
I have some repository method that accepts Pageable as parameter. Here it is:
#Query(value = "SELECT product_name FROM product WHERE number = 1",
countQuery = "SELECT COUNT (id) FROM product",
nativeQuery = true)
public List<String> getAllProducts(Pageable pageable);
When I use this method without sort inside of pageable everything is okay. Hibernate uses next query in this situation: SELECT product_name FROM product WHERE number = 1 limit ?.
But when I use sort inside of Pageable I get such a strange query: SELECT product_name FROM product WHERE number = 1, product_name asc limit ?.
Do you have any suggestions why spring puts comma instead of ORDER BY?
The Spring Data Jpa Method like this:
#Query("select pb.id,pp.max_borrow_amt,pp.min_borrow_amt
from product_loan_basic pb left join product_loan_price pp on pb.code=pp.product_code
where pb.code IN(?1) and pb.status='publish' order by ?2 ",
nativeQuery = true)
List<Object[]> findByCodesIn(List<String> codes,String orderby);
then order by is " max_borrow_amt desc ", but this is invalid.
the List is disordered.
Dynamic sorting in Spring Data JPA
If you used a JPA query you could use Sort as an argument of your query method to define the sorting order:
#Query("select m from Model m")
List<Model> getSortedList(Sort sort);
and then, for example:
List<Model> models = getSortedList(Sort.by(Sort.Direction.DESC, "name"));
But Spring Data JPA can't use Sort with native queries:
Spring Data JPA does not currently support dynamic sorting for native queries, because it would have to manipulate the actual query declared, which it cannot do reliably for native SQL.
However you can use Pageable and its implementation PageRequest instead:
#Query(value = "select m.name as name from models m", nativeQuery = true)
List<ModelProjection> getSortedList(Pageable p);
and then:
List<ModelProjection> modelNames = getSortedList(PageRequest.of(0, 1000, Sort.Direction.DESC, "name"));
P.S. Instead of array of Objects as returned parameters, it's better to use projections, for example:
public interface ModelProjection {
String getName();
}
Note that in this case the good practice is to use aliases in queries (ie m.name as name). They must match with correspondent getters in the projection.
Working demo and test.
Thanks everyone!
My problem has been solved.
If you want to use Spring data jpa nativeQuery & Sort, you should do like this:
#Query(
value ="select pb.id,pp.max_borrow_amt from product_loan_basic pb left join product_loan_price pp on pb.code=pp.product_code ORDER BY ?#{#pageable} ",
countQuery = "select count(*) from product_loan_basic",
nativeQuery = true
)
Page<Object[]> findAllProductsAndOrderByAndSort(Pageable pageable);
?#{#pageable} is required and countQuery is required.
Pageable pageable = new PageRequest(0,1000,Sort.Direction.DESC,"id");
then the result is sorted.
See Spring Data and Native Query with pagination.
I have found lots of answers for how to send a list parameter in to a query and check if a value is in that list but I'm trying to do the opposite - pass in the value and check if it's contained in a list in the object.
I have the following code to try to retrieve a Person using their username.
Person person = uniqueResult(namedQuery(Person.FIND_BY_USERNAME)
.setParameter("username", username).setMaxResults(1));
The username is contained in a list in the Person object.
#Column(name = "usernames")
#Convert(converter = PersonUsernameConvertor.class)
private List<String> usernames;
Is it possible to get the Person with the username parameter in their list with a NamedQuery or do I need something else? Below is what I have so far but it's not working, I'm guessing because the parameter value is on the left of the equation.
#NamedQuery(name = Person.FIND_BY_USERNAME,
query = "SELECT p from Person p WHERE :username IN p.usernames)
Example1:
#NamedQuery(name = Person.FIND_BY_USERNAME,
query = "SELECT p from Person p WHERE p.usernames in (:username)")
If usernames list contains only John and passing the parameter username with john, the above query works and returns the result.
Example2:
#NamedQuery(name = Person.FIND_BY_USERNAME,
query = "SELECT p from Person p WHERE p.usernames like CONCAT('%',:username,'%')")
If usernames list contains John,Joe and passing the parameter username with joe,the above query will check the list whether joe exists in the list or not.
I'm writing a JPQL query that joins across three tables. In my resultlist I would like to get all three entities per matching row (hope that makes sense).
Any ideas?
Hibernate 3.x is my JPA provider.
IIRC, you can do a SELECT o1, o2, o3 FROM EntityA o1, EntityB o2, EntityC o3 WHERE ...., and the result will be a List<Object[3]>, where the array contents will contain the o1,o2,o3 values.
This is a Spring Data sample, however its works the same way in JPA
//HQL query
#Query("SELECT c,l,p,u FROM Course c, Lesson l, Progress p, User u "
+ "WHERE c.id=l.courseId AND l.id = p.lessonId AND p.userId = u.id AND u.id=:userId AND c.id=:courseId")
public List<Object[]> getLessonsWithProgress(#Param("userId") Integer userId, #Param("courseId")Integer courseId);
Then, I call this method and print the results:
List<Object[]> lst = courseRepository.getLessonsWithProgress(userId, courseId);
for (Object o[] : lst) {
Course c = (Course) o[0];
Lesson l = (Lesson) o[1];
Progress p = (Progress) o[2];
User u = (User) o[3];
//all the classes: Course, Lesson, Progress and User have the toString() overridden with the database ID;
System.out.printf("\nUser: %s \n Lesson: %s \n Progress: %s \n Course: %s",u,l,p,c);
}
The output #Test is here:
User: com.cassio.dao.model.User[ id=1965 ]
Lesson: com.cassio.dao.model.Lesson[ id=109 ]
Progress: com.cassio.dao.model.Progress[ id=10652 ]
Course: com.cassio.dao.model.Course[ id=30 ]
Cheers
Since You are asking JPA: Query that returns multiple entities, EclipseLink too comes under it. And I reached on this question googling for EclipseLink. So here is my solution. Hope it works for you.
TypedQuery<Object[]> query = entityManager.createQuery("select p from Post p where p.publisher.pubId= :ID order by p.createdAt desc",
Object[].class);
query.setParameter("ID", publisherID);
Then you can loop through the result objects and cast them accordingly.
for (Object result : query.getResultList()) {
myList.add((Post) result);
}
You can also try this,
Query query = entityManager.createQuery("select p from Post p where p.publisher.pubId= :ID order by p.createdAt desc");
Reference:http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/JPQL
In case of many to one or one to many relationship how to get multiple records of one of the entities? lets say A is one entity and B is another entity but they have one to many relationship and when you get result you expect B has one record and A has more than 1 record? My query is as below but I don't know how shall I get multiple records of 2nd entity?
#Query("SELECT wl, gr FROM WatchList as wl, GeozoneReference gr " +
"WHERE wl.watchlistId = gr.objWatchList.watchlistId " +
"AND wl.watchlistId =:watchlistId")
List<Object[]> findWatchlistByWatchlistId(#Param("watchlistId") Long watchlistId);