I would like to perform a database bulk update using Spring's HibernateTemplate (Hibernate 5.1).
HibernateTemplate offers the following interface: public int bulkUpdate(String,Object...).
My query is UPDATE entity item SET item.attribute.id = ? WHERE item.id in (?.....?).
I had a lot of troubles and want to ask what is the proper way to use HibernateTemplate
The above query results in the deprecation warning [DEPRECATION] Encountered positional parameter near line 1, column 172 in HQ
Replacing the above query with JPA-style parameters (UPDATE entity item SET item.attribute.id = ?1 WHERE item.id in (?2,?3.....?N)) results in NullPointerException thrown by Hibernate when building parameter metadata
As seen on one of the most authoritative Hibernate sources, named parameters result in a misleading exception
Question is: how do I properly formulate a bulk update query using Spring's HibernateTemplate? As correctly reported by Mykong, HibernateTemplate automagically sets query parameters 0-based, but eventually the author got the program working with non-positional parameters without mentioning (or having at all) any warning.
I think that the recommended way to do that now a days is with Spring Data JPA. There is a getting started tutorial here.
So if you have an entity, you can add an interface that extends any of the reposiotry interfaces supported in SpringDataJpa and add a modifying query.
public interface CustomerRepository extends CrudRepository<Customer, Long> {
#Transactional
#Modifying
#Query("update Customer c set c.firstName = ?1 where c.id = ?2")
int updateNameById(String nameToUpdate, long id);
#Transactional
#Modifying
#Query("update Customer c set c.firstName = ?1 where c.id in (?2)")
int updateNameByIds(String nameToUpdate, List<Long> ids);
}
Then Spring will implement that method and you can use the it as:
customerRepo.updateNameByIds("newName", Arrays.asList(cust.getId()));
This will generate the following sql:
update customer set first_name=? where id in (?)
Here is the project I used to test with
Related
I am using mssql and spring data JPA, I want to insert new records to a table by using custom #Query annotation.
public interface CustomerRepository extends JpaRepository<Customers, String>{
#Modifying
#Query("insert into Customers values (?1 , ?2)")
public void saveCutomer(int custId, Customer cust);
}
Its giving error,
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: expecting OPEN, found 'values' near line 1, column 23 [insert into Customers values (?1 , ?2)]
I tried below also, same error.
#Modifying
#Query("insert into Customers select ?1 , ?2")
public void saveCutomer(int custId, Customer cust);
You don't do that using JPQL. You have batch DELETE and UPDATE but that's it.
Your options are:
1) Mark the query as native if you really make explicit insert (not recommended unless you intend to use some database-specific syntax not supported by JPQL.
2) Use standard save(Entity) method on your repository which is of course preferable.
let's assume we have a Spring Data repository interface with a custom method...
#Modifying
#Transactional
#Query("UPDATE MyEntity SET deletedAt = CURRENT_TIMESTAMP WHERE id = ?1")
void markAsSoftDeleted(long id);
This method simply sets the deletedAt field of the entity, ok. Is there any way to allow this method to return an updated version of the MyEntity?
Obviously...
#Modifying
#Transactional
#Query("UPDATE MyEntity SET deletedAt = CURRENT_TIMESTAMP WHERE id = ?1")
MyEntity markAsSoftDeleted(long id);
...does not work, since...
java.lang.IllegalArgumentException: Modifying queries can only use void or int/Integer as return type!
Does anyon know another way to easily allow that, except of course the obvious "add a service layer between repository and caller for such things"...
Set clearAutomatically attribute on #Modifying annotation.That will clear all the non-flushed values from EntityManager.
#Modifying(clearAutomatically=true)
#Transactional
#Query("UPDATE MyEntity SET deletedAt = CURRENT_TIMESTAMP WHERE id = ?1")
void markAsSoftDeleted(long id);
To flush your changes before committing the update latest spring-data-jpa has another attribute on #ModifyingAttribute. But I think its still in 2.1.M1 release.
#Modifying(clearAutomatically=true, flushAutomatically = true)
Please check corresponding jira bug request: https://jira.spring.io/browse/DATAJPA-806
Another approach can be you can implement custom repostiory Implementation and return your updated entity after done with the query execution.
Reference : Spring data jpa custom repository implemenation
There are two ways to do that:
The JPA idiomatic way to do this is to load the entities first, then changing them using Java code.
Doing this in a transaction will flush the changes to the database.
If you insist on doing a batch update you need to mark the entities as part of the update. Maybe with a timestamp, maybe the update itself already marks them. And then you reload them using a select statement that uses the marker set during the update.
Note that you have to ensure that the entities don't exist yet in your EntityManager, otherwise you will keep the old state there. This is the purpose of #Modifying(clearAutomatically=true) recommended by other answers.
#Modifying(clearAutomatically=true)
Its works for me.
It will never return void or your class type, add return type int or Integer like below,
#Modifying(clearAutomatically=true)
#Transactional
#Query("UPDATE MyEntity SET deletedAt = CURRENT_TIMESTAMP WHERE id = ?1")
Integer markAsSoftDeleted(long id);
A logical place to place named query annotations is on a data access class, one that deals the logic of saving, retrieving etc. data for an entity.
However, Hibernate throws an exception "No query defined for that name" if I place a NamedQuery annotation on a class which is not marked with #Entity.
Why does Hibernate or JPA limit named queries to be only placed on entities? Could it be a future feature?
There are a few crude workarounds such as using an
empty entity to hold queries, which makes me think that it would useful to not be restricted like this. I know I can use an XML config, but named queries on non-entities would still be useful.
If you check the JpaRepository you can see that you can declare them in other way:
Annotation based named query configuration
#Entity
#NamedQuery(name = "User.findByEmailAddress",
query = "select u from User u where u.emailAddress = ?1")
public class User {
}
Declare query at the query method using #Query
public interface UserRepository extends JpaRepository<User, Long> {
#Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
}
Hi i am new to spring JPA . I have a table which has two columns namely 'active_from' and 'active_to'.Now I would like to get a column which has a date(ie.current date) which has a value between "active_from" and "active_to".How to acheive this using spring jpa repositories.?
This link you could find query methods to add queries that are not covered by the common jpa repository or with the support creation.
Check 2.3.4 Using #Query in
JPA repositories
Using Query methods you can do that, try something like this.
public interface UserRepository extends JpaRepository<User, Long> {
#Query(value = "SELECT * FROM USERS WHERE active_from > :?0 AND active_to < :?0 )
List<User> findBetweenDate(String currentDate);
}
I'm getting the error Not supported for DML operations when I use the following HQL...
#Query("UPDATE WorkstationEntity w SET w.lastActivity = :timestamp WHERE w.uuid = :uuid")
void updateLastActivity(#Param("uuid") String uuid, #Param("timestamp") Timestamp timestamp);
What could be causing the issue? It doesn't seem to be a common error given the few results I've found in Google.
Check the post hibernate hql ERROR: Not supported for DML operations in the hibernate users forum.
Most likely you called
querySt.list();
for your UPDATE query. Instead you should call
querySt.executeUpdate();
I was also having the same problem with annotations.After searching and doing some tricks I was able to solve it.
There are some below steps which you need to verify while using DML operation with JPA.
Use anotation
#Modifying(org.springframework.data.jpa.repository.Modifying) and #Transactional(org.springframework.transaction.annotation.Transactional) on required method.
Use void as return type of method.
e.g:-
#Modifying
#Query("UPDATE ProcedureDTO o SET o.isSelectedByUser =?1")
#Transactional
public void getListOfProcedureBasedOnSelection(Boolean isSelected);```
Make sure your service class method which calls updateLastActivity has #Transactional(org.springframework.transaction.annotation.Transactional) annotation. and modify the repository method to below,
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
...
#Modifying
#Query("UPDATE WorkstationEntity w SET w.lastActivity = :timestamp WHERE w.uuid = :uuid")
void updateLastActivity(#Param("uuid") String uuid, #Param("timestamp") Timestamp timestamp);
For more insights please use this answer.
I had exact same problem, in my case I had to only add #Modifying annotation. According to documentation:
Indicates a query method should be considered as modifying query as that changes the way it needs to be executed. This annotation is only considered if used on query methods defined through a Query annotation. It's not applied on custom implementation methods or queries derived from the method name as they already have control over the underlying data access APIs or specify if they are modifying by their name.
Queries that require a #Modifying annotation include INSERT, UPDATE, DELETE, and DDL statements.
The same happened to me because, being q an object of class Query, q.list() is not to be used for updates or deletes, but q.executeUpdate()