I am doing a batch update into my DB table using NamedParameterJdbcTemplate.batchUpdate, but I would like to disable auto-commit and perform the commit manually.
I can set auto-commit mode off from the connection object, but not sure how to do the same using NamedParameterJdbcTemplate object.
I have done my implementation using TransactionTemplate
It has an execute method and I do the business logic inside a callback in this function.
transTemplate.execute( new TransactionCallbackWithoutResult()
{
#Override
protected void doInTransactionWithoutResult( TransactionStatus status)
{
status.setRollbackOnly();
//business logic
}
});
I assume you are aware of the transactional management in Spring where by defining #Transactional and passing metadata of Propagation and Isolation you can elegantly manage transactions. If not take a look at the Spring documentation. In most cases that's all you need.
If you want to get transaction management at your own hands and fine-tune it (aka perform commit and rollbacks at will) you have to get the underlying TransactionManager directly.
Quoting from the Spring docs:
Using the PlatformTransactionManager
You can also use the org.springframework.transaction.PlatformTransactionManager directly to manage your transaction. Simply pass the implementation of the PlatformTransactionManager you are using to your bean through a bean reference. Then, using the TransactionDefinition and TransactionStatus objects you can initiate transactions, roll back, and commit.
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can only be done programmatically
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
// execute your business logic here
}
catch (MyException ex) {
txManager.rollback(status);
throw ex;
}
txManager.commit(status);
Related
Let's say I have a #KafkaListener class with a #KafkaHandler method inside that processes any received messages and does some DB operations.
I want to have fine-grained control over how and when to commit (or rollback) the database changes (i.e. manually manage the DB transaction) in this class. The consumed message offset can be committed regardless of the DB transaction result.
Here is a simplified version of what I have:
#Service
#RequiredArgsConstructor
#KafkaListener(
topics = "${kafka.topic.foo}",
groupId = "${spring.kafka.consumer.group-id-foo}",
containerFactory = "kafkaListenerContainerFactoryFoo")
public class FooMessageConsumer {
// ...
private final EntityManager entityManager;
#KafkaHandler
public void handleMessage(FooMessage msg) {
// ...
handleDBOperations(msg);
// ...
}
void handleDBOperations(msg) {
try {
entityManager.getTransaction().begin();
// ...
entityManager.getTransaction().commit();
} catch (Exception e) {
log.error(e.getLocalizedMessage(), e);
entityManager.getTransaction().rollback();
}
}
}
When a message is received and entityManager.getTransaction().begin(); is invoked, this results in an exception:
java.lang.IllegalStateException: Not allowed to create transaction on shared EntityManager - use Spring transactions or EJB CMT instead
Why am I not allowed to create a transaction here?
And if I remove EntityManager and add #Transactional annotations to methods with DB operations (though this is not exactly what I want), then it results in another exception:
TransactionRequiredException Executing an update/delete query
It seems like it completely ignores the annotation. Is this somehow related to Kafka consumer having its own transaction management?
In short, what am I doing wrong here and how can I manage DB transactions in a #KafkaHandler method?
Any help is appreciated.
Thanks in advance.
Try using Springs TransactionTemplate:
https://docs.spring.io/spring-framework/docs/3.0.0.M4/reference/html/ch10s06.html
If your use case is (that) simple, Springs declarative transaction management should also let you achieve the behavior you ask for:
https://docs.spring.io/spring-framework/docs/3.0.0.M3/reference/html/ch11s05.html
It's not exactly as the title says, but close to. Consider these Spring beans:
#Bean
class BeanA {
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = EvilException.class)
public void methodA() {
/* ... some actions */
if (condition) {
throw new EvilException();
}
}
}
#Bean
class BeanB {
#Autowired private BeanA beanA;
final int MAX_TRIES = 3;
#Transactional(propagation = Propagation.NESTED)
public void methodB() {
// prepare to call Bean A
try {
beanA.methodA();
/* maybe do some more things */
}
catch (EvilException e) {
/* recover from evil */
}
}
}
#Bean
class MainWorkerBean {
#Autowired private BeanB beanB;
#Autowired private OtherBean otherBean;
#Transactional(propagation = Propagation.REQUIRED)
public void doSomeWork() {
beanB.methodB();
otherBean.doSomeWork();
}
}
Important note: I'm using JDBC transaction manager that supports savepoints.
What I'm expecting this to do is, when EvilException is thrown, the transaction of the BeanA is rolled back, which with this setup happens to be the savepoint created by starting methodB. However, this appears to not be the case.
When going over with debugging tools, what I'm seeing is this:
When doSomeWork of MainWorkerBean starts, new transaction is created
When methodB starts, transaction manager properly initializes a savepoint and hands it to TransactionInterceptor
When methodA starts, transaction manager sees Propagation.REQUIRED again, and hands out a clean reference to the actual JDBC transaction again, that has no knowledge of the savepoint
This means that when exception is thrown, TransactionStatus::hasSavepoint return false, which leads to roll back of the whole global transaction, so recovery and further steps are as good as lost, but my actual code has no knowledge of the rollback (since I've written recovery for it).
For now, I can't consider changing BeanA's transaction to Propagation.NESTED. Admittedly, looks like it's going to allow me to have the more local rollback, but it's going to be too local, because as I understand it, Spring then will have two savepoints, and only roll back the BeanA savepoint, not BeanB one, as I'd like.
Is there anything else I'm missing, such as a configuration option, that would make internal transaction with Propagation.REQUIRED consider that it is running inside a savepoint, and roll back to savepoint, not the whole thing?
Right now we're using Spring 4.3.24, but I already crawled through their code and can't spot any relevant changes, so I don't think upgrading will help me.
As described in this bug ticket: https://github.com/spring-projects/spring-framework/issues/11234
For spring versions < 5.0, in the situation described, the global transaction is set to 'rollback-only'.
In this transaction I am processing several tasks. If an error should occur during a single task, I do not want the whole transaction to be rolled back, therefore I wrap the task processing in another transaction boundary with a propagation of PROPAGATION_NESTED.
The problem comes when, during task processing, calls are made to existing service methods defined with a transaction boundary of PROPAGATION_REQUIRED. Any runtime exceptions thrown from these methods cause the underlying connection to be marked as rollback-only, rather than respecting the current parent transaction nested propagation.
[...]
As of Spring Framework 5.0, nested transactions resolve their rollback-only status on a rollback to a savepoint, not applying it to the global transaction anymore.
On older versions, the recommended workaround is to switch globalRollbackOnParticipationFailure to false in such scenarios.
However, even for Spring5, I noticed when reproducing the problem, that the nested transaction may be rolled back, including all things done in the catch block of methodB(). So your recover code might not work inside methodB(), depending on what your recovery looks like. If methodA() was not transactional, that would not happen. Just something to watch out for.
Some more details to be found here: https://github.com/spring-projects/spring-framework/issues/8135
In Spring TransactionSynchronization interface it has methods (in order of execution):
- beforeCommit
- beforeCompletion
- afterCommit: Can perform further operations right after the main transaction has successfully committed.
- afterCompletion
Why Spring doesn't have rollback methods, such as beforeRollback or afterRollback but it has for commit only (beforeCommit and afterCommit)? Will this is necessary? Can anyone give me some advices or explains about this?
If I want to continue further operations that are supposed to follow on a successful rollback of the main transaction, like notification messages or emails what should I do in this case?
#Override
public void afterCompletion(int status) {
if (status == TransactionSynchronization.STATUS_ROLLED_BACK) {
logger.trace("Rolled back...");
}
}
Can I ask why you are using this interface?
This is used for callbacks by the PlatformTransactionManager which is in charge of managing the transaction lifecycle.
The "normal" use of Spring Transactions is to utilise #Transactional annotations, aop declarations in xml or TransactionTemplate / PlatformTransactionManager programatically to set the transaction scope, behaviour and visibility, as described in the docs - here.
To manage a transaction behaviour for rollbacks etc you just tell spring what to do in the event of an Exception
public class Foo {
#Autowired public Service someService;
#Transactional(propagation = Propagation.REQUIRES_NEW,
noRollbackFor = {IOException.class})
public boolean bar(SomeObject someObject) throws IOException {
someService.doComplicatedThing(someObject.getValue());
}
}
This tells the PlatformTransactionManager to start a new TX when hitting the foo() method, commit on successful return or an IOException, and rollback if there is some other type of exception. This is the genius of it - you dont need to worry about littering your code with getTransaction().isActive() and complicated checks for managing the isolation.
Does Spring issue a commit to the database after each package call, or does it only commit after everything is done?
I am using Spring and Struts. In my controller class, I am calling many database packages with spring stored procedure in a DAO class. each call has its only method in the DAO class
My question is, will Spring commit only after all calls have been made from the controller class and the controller has finished, or will it commit after each execution of a SpringStoredProcedure?
my_package_getusers = SpringStoredProcedure.getStoredProcedureCompiled(getJdbcTemplate()
....
my_package_getusers.execute(params).get("result")
Spring utility classes from spring-jdbc don't make any commits. The responsibility for commiting transaction belongs to the spring-tx (transaction support.
This is usually made by placing #Transactional annotation on the method that should be run in transaction scope. So if you have method
#Transactional
public void doSomething(SomeClass arg) {...}
entering that method will create new transaction, and that transaction will be commited after leaving the transaction, if the transactional context doesn't exist. However, if that method is called from other method annotated with #Transactional, the parent transaction context will be used.
As alternative, you can use org.springframework.transaction.support.TransactionTemplate to gain more control over transactions (for example, launching separate transaction from transactional context).
Example:
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
#Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// do something in trancation)
}
});
Note, that on some databases, such as Oracle, you can commit transaction within stored procedure, which will persist all changes made in existing transaction, and the new transaction will follow.
Application is based on Spring 2.5.5 and hibernate 3.2.0 GA.
I have the following method in my DAO that gets MessageEntities attached to the specified User:
public MessageEntity findByUserId(int userId) {
List<MessageEntity> result = (List<MessageEntity>) em.createNamedQuery(MessageEntity.Q_BY_USER_ID).setParameter("userId", userId).getResultList();
if (!result.isEmpty()) {
return result.get(0);
} else {
return null;
}
}
I need to call this method from my integration test to check whether system's behaviour is valid. As long as this method is not transactional, all I get is org.hibernate.SessionException: Session is closed!. The easiest way to avoid this is to mark findByUserId method with #Transactional(readOnly = true). But as I understand, transaction management should be the duty of service tier to avoid unnecessary transactions creation. So, my question is: how can I properly get away from SessionException?
You need to perform all your database actions within a transaction scope. As you identified its usually considered good design to let the service layer of your database model deal with transactions. The only constraint then becomes that you must invoke your service model to get within the transaction scope, which might be undesirable during test.
I would recommend to make use of the testing fascilites provided by spring. See 9.3.2.3 Transaction management
You could also manually create a transaction before testing your method, e.g., by
Session sess = factory.openSession();
Transaction tx = null;
// try catch
tx = sess.beginTransaction();
findByUserId(userId);
tx.commit();
tx.rollBack();
Put the following annotations on the top of your test class.
#RunWith(SpringJUnit4ClassRunner.class)
#Transactional
#ContextConfiguration(locations = "classpath:/META-INF/spring/applicationContext.xml")
Also I wouldn't worry about putting additional #Transactional in DAOs.
Spring usually checks to see if you are already in a transaction (with in the same thread) before it creates another.
"But as I understand, transaction
management should be the duty of
service tier to avoid unnecessary
transactions creation."
This is more of a design choice (Spring Roo for example violates this)
You can use this annotation on your controller method:
#Transactional(readOnly = true)