I have defined a method on my JPARepository to update a property's entity for a given list of ids.
#Modifying
#Transactional
#Query("UPDATE Entity SET date = ?1 WHERE id IN (?2)")
void updateDeletionDate(Date date, List<Long> ids);
This works, but I've just found out that maximum length of the list is 1000 items (due to ORA-01795), so I'm trying the best approach I've found so far: 2. Use tuples. However, I don't know how to translate the query, since something like this obviously fails: UPDATE Entity SET date = ?1 WHERE (id , 0) IN ((?2, 0))
you could use a join on a subquery which selects the ids you want to update
UPDATE Entity e
SET e.date = ?1
WHERE e.id IN (SELECT i.id FROM (VALUES (?2), (?3), ...) as i(id))
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());
}
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.
The following query return a list but I am only interested in the last element of the list.
#Query("SELECT r FROM Reservation r WHERE r.reservationSeance.id=:seanceId AND r.seanceDate=:seanceDate")
public Reservation findReservationBySeanceDateAndSeanceId(#Param("seanceId") int seanceId, #Param("seanceDate") java.time.LocalDate seanceDate);
How shall I rewrite the SQL-Query in order to implement my idea?
One possible solution is to use ORDER BY r.id DESC :
#Query("SELECT r FROM Reservation r " +
"WHERE r.reservationSeance.id=:seanceId AND r.seanceDate=:seanceDate " +
"ORDER BY r.id DESC")
public Reservation findReservationBySeanceDateAndSeanceId(
#Param("seanceId") int seanceId,
#Param("seanceDate") java.time.LocalDate seanceDate, Pageable pageable);
and because there are no way to use limit in JPQL, you can use Pageable
Pageable pageable = new PageRequest(0, 1);
Reservation reservation = r.findReservationBySeanceDateAndSeanceId(seanceId, seanceDate, pageable);
Another possible solution without Query :
public Reservation findTop1ByReservationSeanceAndSeanceDateOrderByIdDesc(
ReservationSeanceEntity reservationSenace,
java.time.LocalDate seanceDate
)
In this second solution you have to pass the ReservationSeance Object and not the id of ReservationSeance, the query can be read as :
Find top 1 (first one) by `ReservationSeance` and `SeanceDate` order by `Id` Desc order
You need to provide a couple more parameters to your query, especially an ORDER BY clause.
To get the latest seanceId, you'll want to order your results by that id, but in reverse order. Then, just tell the query to return only the first result:
SELECT r FROM Reservation r
WHERE r.reservationSeance.id=:seanceId
AND r.seanceDate=:seanceDate
ORDER BY seanceId
DESC LIMIT 1;
You can try the following, if you are using mysql as your database:
SELECT r
FROM Reservation r
WHERE r.reservationSeance.id=:seanceId
AND r.seanceDate=:seanceDate
order by r.reservationSeance.id desc limit 0,1
I have a class named Submission and another SubmissionDAO for the repository. The submission class has a number of fields such as id, author, title,...
What I want to do is search through the database and get a list of (count, author) pairs for each author value in the database.
I made a query
#Query(value = "select author, count(*) from submissions GROUP BY author order by count(author) desc", nativeQuery = true)
List<Submission> findByAuthorOccurance();
Obviously, this doesn't work because it can't put the count value in the Submission object.
My question is how would I go about getting this pair of values back to my controller?
I've tried searching but nothing comes up.
In case anyone comes here in the future for whatever reason, I figured my problem out.
When you specify a range of data points to get from the query (ie author and count), it groups the values into object arrays (Object[]) and puts those in a normal List.
So my code ended up being like this:
#Query(value = "select author, count(*) from submissions GROUP BY author order by count(author) desc", nativeQuery = true)
List<Object[]> findByAuthorOccurance();
For the query and
List<Object[]> map =submissionRepository.findByAuthorOccurance();
for(Object[] objs : map){
System.out.println((String)objs[0]+" : "+(BigInteger)objs[1]);
}
To get the data.
I have a table with a column that holds int values, I need to know how to write a JPQL query that, instead of overriding the existing value, it add the new value to the existing one and persist the sum. Here is the JPQL that sets a value.
UPDATE Transaction t SET t.amount = :amount WHERE t.id = :id
Just like in SQL:
UPDATE Transaction t SET t.amount = t.amount + 1 WHERE t.id = :id