Im using Spring and have two methods which are using declarative transaction...
in some cases methodA calls methodB.. what i need to know is in declarative trasaction does the commit occur twice...
example
public void methodA() throws Exception {
this.transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED)
final Order order = this.transactionTemplate.execute(new TransactionCallback<Order>() {
#Override
public Order doInTransaction(TransactionStatus status) {
Order order = new Order();
String name = "Customer 2 " + (new Date()).toLocaleString();
order.setCustomer(name);
entityManager.persist(order);
..........................
.......................
// call methodB
methodB();
}
public void methodB() throws Exception {
this.transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
final Address add = this.transactionTemplate.execute(new TransactionCallback<Address >() {
#Override
public Order doInTransaction(TransactionStatus status) {
Address add= new Address ();
add.setAddress("address");
entityManager.persist(add);
......................
.....................
}
By using PROPOGATION_REQUIRED, the transaction in methodB will join the transaction started in methodA.
But would this mean that the transaction is committed twice?
I added sychronizer to the transaction in methodB :
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization(){
public void afterCommit() {
System.out.println("====> AFTER SUCCESSFUL COMMIT 2 TO DB...");
}
I added sychronizer to the transaction in methodA :
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization(){
public void afterCommit() {
System.out.println("====> AFTER SUCCESSFUL COMMIT 1 TO DB...");
}
The output i got was after methodA completed::
====> AFTER SUCCESSFUL COMMIT 2 TO DB...
====> AFTER SUCCESSFUL COMMIT 1 TO DB...
I guess this means that two sycnronisers have been assigned tot he trans..manager, and when the commit occurs once... both synchronisers are called?.. correct?
since you are using PROPAGATION_REQUIRED there will not be two different transaction, both methods will be running within the same transaction. If you look at the definition of PROPAGATION_REQUIRED it says
Support a current transaction; create a new one if none exists.
.
So if methodB completes successfully then it will not commit the transaction, but if it fails it will mark the transaction as rollback-only. The responsibility to actually commit or rollback will be on the the guy who created the transaction. In your case it will be on methodA
Related
I have a job method in a class-annotated #Transactional. This job method calls inner methods for persistence of individual records. If I simulate an error in the following inner update() method somewhere in the middle of my result set processing, I see that all successful records before/after this exception do not get saved after job completion. Why is that? All outside persistence should remain, with the exception of the individual record that failed. The inner update alone has rollbackFor.
#Service("mailService")
#Transactional
#EnableScheduling
public class MailServiceImpl implements MailService {
#Override
#Scheduled(cron = "${mail.cron.pubmed.autosynch.job}")
public void autoSynchPubMedJob() {
//... Fetch result set
for (Result r: resultset) {
try {
pubService.updatePublication(r);
} catch (Exception e) {
// Silently log and continue
log.error("Error on record: ", e);
}
}
}
The updatePublication method, this is the one with rollbackFor:
#Override
#Transactional(readOnly = false, rollbackFor = Exception.class)
public void updatePublication(Publication publication) throws Exception {
dao.update1(..);
dao.update2(..);
// Simulate exception for a specific record for testing
if (publication.getId() == 123) {
throw new Exception("Test Exception");
}
}
Result: no successful data persisted at all at the end of job completion. There should be partial persistence (for other successful records).
When I remove this Exception simulation, all data is successfully persisted at the end. Also, all data is persisted if I remove the inner call's rollbackFor.
Probaby because it uses existing transaction. Try opening a new one with propagation = REQUIRES_NEW.
Note: New transaction won't be opened if you call the method from the same service. You should use either self-reference call or extract logic to another #Service.
I am trying to use Spring retry for my Spring web application (with JPA, hibernate).
Currently, I have a long transaction chain (marked functions as #Transactional) in my application, intuitively:
start transction -> A() -> B() -> C() -> D() - E() -> commit/rollback
now I want to have D() to be retried if any concurrency failure occurred (marked D() with #retryable), but remain A(), B(), C() in current states
I failed, the function D() is not retried at all and just throwed a concurrency failure error (eg. ObjectOptimisticLockingFailureException)
For me, if I want to do such things in database, I have to make a new transaction try catch block with a cursor while loop to handle retries. I wonder is there a simple way I can handle this "partial" transaction retry in Spring?
An example code would be:
#RestController
public DimensionController()
{
...
#Autowired
private TblDimensionService dimensionService;
...
#PutMapping(...)
public ResponseEntity<TblDimensionDTO> update(#Valid #RequestBody TblDimensionDTO dimensionDTO)
{
...
dimensionService.update(dimensionDTO);
...
}
}
#Transactional //transaction in service level
#Service
public TblDimensionService()
{
...
#Autowired
private RetryService retryService;
...
public TblDimensionDTO update(TblDimensionDTO dimensionDTO) throws InterruptedException
{
if (dimensionDTO.getId() == null)
{
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "not found");
}
TblDimension dimension = findByIdOrElseThrow(dimensionDTO.getId()); //call another function to retrieve entity by id with JPA repository
dimension = retryService.updateEntity(dimension, dimensionDTO);
return tblDimensionMapper.toDto(dimension);
}
...
}
#Transactional //transaction in service level
#Service
public RetryService()
{
...
#Autowired
private TblDimensionRepository dimensionRepository;
...
//I want to only retry this part
//but this retry does not work
#Retryable(value = {ConcurrencyFailureException.class})
public TblDimension updateEntity(TblDimension dimension, TblDimensionDTO dimensionDTO) throws InterruptedException
{
Thread.sleep(3000);
dimension.setHeight(dimension.getHeight() + 1);
Thread.sleep(3000);
return dimensionRepository.save(dimension);
}
...
}
What is the difference between method with #Transactional(propagation = Propagation.SUPPORTS) vs having no #Transactional annotation at all?
#Transactional(propagation = Propagation.SUPPORTS)
public void MyMethod()
vs
public void MyMethod()
Wouldn't the one without the annotation also use a transaction if one is already open, otherwise continue without any transaction?
There is a small difference. suppose we have two methods a() and b(), and a() is going to call b(). a itself is transnational and its propagation level is Propagation.REQUIRED but b one time is annotated with #Transactional(propagation = Propagation.SUPPORTS) and one time is not with annotation.
case 1:
#Transactional
public void a() {
for (int i = 0; i < 10; i++) {
try {
productRepository.b(i, "product " + i);
} catch (RuntimeException ex){
// do nothing
}
}
}
public void b(int id, String name) {
if(id > 5)
throw new RuntimeException();
String sql = "INSERT INTO test_table VALUES(?, ?)";
template.update(sql, id, name);
}
in case 1 we have aspect() -> a() -> b() and you are able to prevent RuntimeException to reach aspect and aspect inspects the transaction to see whether it is marked to rollback, so aspect consider this transaction successful and you can see we have this result in our database
0,product 0
1,product 1
2,product 2
3,product 3
4,product 4
5,product 5
even though multiple exceptions were thrown but we were able to commit the operations have been done so far.
now consider case 2:
#Transactional
public void a() {
for (int i = 0; i < 10; i++) {
try {
productRepository.b(i, "product " + i);
} catch (RuntimeException ex){
// do nothing
}
}
}
#Transactional(propagation = Propagation.SUPPORTS)
public void b(int id, String name) {
if(id > 5)
throw new RuntimeException();
String sql = "INSERT INTO test_table VALUES(?, ?)";
template.update(sql, id, name);
}
with propagation = Propagation.SUPPORTS if a transaction already exists it's going to use it. so if you throw an exception in b it is going to mark the same transaction to rollback and even if you use the try/catch block to prevent the RuntimeException to reach aspect in a() the transaction is already marked to rollback in b and you see no result in your database. aspect() -> a() -> aspect() -> b()
credit goes to Laurentiu Spilca, see this and read the comment section
From your link, it states that Propagation.SUPPORTS might have impact on
synchronization:
SUPPORTS is slightly different from no transaction at all, as it defines a transaction scope that synchronization will apply for. As a consequence, the same resources (JDBC Connection, Hibernate Session, etc) will be shared for the entire specified scope
Yes one without any annotation will use existing transaction if one is already open but what if there is not any won't you want to revert the changes if your transaction fails.
Propagation has also other attribute has well in case you method is not dependant of parent transaction and runs in individual transaction scope, probably you can go for REQUIRES_NEW this way you can have independent transaction for your method.
I use Spring + Hibernate + JPA
I need to handle the list of customers by inserting their orders.
Here is the Unit of work :
for(Customer customer: CustomerList) {
List<Order> orderList = customer.getOrders();
for(Order order: OrderList) {
//1. Insert order into ORDER table
//If insert fails due to Duplicate key then no rollback and I follow steps 2 & 3.
//If insert fails due to any reason except duplicate key then rollback all the previous transactions
//2. select the order record (If any issue during selection then rollbackall the previous transactions)
//3. Update the order If status of the order is different from that in DB (If any issue during update then rollback all the previous transactions)
}
// 4. Update Customer record into CUSTOMER table (If any issue during update then rollback all the previous transactions)
}
Commit is required when all the orders and customer db processes are ok.
Insert order
1.a If duplicate order do not roll back. But select that order from table and update if the status of the order is different in the req compared to the one in db
1.b If any other error during inserting of ORDER then roll back
1.c If no error then proceed inserting orders of the particular Customer
Once orders of the particular Customer is done, then update Customer table
Loop continues..
While handling point 1, 2 and 3 If everything is ok, then commit is required.
If any issues in the middle then all transactions are rolled back
Controller --> Facade layer --> Service --> Repository/Dao
Facade:
#Autowired
MyServiceBean serviceBean;
#Transactional(noRollbackFor = {EntityExistsException.class, PersistException.class, ConstraintViolationException.class, DataIntegrityViolationException.class})
#override
public void facadeMethod(MyReq req) {
List<Customer> custList = req.getCustomers():
for(Customer customer: CustList) {
List<Order> orderList = customer.getOrders();
for(Order order: orderList) {
String dbAction = "";
try {
dbAction = serviceBean.insertOrder(order);
} catch(Exception e) {
// log exception and roll back completely
}
if("handleDupl".equalsTo(dbAction) {
serviceBean.handleDuplOrder(order);
}
}
myService.updateCustomer(customer);
}
}
Service:
#Autowired
MyRepository repo;
#Transactional(propagation = propagation.REQUIRES_NEW)
#override
public String inserOrder() {
String dbAction = "";
try {
repo.insertOrderDao(order);
} catch(all duplicate key exceptions like entityExist, persist, ConstraintVioaltion, DataIntegrity e) {
dbAction = "handleDuplOrder";
} catch(all other Exception except duplicate key e) {
// roll back and throw exception to Facade layer
}
return dbAction;
}
#Transactional(propagation = propagation.REQUIRES_NEW)
#override
public void handleDuplOrder(Order order) {
try {
repo.selectOrderDao(order);
repo.updateOrder(order);
} catch(Exception e) {
// roll back and throw exception to Facade layer
}
}
Repository:
#PersistentContext(unitNmae = "MY_SHCEMA")
EntityManager entityManager;
#Override
public void insertOrderDao(Order order) {
entityManager.persist(order);
entityManager.flush();
}
Problem:
When I send req with One customer who has single order, where the order is duplicate, I see PersistException is caught inside Service method and when it exists from Service method it also throws TransactionSystemException(nested exception is RollbackException: Transaction is marked as rollback only, could not commit JPA transaction) is thrown to Facade layer irrespective of the how I suppress the exception in inner transaction.
Kindly advice If I can achieve Unit of work commit or rollback in this way.
Expected:
I want Spring's #Transactional to ignore Duplicate key exceptions by not rolling back and not affecting the next transaction.
Your outer transaction still fail because you throw ApplicationDuplOrderException() inside your service.
You should setup your services like below:
#Transactional
#Override
public void facadeMethod(MyReq req) {
List<Customer> custList = req.getCustomers():
for(Customer customer: CustList) {
List<Order> orderList = customer.getOrders();
for(Order order: orderList) {
try {
myService.insertOrder(order);
} catch(Exception e) {
// log exception and roll back completely
throw e; // important, you must rethrow
}
}
myService.updateCustomer(customer);
}
}
#Transactional(propagation = propagation.REQUIRES_NEW)
#Override
public void inserOrder() {
try {
repo.insertOrderDao(order);
} catch(all duplicate key exceptions like entityExist, persist, ConstraintVioaltion, DataIntegrity e) {
log.error(xxx); // instead of throwing
} catch(all other Exception except duplicate key e) {
throw e;
}
}
we are using spring boot 2, and in our integration tests we need manually execute some code in transaction, and on the end of transaction and after asserts, we want rollback that transaction.
We are using explicit defined transactions instead of #Transactional because sometimes we need execute in test 2 transactions.
here is sample of test:
#Test
public void fooTest() {
// transaction 1
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
#Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// some code in transaction
}
// transaction 2
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
#Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// some code in transaction
}
// here I need rollback these transactions for clean db for another tests
}
can you tell me how to use rollback in our case to rollback both transactions? It is little older code which we maintain so if it possible to do it better in boot 2 I will be gratefull for any advice. We just have to execute 2 transactions in one test.
Store references to each TransactionStatus in AtomicReference and rollback them with transaction manager after the test.
#Test
void test() {
final AtomicReference<TransactionStatus> first = new AtomicReference<>();
final AtomicReference<TransactionStatus> second = new AtomicReference<>();
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
#Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// some code in transaction
first.set(status);
}
});
// transaction 2
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
#Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// some code in transaction
second.set(status);
}
});
transactionTemplate.getTransactionManager().rollback(first.get());
transactionTemplate.getTransactionManager().rollback(second.get());
}
If you really need to start two transactions that run independently, doing some work on each one, and you need to check some asserts after they have both run, but before either has committed or rolled back - and then need to commit or rollback both after the asserts, you would need to set up your own multithreaded structure... Something like:
execute(AssertCallback aC, TransactionCallback ... tCs);
This method would start a thread for each of the tCs. Each thread would call the callback method in each one, and on return block on a barrier until all the threads executing tCs get to that same point. The main thread would also be waiting for all the tC threads to block, and then it would run the aC callback, and when that returns, release all the tC threads so they can commit/rollback and exit.
It all seems a little weird, in that the aC callback could not "see" any of the work done by any of the tC callbacks, since that's in transactions that have not been committed yet.
I've never seen this implemented... But, in case you want to see what I was talking about, take a look at the parallel-test project here: https://github.com/moilejter/examples.git