The goal is to update entries in a table. Conceptually I need...
public interface MessageRepository extends JpaRepository<Message, Long> {
#Modifying
#Transactional
#Query("update Message set isRead = true where receiver = id and isRead = false")
void markAllAsRead(Long id);
This compiles however when I run it It seems as though the transaction never commits. According to the documentation I need to flush the cache. This doesn't seem possible with my current setup with what Ive seen here because I do not have a function body. I'm trying to fix someone elses' code so I really don't want to refactor the entire thing.
Have you tried using the following parameter?
#Modifying(flushAutomatically = true)
Related
I have a field in my SQL table which needs to be updated by one and return a unique ID. But looks like it is not being updated on the latest data, especially when I give a lot of requests.
#Transactional
public interface CompanyRepository extends CrudRepository<Company, Integer> {
#Modifying(clearAutomatically = true, flushAutomatically = true)
#Query(value = "update Company c set c.accessId = :accessId WHERE c.id = :companyId AND c.accessId = :oldAccessId", nativeQuery = true)
int updateAccessId(#Param("companyId") Integer companyId, #Param("accessId") Integer accessId, #Param("oldAccessId") Integer oldAccessId);
}
Even with both clearAutomatically and flushAutomatically set to true, it is not working on the latest data.
I could see two update query being successful both with oldAccessId as the same.
Should the table design be changed?
PS : I have tried without nativeQuery = true as well.
What you have here is a classical race condition.
Two threads read the same entity, with identical accessId, increment it by one and then writing the result using the method you show in your question. Resulting in effectively only one update.
There are various ways how to fix this.
Use JPA and optimistic locking.
Assuming you have an attribute with #Version annotated you can do the
following in a single transactional method:
Load the entity.
increment the accessId.
persist the entity.
If another transaction tries to do the same on the same entity one of the two will get an exception. In that case retry until the update goes through.
Use the database.
Make reading and updating atomic in the database. Instead of passing the new value as parameter use a query like this one:
update Company c
set c.accessId = c.accessId + 1
WHERE c.id = :companyId
Make it a version attribute.
As mentioned above JPA already has #Version attributes which get updated on every change. Depending on the exact requirements you might be able to make accessId simply that attribute and get it updated automatically.
Check if any rows got updated.
Based on your comment your intention was to basically reimplement what JPA does with version attributes. If you do this you are missing a crucial part: checking that the update actually updated anything, by comparing the return value against 1.
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);
I'm refactoring a code base to get rid of SQL statements and primitive access and modernize with Spring Data JPA (backed by hibernate). I do use QueryDSL in the project for other uses.
I have a scenario where the user can "mass update" a ton of records, and select some values that they want to update. In the old way, the code manually built the update statement with an IN statement for the where for the PK (which items to update), and also manually built the SET clauses (where the options in SET clauses can vary depending on what the user wants to update).
In looking at QueryDSL documentation, it shows that it supports what I want to do. http://www.querydsl.com/static/querydsl/4.1.2/reference/html_single/#d0e399
I tried looking for a way to do this with Spring Data JPA, and haven't had any luck. Is there a repostitory interface I'm missing, or another library that is required....or would I need to autowire a queryFactory into a custom repository implementation and very literally implement the code in the QueryDSL example?
You can either write a custom method or use #Query annotation.
For custom method;
public interface RecordRepository extends RecordRepositoryCustom,
CrudRepository<Record, Long>
{
}
public interface RecordRepositoryCustom {
// Custom method
void massUpdateRecords(long... ids);
}
public class RecordRepositoryImpl implements RecordRepositoryCustom {
#Override
public void massUpdateRecords(long... ids) {
//implement using em or querydsl
}
}
For #Query annotation;
public interface RecordRepository extends CrudRepository<Record, Long>
{
#Query("update records set someColumn=someValue where id in :ids")
void massUpdateRecords(#Param("ids") long... ids);
}
There is also #NamedQuery option if you want your model class to be reusable with custom methods;
#Entity
#NamedQuery(name = "Record.massUpdateRecords", query = "update records set someColumn=someValue where id in :ids")
#Table(name = "records")
public class Record {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
//rest of the entity...
}
public interface RecordRepository extends CrudRepository<Record, Long>
{
//this will use the namedquery
void massUpdateRecords(#Param("ids") long... ids);
}
Check repositories.custom-implementations, jpa.query-methods.at-query and jpa.query-methods.named-queries at spring data reference document for more info.
This question is quite interesting for me because I was solving this very problem in my current project with the same technology stack mentioned in your question. Particularly we were interested in the second part of your question:
where the options in SET clauses can vary depending on what the user
wants to update
I do understand this is the answer you probably do not want to get but we did not find anything out there :( Spring data is quite cumbersome for update operations especially when it comes to their flexibility.
After I saw your question I tried to look up something new for spring and QueryDSL integration (you know, maybe something was released during past months) but nothing was released.
The only thing that brought me quite close is .flush in entity manager meaning you could follow the following scenario:
Get ids of entities you want to update
Retrieve all entities by these ids (first actual query to db)
Modify them in any way you want
Call entityManager.flush resulting N separate updates to database.
This approach results N+1 actual queries to database where N = number of ids needed to be updated. Moreover you are moving the data back and forth which is actually not good too.
I would advise to
autowire a queryFactory into a custom repository
implementation
Also, have a look into spring data and querydsl example. However you will find only lookup examples.
Hope my pessimistic answer helps :)
For concurrency purpose, I have got a requirement to update the state of a column of the database to USED while selecting from AVAILABLE pool.
I was thinking to try #Modifying, and #Query(query to update the state based on the where clause)
It is all fine, but this is an update query and so it doesn't return the updated data.
So, is it possible in spring data, to update and return a row, so that whoever read the row first can use it exclusively.
My update query is something like UPDATE MyObject o SET o.state = 'USED' WHERE o.id = (select min(id) from MyObject a where a.state='AVAILABLE'), so basically the lowest available id will be marked used. There is a option of locking, but these requires exceptional handling and if exception occur for another thread, then try again, which is not approved in my scenario
You need to explicitly declare a transaction to avoid other transactions being able to read the values involved until it's commited. The level with best performance allowing it is READ_COMMITED, which doesn't allow dirty reads from other transactions (suits your case). So the code will look like this:
Repo:
#Repository
public interface MyObjectRepository extends JpaRepository<MyObject, Long> {
#Modifying
#Query("UPDATE MyObject o SET o.state = 'USED' WHERE o.id = :id")
void lockObject(#Param("id") long id);
#Query("select min(id) from MyObject a where a.state='AVAILABLE'")
Integer minId();
}
Service:
#Transactional(isolation=Isolation.READ_COMMITTED)
public MyObject findFirstAvailable(){
Integer minId;
if ((minId = repo.minId()) != null){
repo.lockObject(minId);
return repo.findOne(minId);
}
return null;
}
I suggest to use multiple transactions plus Optimistic Locking.
Make sure your entity has an attribute annotated with #Version.
In the first transaction load the entity, mark it as USED, close the transaction.
This will flush and commit the changes and make sure nobody else touched the entity in the mean time.
In the second transaction you can no do whatever you want to do with the entity.
For these small transactions I find it clumsy to move them to separate methods so I can use #Transactional. I therefore use the TransactionTemplate instead.
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()