Spring #Transactional annotation behaves weird - spring

I have the following method:
#Transactional
public void onEmailMessage() {
this.departmentService.removeUserFromDepartments(user, depsids);
this.departmentService.getDepartmentUsers(user.id);
}
The weird thing when i invoke this method, the first line:
this.departmentService.removeUserFromDepartments(user, depsids);
is called but the DB is not changing at all and the user is still connected to the deparment (many to many relation)
afterwards the method :
this.departmentService.getDepartmentUsers(user.id);
is called and returns users that are connected to the department including the removed user from line#1.
when the method returns - if i check the DB the user i removed is actually been removed from the table!
can i make the query return the actual updated values??

There is nothing weird about this. You are performing two different queries within the same transaction. Persistence context is updated, but the transaction hasn't been committed yet, and you can't see your changes after first line is finished. Transaction is a set of statements (in this case - statements created by those two methods of yours) which gets executed after commit is invoked. When the whole (onEmailMessage) method finished it's job, the transaction is committed and you are seeing the changes.
The solutions would be:
Make them as two separate transactions. For e.g:
#Transactional
public void removeUser(...) {
someInstance.departmentService.removeUserFromDepartments(user, depsids);
}
And:
#Transactional
public List<?> getUsers(...) {
return someInstance.departmentService.getDepartmentUsers(user.id);
}
Then the highest level would be onEmailMessage() method, which has to be non-transactional and in separate class then these two methods above. Call them both in this level and it will work.

You have marked it as Transactional. Changes in DB is made after executing all the queries. Either all of the operations will be committed or none.

The transaction hasn't been committed yet so changes won't necessarily have been written to the DB.
You could try calling
entityManager.flush();
after removeUserFromDepartments() but before getDepartmentUsers() to force the DB changes to be written before the commit.

Related

How make repository save synchronized with Spring Boot

I have one method that first get the last id from my table by one type.
then I set my bean with this number and save
Long number = geradorProtocoloService.getNumero(protocolo.getTipo()
protocolo.setNumber(number);
protocolo = protocoloRepository.saveAndFlush(protocolo);
I put this code in a synchronized block
synchronized (this) {
protocolo.setNumero(geradorProtocoloService.getNumero(protocolo.getTipo());
protocolo = protocoloRepository.saveAndFlush(protocolo);
}
the method have more logic(save a file and etc) then return this to my user
but when I run my tests the he are catching a few times the same number (numero) I think that happens because the jpa don't commit my transaction when I execute saveAndFlush.
So how can I make everything in my synchronizes block be commited with jpa
tks
You are probably right in your analysis.
Normally transactions are controlled by #Transactional annotations. So if you extract your synchronized block in a method in a Spring Bean with a #Transactional annotation and call it from outside any transaction it will commit in the end.

Spring native query executed within a transaction taking outdated value

I'm using Spring Boot (1.4.4.REALEASE) with Spring Data in order to manage a MySql Database. I've got the following case:
We update one revision performed in one equipment using the RevisionService.
RevisionService saves the revision and calls the EquipmentService to update the equipment status.
The updateEquipmentStatus does a call to a Db stored procedure in order to evaluate the equipment with its revisions altogether and update the field.
I've tried some options but don't achieve to get the updated status for the equipment. The updateEquipmentStatus method keeps writing the previous status for the equipment (not considering the current revision being stored in the transaction). The code is written this way:
RevisionService
#Service
public class RevisionService{
#org.springframework.transaction.annotation.Transactional
public Long saveRevision(Revision rev){
//save the revision using JPA-Hibernate
repo.save(rev);
equipmentService.updateEquipmentStatus(idEquipment);
}
}
EquipmentService
#Service
public class EquipmentService{
#org.springframework.transaction.annotation.Transactional
public Long updateEquipmentStatus(Long idEquipment){
repo.updateEquipmentStatus(idEquipment);
}
}
EquipmentRepo
#Repository
public interface EquipmentRepo extends CrudRepository<Equipment, Long> {
#Modifying
#Procedure(name = "pupdate_equipment_status")
void updateEquipmentStatus(#Param("id_param") Long idEquipment);
}
As far as I understand, as both methods are annotated with Spring's transactional, the updateEquipmentStatus method should be executed in the scope of the current transaction. I've also tried with different options for the #Transactional annotation from updateEquipmentStatus, such as #Transactional(isolation=Isolation.READ_UNCOMMITTED) (which shouldn't be required, because I'm using the same transaction) and #Transactional(propagation=Propagation.REQUIRES_NEW), but keeps not considering the current status. That's how my stored procedure is saved into the MySql DB:
CREATE DEFINER=`root`#`localhost` PROCEDURE `pupdate_equipment_status`(IN `id_param` INT)
LANGUAGE SQL
NOT DETERMINISTIC
MODIFIES SQL DATA
SQL SECURITY DEFINER
COMMENT ''
BEGIN
/*Performs the update considering tequipment and trevision*/
/*to calculate the equipment status, no transaction is managed here*/
END
I also want to clarify that if I execute some modification in the equipment itself (which affects only tequipment), the status is being properly updated. InnoDb is the engine being used for all the tables.
UPDATE
Just changed the repo method to use a nativeQuery instead and the same problem keeps happening, so the Db procedure being involved should be discarded:
#Modifying
#Query(nativeQuery = true, value= "update tequipment set equipment_status = (CASE WHEN (...))")
void updateEquipmentStatus(#Param("id_param") Long idEquipment);
UPDATE2
Having done more tests and added a log with TransactionSynchronizationManager.getCurrentTransactionName() in the methods, that's the concrete issue:
Changes done in the equipment service are properly picked by the updating function (When something in tequipment changes, the status in tequipment is calculated properly).
Changes done in the revision service (trevision) result in an outdated value in tequipment (it doesn't matter if Spring does it in a different transaction using REQUIRES_NEW or not). Spring seems to create a new transaction properly when using REQUIRES_NEW in establishEquipmentStatus, because the current transaction name changes, but the native query doesn't have the latest values (because of the transaction before not being commited?). Also tried removing #Transactional from establishEquipmentStatus so the same transaction is used, but the issue keeps happening.
I would like to highlight that the query used to update equipment status has a case expression with multiple subqueries using trevision.
Adding the following code fixes it (programatically flushing the transaction state to the Database):
#Service
public class EquipmentService{
#PersistenceContext
private EntityManager entityManager;
#org.springframework.transaction.annotation.Transactional
public Long updateEquipmentStatus(Long idEquipment){
entityManager.flush();
repo.updateEquipmentStatus(idEquipment);
}
}
Still it would be great to find a declarative way to do it..
Changing to read uncommitted is the right idea but you'd also need to flush the entitymanager before your stored procedure is called. See this thread:
How to make the queries in a stored procedure aware of the Spring Transaction?
Personally I'd do it all in Spring unless you are absolutely forced to use a stored procedure.

Spring Data (Hibernate) JPA update of individual fields not visible within transaction

I have an entity "Job" with a boolean flag "suspended":
#Entity
#XmlRootElement(name = "Job")
#Where(clause = "deleted=0")
public class Job {
...
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private boolean suspended;
...
}
And a Spring CrudRepository (JPA Hibernate) is used for persistence:
#Repository
public interface JobRepository extends CrudRepository<Job, Integer>, JobStatusSupport {}
I need to update the "suspended" flag individually, without overwriting updates done to other fields in concurrent threads. So the natural thing to do seemed to be writing a method that only updates the "suspended" field:
public class JobRepositoryImpl implements JobStatusSupport {
private final static String SET_SUSPENDED = "UPDATE Job SET suspended = :suspended, modificationDate = :modificationDate WHERE id = :id";
#Override
public int setSuspended(int id, boolean suspended, Instant modificationDate) {
int updateCount = em.createQuery(SET_SUSPENDED)
.setParameter("suspended", suspended)
.setParameter("modificationDate", modificationDate)
.setParameter("id", id)
.executeUpdate();
return updateCount;
}
}
Now I have the following scenario in my code (shortened obviously, in reality this is spread out over several methods, but this example does reproduce the problem):
#Transactional
public void resumeJob(int id) {
Job jobA = jobRepository.findOne(Integer.valueOf(id));
// jobA.suspended == true
// let's set "suspended" to "false"
int updateCount = jobRepository.setSuspended(id, false, Instant.now());
// OK: updateCount is 1
Job jobB = jobRepository.findOne(Integer.valueOf(id));
// jobB.suspended == true ??? that was just set to "false, wasn't it?
}
Probably I am missing some basics about JPA/Hibernate. But still, this is extremely counterintuitive: Why is jobB.suspended still "true" although the update is successful and the data is read again "from the DB"? Why is the update of the individual field not visible within the transaction?
(As one would expect, after the transaction is complete, Job.suspended is "false" in the database and for subsequent reads.)
How would one go about this properly? How should I write code that updates individual fields so that JPA becomes aware of what was done? Do I have to look into "merge" for something as simple as this?
Being able to write our own SQL statements is crucial for our project. I am trying Spring Data JPA mainly to avoid the tedious work of writing tons of CRUD operations. But if I am already encountering such problems with this simple scenario, I am wondering whether we would not be better of using JdbcTemplate.
UPDATE: LEARNED SOMETHING ABOUT ORM
Man, was I clueless! I worked in projects where JPA was used before. But I never had to deal with it in detail (and I wonder if anybody else did).
The entire effort of writing an "update method" is futile! I have reduced this to the following:
#Transactional
public void resumeJob(int id) {
Job job = jobRepository.findOne(Integer.valueOf(id));
job.setSuspended(false);
job.setName("And Now for Something Completely Different.");
}
That's all! This updates the DB and the cache and God knows what. The #Transactional annotation alone is sufficient for persisting the changes. If the annotation is removed, the DB remains unchanged. So ORM is basically working against a cache (through "attached objects") that everybody sees. Then one hopes that people put #Transactional in the right places (not on private methods, for example...) and that the ORM machinery knows what it is doing (for example, not making cache updates visible outside of an open transaction).
Honestly, this seems a bit too magic for my taste. But now that I know what it is all about, I will give it a try. Writing gazillions of CRUD methods isn't very appealing, either.
Please comment if I got this wrong or if you have links with best practices. (I am starting to wonder if it wouldn't be best to immediately detach every object I get from the DB, defeating the entire purpose of ORM :-)
UPDATE: EntityManager#clear() Is Enough for a Quick Fix
This is definitely not the clever way to use ORM, but for the moment I can simply call clear() in the few update methods I have written. This invalidates the entire cache and the next read somewhere else in the transaction receives the updated data. Of course, the right way to do it, would be to simply modify the attached entity, i.e. "job.setSuspended(false);".
Calling flush() is not needed, probably it only becomes of interest when you want to minimize the risk of losing data in case of a system crash. I suppose that Hibernate does not immediately write completed transactions to disk?
It's counter-intuitive, but if you think about it, it's quite normal.
You load an entity with ID 3. Hibernate stores it in its session cache
You execute an update query. This query is almost a black box to Hibernate. It can't know which rows are affected by the changes, and you're not doing these changes by modifying the entities, but by modifying rows in the database directly. So the rows are modified, but the entity with ID 3 is left, untouched, in the session cache
You load the entity again, in the same session. So Hibernate just returns the instance that is already in the cache, and thus doesn't contain the changes.
If you want an updated entity, you have two solutions:
modify the database by modifying the entity, or
clear the cache after the update query has been made.

Spring transaction propagation_required issue

In our java project we are using ORM with hibernate and spring.
I had problems in deleting persistent objects. For example this sample method gets entities by ids and then delete them:
#Transactional
public void remove(List<Long> ids) {
SearchTemplate template = new SearchTemplate();
template.addParameter("milestoneId",ids);
List <InvoiceQueue> items = this.findByCriteria(template);
...
this.delete(items);
}
Method executes Ok without any exception but doesn't actually delete the items from the DB.
Adding the following annotation to the method definition #Transactional(propagation = Propagation.REQUIRES_NEW) solves the problem.
Can anyone explain why it doesn't work with the default propagation type PROPAGATION_REQUIRED.
Thanks in advance.
Environment details :
hibernate.version 3.5.5-Final, spring.version 3.0.5.RELEASE
Really just repeating what #PeterBagyinszki said in his comment, but the reason quite probably is that the transaction within which your delete occurs gets rolled back due to some other part throwing an exception, and all the changes made during the transaction get canceled. With Propagation.REQUIRES_NEW, the delete is done within it's own separate nested transaction. The outcome of the nested transaction (committed or rolled back) won't affect the "outer" transaction and vice versa.
Check your logs to see what is causing the transaction to be rolled back, note that even something like a simple SELECT -query failing with something like NoResultException will cause the transaction to roll back, unless you explicitly state in the #Transactional-annotation it not to roll back on certain exceptions.

#Transactional Annotation + for a data insertion in a loop

I am using Spring 3, JPA + Hibernate for a CMS application. In that application I have a service class method which is annotated with #Transactional Annotation with rollBack property. Inside that method I am inserting data (ie entity classes) to a table using a loop. For each iteration of the loop entity classes has to be saved to the database. But it is not happening. The commit only happens when the execution of the loop has completed and exits from the method. Then it commits and saves all at once. But I need to read data once it gets inserted into the database before committing in this case. I tried with the ISOLATION LEVEL to read uncommitted but it didn't supported since I am using the default JPADialect. Also tried to add the hibernate implementation of jpaDialect but still it didn't worked. Please help with a workaround for this problem. One more thing, is there any way using propagation required method.
You are right, this is what I stands for in acid. Because the transactions are working in isolation, other transactions cannot see them before they are committed. But playing with isolation levels is a bad practice. I would rather advice you to run each and every iteration in a separate transaction with start and commit inside.
This is a bit tricky in Spring, but here is an example:
public void batch() {
for(...) {
insert(...)
}
}
//necessarily in a different class!
#Transactional
public void insert() {
}
Note that batch() is not annotated with #Transactional and insert() has to be in a different class (Spring service). Too long to comment, but that's life. If you don't like it, you can use TransactionTemplate manually.
remove the transactional annoation on the the method with loop.
In the loop call a separate method to perform the save, make that method transactional
You either need to go with programmatic transactions (Spring's TransactionTemplate or PlatformTransactionManager are the classes to look at, see Spring Doc for programmatic transactions, or you can call another transactional method from within your loop where the transaction is marked with Propagation.REQUIRES_NEW, meaning each call of that method is executed in its own transaction, see here. I think that the second approach requires you to define the REQUIRES_NEW method on a different Spring bean because of the AOP-Proxy. You can also omit the REQUIRES_NEW if the loop is not executed within a transaction.

Resources