Manualy execute code in transaction in test with rollback - spring

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

Related

Failure in inner #Transactional(readOnly = false, rollbackFor = Exception.class) rolls back entire outside persistence

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.

do some asynchronous action after current transaction commited in Quarkus

Let's say I have a rest call which creates some object "A" in the database. Method is marked with #Transactional annotation. And just after creation I need to launch another asynchronous process in another thread or through some messaging system or in some other async way. That new process depends on the object "A" and needs to see it.
How can I make sure that transaction is commited before new process starts execution?
For example in Spring there is
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization(){
void afterCommit(){
//do what you want to do after commit
}
})
Does Quarkus has something similar?
You can inject TransactionManager
#Inject
TransactionManager transactionManager;
and do
Transaction transaction = transactionManager.getTransaction();
transaction.registerSynchronization(new Synchronization() {
#Override
public void beforeCompletion() {
//nothing here
}
#Override
public void afterCompletion(int status) {
//do some code after completion
}
});

JDBCTemplate multiple statements in Transaction - propagation

I'm doing an insert + some updates inside a Transaction using JDBCTemplate.
The thing is that I have to set primary key programmatically which I read from a table which keeps track of Primary keys.
public void insert(final COrder cOrder) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
public void doInTransactionWithoutResult(TransactionStatus status) {
Long cOrderId = insertOrder(cOrder);
insertOrderLines(cOrder, cOrderId);
insertCOrderTaxes(cOrder, cOrderId);
updateMStorage(cOrder);
insertMTransactions(cOrder);
}
});
}
public Long insertOrder(COrder o) {
Long cOrderId = getNextId("C_ORDER");
o.setcOrderId(cOrderId);
...
}
//the above insert methods here
and finally getNextId() which gets the next id.
public synchronized Long getNextId(final String tableName) {
final IdHolder idHolder = new IdHolder(-1l);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
public void doInTransactionWithoutResult(TransactionStatus status) {
Long nextId = jdbcTemplate.queryForObject("SELECT CURRENTNEXT FROM AD_SEQUENCE WHERE NAME=?",
new String[] { tableName }, Long.class);
jdbcTemplate.update("UPDATE AD_SEQUENCE SET CURRENTNEXT = CURRENTNEXT + 1 WHERE NAME=?",
new Object[] { tableName });
idHolder.setId(nextId);
}
});
return idHolder.getId();
}
Basically I want all these insertions/updates done all or none, but this getNextId() I need to commit regardless of the outer transaction(because of reserving the next primary key).
My question is, is propagation PROPAGATION_REQUIRES_NEW the right one for Transaction which runs in method getNextId()?
Yes. If you use PROPAGATION.REQUIRES_NEW the current transaction, if any, will be paused and a new one will be created. Once the execution of that transaction completes, it will be committed if no error occurs and the outer transaction will be resumed. Regardless of what happens next, the inner transaction is committed and is now independent.
In your code above, make sure that your transactionTemplate has been configured with the right propagation mode, that is:
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);

Spring PROPAGATION_REQUIRED.. joined

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

Controlling inner transaction settings from outer transaction with Spring 2.5

I'm using Spring 2.5 transaction management and I have the following set-up:
Bean1
#Transactional(noRollbackFor = { Exception.class })
public void execute() {
try {
bean2.execute();
} catch (Exception e) {
// persist failure in database (so the transaction shouldn't fail)
// the exception is not re-thrown
}
}
Bean2
#Transactional
public void execute() {
// do something which throws a RuntimeException
}
The failure is never persisted into DB from Bean1 because the whole transaction is rolled back.
I don't want to add noRollbackFor in Bean2 because it's used in a lot of places which don't have logic to handle runtime exceptions properly.
Is there a way to avoid my transaction to be rolled back only when Bean2.execute() is called from Bean1?
Otherwise, I guess my best option is to persist my failure within a new transaction? Anything else clean I can do?
This is one of the caveats of annotations... your class is not reusable!
If you'd configure your transactions in the XML, if would have been possible.
Assuming you use XML configuration: if it's not consuming expensive resources, you can create another instance of bean2 for the use of the code you specified. That is, you can configure one been as you specified above, and one with no roll back for exception.

Resources