I have the following setting (it's an analogy), and the repository changes on methodB are not rollbacked. Propagations should be default = REQUIRED, so what could be the explanation?
Parent.java
#Transactional
public void methodA(){
child.methodB();
anotherMethodThatThrowsARuntimeException();
}
Child.java
#Transactional
public void methodB(){
repository.save(entity)
}
First of all, is my understanding correct in that I should expected everything rollbacked?
Even if all this situation is happening when this code is wrapped under a #Transactional(isolation = Isolation.READ_UNCOMMITTED) test?
EDIT: Just for the sake of the resolution: the problem was that.SQL rollback was indeed at the end of of outer transaction but the managed context was not cleared so rollback from inner transaction was not visible
Some database engines don't have support for transaction. First check your database engine. MyISAM engine of MySQL is an example for this case.
Related
I've got a method in my service class which performs some changes to an entity, afterwards it executes some critical code (communication to different microservice) which may come back with error information attached to an exception. If an error occurs, the changes should be reverted. To achieve this I thought I'd use #Transactional.
Service
#Service
public class MyService {
#Autowired
private EntityRepository repository;
#Transactional(rollbackFor = CriticalCodeException.class, isolation = Isolation.READ_COMMITTED, propagation = Propagation.NESTED)
public Entity foo(entity) {
/* perform changes ... */
entity.setBar(...)
try {
/* critical code */
} catch (CriticalCodeException e) {
e.setEntityId(entity.getId());
throw e;
}
}
public attachError(CriticalCodeException e) {
// reload entity which should be rolled back but is not
Entity reloaded = repository.findOne(e.getEntityId);
reloaded.setError(e.getError); // attach error
repository.save(reloaded) // saves changes which should be rolled back
}
}
As the CriticalCodeException is a RuntimeException and might also occur in another context, I've created a designated ExceptionHandler which is supposed to catch it and attach the error to my entity.
ExceptionHandler
#ExceptionHandler(CriticalCodeException .class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
public ErrorResponse processError(CriticalCodeException e) {
entityService.attachError(e);
return e.getError();
}
Yet, when I attach the error I do not wont to save the pervious made changes too. So when I reload the entity after the Exception has been thrown out of my nested #Transactional-method I'd expect it to be in its initial state. Well, it is not. If I do not attach the error (and thus omit my save() call) the changes do not get persisted. So I figure the rollback eventually executes after my exception handling.
Can anybody tell me how I can access my entity in its initial state AFTER the rollback is done?
I am using Spring Boot Data with JPA and Hibernate. I've tried various configurations with things like #EnableTransactionManagement and setting up different combinations of beans. Yet, after a lot of research, I'm pretty confindent that the #Transactional is actually working (as to be seen from the eventual rollback described above) and should work without further configuration. What am I doing wrong?
EDIT
The changes were in fact rolled back but my entity-manager did not get reset, just M. Deinum pointed out. In my persistence setup changes to an entity were only persisted by a call to repository.save(entity). What I did eventually was to omit the transactional approach and instead implement repository.reload(entity) which refreshes my entity via the entity-manager. This way I can get rid of previous made changes which did not even get persisted in the first place.
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.
I use spring 3.2 and Hibernate 4 in my project. When i query table i get a "No Session found for current thread" message. I try to use #Transactional annotation(it get success) but i don't want to put #Transactional to every service implementation.
Is there an another way?
In other words "How can i do a simple "insert" operation without using #Transaction?"
Thx...
You should not have #Transactional on you DAO methods, in fact you should never be accessing your DAO methods directly, you should be using an #Service. A service will use zero or more DAO classes to perform operations, only after all operations are completed will the transaction be committed.
#Repository
public class CustomerDao() {
// dao methods here, they are not transactional but will be run within a sevice transaction
}
#Service
#Transactional
public class CustomerService() {
private final CustomerDao customerDao;
#Autowired
public CustomerService(CustomerDao customerDao) {
this.customerDao = customerDao;
}
//service methods here (they are all transactional because we have annotated the class)
}
#Transactional is used for making a java code call in transaction so that in case any exception occurred during the process then all database changes will be rolled back. In ideal scenario every service which you think should be independent should have #Transactional annotation. Hibernate also want each database calls in transaction thats why they have implemented in such a way that Transaction will be required for each database query to be successful. I am not sure why you wanted your service to be out of transaction still they would like to fire database calls.
I am fairly new to spring, and doing some integration tests.
Using Hibernate, MySql and Spring data JPA.
I am using transaction support and everything gets rolled back at the end of each test.
For example:
#Test (expected=DataIntegrityViolationException.class)
public void findAndDelete() {
UUID uuid = UUID.fromString(TESTID);
User user= iUserService.findOne(uuid);
iUserService.delete(cashBox);
iUserService.flush();
assertNull(iUserService.findOne(uuid));
}
In the above code, I call the iUserService.flush(), so that the sql gets sent to the DB, and an expected DataIntegrityViolationException occurs because there is a foreign key from User to another table (Cascade is not allowed, None). All good so far.
Now, if I remove the iUserService.flush()
then the expected exception does not occur because the sql does not get sent to the DB.
I tried adding the flush() into a teardown #After method, but that didn't work as the test does not see the exception outside of the test method.
Is there any way to avoid calling the flush within the test methods?
It would be preferable if the developers on my team did not have to use the flush method at all in their testing code
Edit:
I tried adding the following
#Before
public void before() {
Session session = entityManagerFactory.createEntityManager().unwrap(Session.class);
session.setFlushMode(FlushMode.ALWAYS);
}
but it does seem to flush the sqls, before each query.
In my humble opinion, it's better than the developers of your team know what they are doing.
It includes the things that are configured by default and the consequences of that.
Please, take a look to why you need avoid false positives when testing ORM code
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.