I am learning JDBS template and wonder if there is a way to rollback operation.
It is easy to do it using JDBC, just
conn.setAutoCommit(false);
// DoinП stuff
con.rollback();
But is there a way do the same using JDBS template?
You should use the spring-boot-starter-data-jdbc like in this guide
Thanks to this spring-boot-starter-data-jdbc and to the spring boot autoconfiguration mechanism, a jdbc template bean and a transaction manager bean will be created for you.
Then you can use #Transactional annotation on non private method to wrap your code into a transaction :
#Transactional
public void methodA() {
jdbcTemplate.update(...);
jdbcTemplate.update(...);
}
or you can use a transacionTemplate to handle the transaction programatically
#Service
public class ServiceA {
private final TransactionTemplate transactionTemplate;
public SimpleService(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
public void methodA() {
transactionTemplate.execute(new TransactionCallback<>() {
public Object doInTransaction(TransactionStatus status) {
jdbcTemplate.update(...);
jdbcTemplate.update(...);
return jdbcTemplate.query(...);
}
});
}
}
If you want to execute multiple statements and want to be sure all or none executed, you can use transactions.
First, you have to create a session and begin the transaction.
After, you can execute statements and commit the transaction.
But, in case of problems, you can rollback (undo previous statements).
With spring you can use #Transactional annotation
#Transactional
public void transaction() { // Spring will begin transaction
doTransactionalStatements(); // Spring will rollback in case of RuntimeException
} // Spring will commit transaction
Related
I am using jdbc:mysql:replication
For Example my connection info
spring.datasource.url = jdbc:mysql:replication://masterDB:3306,readerCluster:3306
where spring #transaction with readOnly=true will route to reader DB
I have a transaction for example
#Override
#Transactional(rollbackFor = Exception.class)
public BankGroup save(BankGroup bankGroup) {
BankGroup bankGroup= bankGroupRepo.save(bankGroup);
List<MerchantBankConfig> merchantBankConfigList =merchantBankConfig.findAll();
// some business logic...
// end
return bankGroup;
}
#Override
#Transactional(readOnly = true)
public List<MerchantBankConfig> findAll() {
return merchantBankRepo.findAll();
}
in save() method , first it will open a connection in masterDB since readyOnly=false.
after perform bankGroupRepo.save(bankGroup) save method. there is a findAll() method which I hope it route to reader DB.
Understand that the findAll() is using default Propagation.REQUIRED so it will using same transaction.
Is there any other way that can route all select statement to readerDB with best optimize way?
Current I can use Propagation.REQUIRES_NEW to route to reader DB. is there any better way?
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW , readOnly = true)
public List<MerchantBankConfig> findAll() {
return merchantBankRepo.findAll();
}
What i have tried:
AOP spring data repositry and use DBContextHolder to set to readerDB by implement AbstractRoutingDataSource
public class MasterSlaveRoutingDataSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
return DatabaseContextHolder.getEnvironment();
}
}
#Aspect
#Component
#Order(0)
public class TransactionReadonlyAspect {
#Around("#annotation(transactional)")
public Object proceed(ProceedingJoinPoint proceedingJoinPoint, org.springframework.transaction.annotation.Transactional transactional) throws Throwable {
System.out.println("Aspect executed db connection = " +transactional.readOnly());
try {
if (transactional.readOnly()) {
DatabaseContextHolder.set(DatabaseEnvironment.READ);
}
return proceedingJoinPoint.proceed();
} finally {
DatabaseContextHolder.reset();
}
}
}
This is not working as where is AOP is happened before #transactional is opened in service layer. Even I set the connection to reader, it will no reopen a DB connection since the connection is opened.
Expected:
Best optimize way for all select statement will route to reader DB in this kind of scenario as we have alot of this kind of select query in our project.
I'm trying to update a few columns in a table and the updation happens inside a forEach.
I want to handle each iteration as an individual transaction and any rollback inside the forEach should only rollback on the transactions that occurred on the specific iteration (not all previous iterations).
Moreover, I don't want an exception to trigger the rollback. Rather, it has to be triggered programmatically. For that, I'm making use of this - TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
This is what I tried so far:
#Service
public class MyService {
#Transactional
public void processLabResults() {
arrayList.forEach(i -> {
proccessDiagnosis();
});
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
proccessDiagnosis() {
boolean isDispositionUpdated = updateDisposition(); // calls JPA Repository to update
if(isUpdated) {
boolean isSomethingElseUpdated = updatedSomethingElse(); // calls JPA Repository to update
if(!isSomethingElseUpdated) {
TransactionInterceptor.currentTransactionStatus().setRollbackOnly(); //Should rollback only the transactions that happened in the current iteration
}
}
}
}
If I executed the above, it rolls back all the previous transactions that are not part of the current iteration as well. If I remove # Transactional annotation from the processLabResults method, I'm getting No transaction aspect-managed TransactionStatus in scope error and no rollback happens.
Any help would be appreciated. Thanks!
This answer helps me resolve the issue. I moved the proccessDiagnosis() function to a different service file.
Service.java
#Service
public class MyService {
#AutoWired
AnotherService anotherService;
#Transactional
public void processLabResults() {
arrayList.forEach(i -> {
anotherService.proccessDiagnosis();
});
}
}
AnotherService.java
#Service
public class AnotherService {
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void proccessDiagnosis() {
boolean isDispositionUpdated = updateDisposition(); // calls JPA Repository to update
if(isUpdated) {
boolean isSomethingElseUpdated = updatedSomethingElse(); // calls JPA Repository to update
if(!isSomethingElseUpdated) {
TransactionInterceptor.currentTransactionStatus().setRollbackOnly(); //Should rollback only the transactions that happened in the current iteration
}
}
}
}
I have a Spring Boot 2.3 REST application with a standard architecture (controller -> service -> repository). For auditing purposes, I inserted a thin layer (some kind of a Mediator), so that I persist all the requests to some specific service method regardless they are successfully persisted or an exception is thrown and the transaction is rollbacked. Example:
#Component
public class Mediator {
private final Service service;
private final AuditService auditService;
public Mediator(Service service, AuditService auditService) {
this.service = service;
this.auditService = auditService;
}
#Transactional
public void saveReport(Report report) {
try {
service.saveReport(report);
auditService.saveReport(report);
} catch (Exception exception) {
auditService.saveReport(report, exception);
throw exception;
}
}
}
Thus I encountered a weird situation: if I place the #Transactional on the Mediator's method (example above), all the operations in the JPA Repositories are successfully persisted. If I place the #Transactional on the ServiceImpl method instead (example below) and no longer on the Mediator, one of the delete queries is not ran, although the rest of the queries are executed just fine. Suppose my ServiceImpl looks something like:
#Service
public class ServiceImpl implements Service {
private final RepositoryA repositoryA;
private final RepositoryB repositoryB;
public ServiceImpl(RepositoryA repositoryA, RepositoryB repositoryB) {
this.repositoryA = repositoryA;
this.repositoryB = repositoryB;
}
#Transactional
public void saveReport(Report report) {
repositoryA.save(report.getA());
repositoryB.save(report.getB());
repositoryB.delete(report.getSomethingElse());
}
}
The only visible difference between the two approaches with respect to the Transactions is that in the first scenario, I have this for each Mediator call:
o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(909894553<open>)] for JPA transaction
while in the second scenario, I have this:
tor$SharedEntityManagerInvocationHandler : Creating new EntityManager for shared EntityManager invocation
I guess there is a difference between annotating directly a bean's method with #Transactional (what I do in the Mediator) and annotating a bean's (that is the implementation of the interface injected) method with the same thing (what we usually do by annotating a ServiceImpl method), but I am not sure and cannot spot the reason for this weird behaviour. Does anyone have an idea why this happens?
I guess that this difference in behavior is due to Spring OpenSessionInView which is enabled by default.
You must set in application.yml
spring:
jpa:
open-in-view: false
Please see OSIV
Recently, I've been working with Spring boot + spring data jpa + hibernate. I faced one problem with spring transactions. Here is my service class and two questions:
#Transactional
#Service
class MyService {
#Autowired
private MyRepository myRep;
public void method_A() {
try {
method_C();
.....
method_B();
} catch(Exception e) {}
}
public void method_B() {
Entity e = new Entity();
e.set(...);
myRep.save(e);
}
public void method_C() throws Exception {
....
}
}
1.If method method_C() throws an Exception and I want to catch it and log it, the transaction is not rollbacked in method method_B(), because the Exception does not reach Spring framework. So how should I do in order to catch Exceptions from method_C() and at the same time do not lose capability of method method_B() be rollbacked?
2.Consider new method method_A().
public void method_A() {
for(...) {
...
...
method_B();
}
}
I want invoke method_B() in a loop. If an exception occurs in a method_B() I want transaction of method_B() be rollbacked but method_A() should not exit and the loop should continue excuting. How can I achieve this?
I solved my 2 problems this way: created another #Service class and moved method_B() into it. I've annotated this class as #Transactional. Now the method method_A() looks like this:
public void method_A() {
for(...) {
...
try {
anotherService.method_B();
} catch (Exception e) {
logger.error(...);
}
}
}
If RuntimeException occurs in the method_B() method, the exception is propertly logged, transaction of method_B() is rollbacked and the loop continuous. Thanks everybody for responses.
Instead of throwing exceptions do the following. (return error code).
Update: I read your question after posting. if you call method_b from method_A both are under same transaction. Unfortunately you cannot rollback the method_b changes alone. Spring considers it as one transaction if they are all under one service class. (all methods).
One thing you can try is the following.
request to--> Controller() ---> (spring opens transaction) service_method_a(); (spring closes transaction)
Controller() ---> (spring opens transaction) service_method_c(); (spring closes transaction)
Controller() ---> (spring opens transaction) service_method_b(); (spring closes transaction)
return <--
I hope it makes sense
Each of your methods a,b,c throw exceptions if it likes to be rolledback.
Update:
another approach. This one is much better.
If each of your method are in a different service then you can use the following annotations of the spring to run each of the method in a different transaction boundaries
p v serviceA{
#transactional
method_a(){
serviceb.method_b();
}
}
p v serviceB{
#Transactional(propagation=Propagation.REQUIRED)
method_b(){
}
}
more on it here
Spring transactional story here . Read the points below this article. Those are most important when developing the spring transactional app.
Consider I have following spring beans
CompositeService:
#Service
public class CompositeService {
#Resource
private ServiceA serviceA;
#Resource
private ServiceB serviceB;
public ResultBean compositeMethod() {
ResultBean result = new ResultBean();
result.setA(serviceA.getA());
result.setB(serviceB.getB());
return result;
}
}
ServiceA:
#Service
public class ServiceA {
#Transactional
#Cacheable
A getA() {
// calls DAO layer and makes a query to the database
}
}
ServiceB:
#Service
public class ServiceB {
#Transactional
#Cacheable
B getB() {
// calls DAO layer and makes a query to the database
}
}
The cacheable aspect has a higher order
The problem with this code is that it will start two transactions (and take two connections from the pool) in case of cache-miss in both services.
Can I configure Spring to use the same transaction in this use case?
I.E. propagate the transaction from ServiceA to the CompositeService and after that down to ServiceB?
I cannot set the CompositeService as transactional because I do not want to start a transaction (and borrow a connection from pool) in case of cache-hit both in ServiceA and ServiceB
Spring will only propagate a transaction if everything is under the same transaction. So the short answer is that you should annotate your CompositeService with #Transactional.
#Service
public class CompositeService {
#Transactional
public ResultBean compositeMethod() {
ResultBean result = new ResultBean();
result.setA(serviceA.getA());
result.setB(serviceB.getB());
return result;
}
}
Generally this is fast enough as it only does a checkout from the underlying connection pool. However if you experience latency or don't always need a connection you can wrap your actual DataSource in a LazyConnectionDataSourceProxy. This will obtain a Connection when first needed.
What you can do is you can annotate the compositeMethod with #Transactional as well. The default propagation level of a transaction is set to Required
Support a current transaction, create a new one if none exists.
So even though its not exactly what you asked, the transaction demarcation starts with the compositeMethod, but it should be exactly the semantics you want.