Using Spring #Transactional in a method with multiple operations - java

I have a service which runs every few seconds and processes some records from a queue which is stored in the database. I noticed that a #Transactional attribute added and was trying to figure out what it's usage is and wanted to make sure it's the right usage.
#Scheduled(initialDelay = 10000, fixedDelay = 10000)
#Transactional
public void notificationTask() {
Integer txId = myDao.getMinTxIdByStatusCode("Pending");
myDao.updateStatusByTxId(txId, "Processing");
boolean status = true;
Transaction tx = myDao.getTxById(txId);
if (txId != null) {
status = myService.processTx(txId, myTx.getX(), myTx.getY());
if (status) {
myDao.updateStatusByTxId(txId, "Complete");
} else {
myDao.updateStatusByTxId(txId, "Failed");
}
}
}
My main question here is, if for some reason either the getTxById or the processTx methods fail, does that mean with the #Transational attribute, it will roll back the updateStatusByTxId? What is a use case where the Transactional attribute would be used here?

When you use #Transactional on a method, Spring wraps the entire method in a transaction. This means that a new transaction is started before the method is entered and committed after the method is exited.
If there is an exception, the whole transaction will be rolled back. That's what is called "atomicity": transactions are "all or nothing".
Thus, #Transactional is a "shortcut" for something like this when using Hibernate:
#Scheduled(initialDelay = 10000, fixedDelay = 10000)
public void notificationTask() {
Transaction transaction = null;
try {
Session session = sessionFactory.openSession(); //Hibernte session
transaction = session.beginTransaction();
//... your stuff
Integer txId = myDao.getMinTxIdByStatusCode("Pending");
myDao.updateStatusByTxId(txId, "Processing");
boolean status = true;
Transaction tx = myDao.getTxById(txId);
if (txId != null) {
status = myService.processTx(txId, myTx.getX(), myTx.getY());
if (status) {
myDao.updateStatusByTxId(txId, "Complete");
} else {
myDao.updateStatusByTxId(txId, "Failed");
}
}
//... your stuff done
transaction.commit();
}catch (Exception ex) {
ex.printStackTrace();
transaction.rollback();
}
}
So, if your method updateStatusByTxId is just a database call (is not flushing, nor committing or has is own nested transaction): yes, it is rolled back.
And since the transaction is also active, when entering processTx, this service call will also be in the same transaction, even if you add an additional #Transactional to processTx. Except you explicitly start a new transaction by e.g. #Transactional(Transactional.TxType.REQUIRES_NEW).
Additionally, Spring has a really detailed doc:
https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#transaction

Related

Proper practice to check table then save / update for Hibernate

All applications using hibernate need save and update to interact with the database. For save, I will check the existence for some criteria. If it doesn't exist, I will save. For update, I will check the existence and certain criteria to determine whether update or not. What is the best practice to do the check and save / update?
I am currently creating a separate function that open a session and search to determine the existence. The session open/close is very clumsy. I think there should be better way to do it.
public Event searchByDateAddress(Date _date, String _address, boolean _closeSess)
{
try
{
if(!session.isOpen())
{
session = HibernateUtil.getSessionFactory().openSession();
}
session.beginTransaction();
Criteria criteria = session.createCriteria(Event.class);
criteria.add(Restrictions.eq("date", _date));
criteria.add(Restrictions.eq("address", _address));
criteria.setFetchMode("organizerProfile", FetchMode.JOIN);
Event evt = (Event)criteria.uniqueResult();
if(_closeSess)
{
session.close();
}
if (evt==null)
{
LogUtils.logInfo("The event does not exist: " + _date + " " + _address);
return null;
}
else
{
return evt;
}
}
catch(Exception e)
{
LogUtils.logInfo(e.toString());
if(_closeSess)
{
session.close();
}
return null;
}
}
public EventDTO insertEvent(Event _event)
{
try
{
Event tmpEvent=new Event();
//Event not exists
if((tmpEvent=this.searchByDateAddress(_event.getDate(), _event.getAddress(), true))==null)
{
//insert
if(!session.isOpen())
{
session = HibernateUtil.getSessionFactory().openSession();
}
Transaction tx=session.beginTransaction();
long retOid=(Long)session.save(_event);
session.flush();
tx.commit();
session.close();
_event.setOid(retOid);
return new EventDTO(_event);
}
}
catch(Exception e)
{
e.printStackTrace();
session.close();
}
return new EventDTO();
}
Thanks
Regarding session handling, its best to handle in the API level, a leve before this "EventService". So you always assume that the session is available in the service methods.
Then the code will look more neet, avoiding session with the hibernate query.
Its also possible to avoid session handling at all using #Transactional attribute in the methods where you want session. This is possible using Spring. Look at : https://spring.io/guides/gs/managing-transactions/ for more info.
For checking for if a table has the data you with your where clause you may use count(*). So you dont need to get all the data at first.
Look at this example: hibernate native query, count

How to handle reversal with JPA?

I am using Spring and JPA for a financial data handling project. I have to handle scheduling transactions for future dates. In a daily run quartz cron job and execute all schedule transaction and persist to real table.
My problem is, when going to execute trigger for a given time, one record failed due to some reason, then all other records were not executed.
I need to execute all other transactions and failed transactions should rollback.
Is there a way to handle these things?
following code block get all the schedule job
public void bankingPaymentSchedullerRun(){
// get all pending job
List<BankScheduler> bankSchedulers = bankSchedulerDao.findByStatus(ScheduleStatus.PENDING);
Calendar currentDate = DateUtil.getFormatedCalenderDate(DateUtil.currentDate(), "yyyy-MM-dd");
if (bankSchedulers != null) {
for (BankScheduler bankScheduler : bankSchedulers) {
LOGGER.info("bankingPaymentSchedullerRun " + bankScheduler.toString());
//compare from date and to date
if ((bankScheduler.getFromDate().compareTo(currentDate) <= 0)
&& (bankScheduler.getToDate().compareTo(currentDate)) >= 0) {
if (bankScheduler.getSchedulerType().equals(SchedulerType.FUND_TRANSFER.toString())) {
scheduleFundTransfer(bankScheduler);
}else {
scheduleUtilityPayment(bankScheduler);
}
}
}
}
}
Fund transfer execution code block
public PaymentResponse scheduleFundTransfer(final BankScheduler bankScheduler){
FundTransfer fundTransfer = new FundTransfer();
fundTransfer.setBranchName(bankScheduler.getBankSchedulerFundTransfer().getBeneficiaryBranchName());
fundTransfer.setBeneficiaryBankName(bankScheduler.getBankSchedulerFundTransfer().getBeneficiaryBankName());
fundTransfer.setBeneficiaryType(bankScheduler.getBankSchedulerFundTransfer().getBeneficiaryType());
fundTransfer.setBeneficiaryName(bankScheduler.getBankSchedulerFundTransfer().getBeneficiaryName());
fundTransfer.setCurrency(bankScheduler.getBankSchedulerFundTransfer().getCurrency());
fundTransfer.setNarration(bankScheduler.getDetail());
fundTransfer.setThirdPartyType(bankScheduler.getBankSchedulerFundTransfer().getThirdPartyType());
fundTransfer.setTransferedAmount(bankScheduler.getBankSchedulerFundTransfer().getAmount());
fundTransfer.setUserAccountNumber(bankScheduler.getBankSchedulerFundTransfer().getAccountNumber());
fundTransfer.setUserName(bankScheduler.getBankSchedulerFundTransfer().getUserName());
FundTransfer fundTransferUpdate = null;
//commit to fundtransfer real table
try {
fundTransferUpdate = fundTransferDao.create(fundTransfer);
} catch (Exception e) {
LOGGER.error("Exception occur when call update fund transfer in fundTransfer()", e);
}
// getting from currecy Decimals
BankCurrecyInfor currecyInfo =
bankCurrecyInforDao.getDecimalpointsByCurrecy(fundTransfer.getUserAccount().getCurrencyCode());
fundTransferUpdate.setDecimalAmt(String.valueOf(currecyInfo.getNoOfDecimal()));
//call to bank back-end to update
PaymentResponse response = accountServiceInvoker.fundTransfer(fundTransferUpdate);
//for sending sms
SmsCriteria smsCriteria = new SmsCriteria();
smsCriteria.setBank_name("ABC");
messageServiceInvoker.sentIbSms(smsCriteria);
return response;
}
Yes there is, use proper exception handling so when single transaction fails, other will still (try to) execute.
Pseudocode
for(scheduled task from all scheduled tasks) {
try{
begin transaction
do your stuff with jpa
commit transaction
}catch(Exception e){
rollback transaction, log error and stuff
}finally{
release resources
}
}

Hibernate org.hibernate.TransactionException: nested transactions not supported on jaxrs

I am using jersey with mysql and hibernate 4 and c3p0. I have created a initialization servlet that configures hibernate and sets the current session context class to thread.
I have created hibernateUtils class that contains static methods for getting and committing sessions and I am using a filter to start a session on inbound request and commit it upon response.
The problem is that at some random intervals I am getting org.hibernate.TransactionException: nested transactions not supported exception, but I am not trying to create a new session except on the filter.
Correct me if I am wrong but when setting the current session class to thread I don't need to create a threadlocal in the hibernateutil, hibernate does this. So my question is, is this a safe way to handle that and what could cause the error happening on random intervals?
====================== EDIT ===========================
Sorry for not posting the code earlier.
So the filter implements the ContainerRequestFilter,ContainerResponseFilter
in the request filter I am doing
Session session = sessionfactory.getCurrentSession();
session.getTransaction().begin();
session.setDefaultReadOnly(readOnly);
and in the response
Transaction transaction = sessionfactory.getCurrentSession().getTransaction();
try {
if (transaction != null && !transaction.wasCommitted()
&& !transaction.wasRolledBack() && transaction.isActive()) {
transaction.commit();
}
} catch (HibernateException e) {
Transaction transaction = sessionfactory.getCurrentSession().getTransaction();
try {
if (transaction != null && transaction.isActive()) {
transaction.rollback();
}
} catch (HibernateException e) {
} finally {
Session session = sessionfactory.getCurrentSession();
try {
if (session != null && session.isOpen()) {
session.close();
}
} catch (HibernateException e) {
log.error("Closing session after rollback error: ", e);
throw e;
}
}
It seems that you are using programmatic transaction demarcation in your filter (as far as I understood). So please double check that you terminate properly each transaction, nevermind what append during the request (i.e. rollback if you get an exception and commit otherwise) :
try {
session.getTransaction().begin();
// call the filter chain
session.getTransaction().commit()
}
catch (RuntimeException e) {
session.getTransaction().rollback();
}
Without the code it's difficult to be sure, but I guess that for some request you didn't terminate the transaction properly (i.e. by a commit or by a rollback). So the transaction remains associated to the thread and the thread go back to the thread pool (in a very strange state since there is still a transaction associated with it), then another request reuse the same thread, a new transaction is created in the filter... and you got the exception.
EDIT
After looking carrefully at your code, it (may) confirms my assumptions.
Look at the flow when transaction.wasRolledBack()==true : it won't be commited nor rolled back.
And if you the javadoc for Transaction.wasRolledBack() :
Was this transaction rolled back or set to rollback only?
If the transaction is marked as "RollBack only" : it will return true, but it don't means that the transaction is ended. It means that the only possible ending state for the transaction is "RollBack".
But, on the other hand the same javadoc also say this:
Returns: boolean True if the transaction was rolled back via this local transaction; false otherwise.
I found that ambiguous.
So I suggest you to do this:
if (transaction != null && !transaction.wasCommitted()
&& !transaction.wasRolledBack() && transaction.isActive()) {
transaction.commit();
}else if(transaction.wasRolledBack()){
transaction.rollback();
}

How to Encapsulate Transaction Pattern in Java

I'm trying to write DAOs for my database models using the transaction pattern like such,
Session session = null;
Transaction tx = null;
try{
session = HibernateUtil.getSessionFactory().openSession();
tx = session.beginTransaction();
tx.setTimeout(5);
//doSomething(session);
tx.commit();
}catch(RuntimeException e){
try{
tx.rollback();
}catch(RuntimeException rbe){
log.error("Couldn’t roll back transaction", rbe);
}
throw e;
}finally{
if(session!=null){
session.close();
}
}
What's a good approach to encapsulate this pattern in a method with
//doSomething(session);
as an argument to be performed as part of the transaction? Sometimes I run a query, sometimes I operate on session.saveOrUpdate, etc. I have many DAOs to write and this pattern of code duplication is bothering me.
EDIT
Is there a direct mapping between session operations and HQL (saveOrUpdate, delete, etc) so all I need to pass into this method is just a query?
Thanks for the insights.
Something like this might be what you're after
public void doSomething(MyQuery myQuery) {
...
Transaction tx = null;
try {
...
myQuery.execute(tx);
...
} catch (...) {
} finally {
}
}
public class MyQuery {
public void execute(Transaction tx) {
// run queries on transaction
}
}
Either create a new MyQuery instance or a new MyQuery subclass for each query or set of queries you want to execute

Hibernate: Do you need to manually close with sessionFactory?

I have way too many threads being used. I keep running out of memory in my unit tests. Do I need to close my session if I'm using sessionFactory. Won't the commit below end the session?
Session session = sessionFactory.getCurrentSession();
Transaction transaction = null;
try
{
transaction = session.beginTransaction();
transaction.commit();
}
catch (Exception e)
{
if (transaction != null)
{
transaction.rollback();
throw e;
}
}
finally
{
//Is this close necessary?
session.close();
}
No, it won't end the session. One session can span any number of transactions. Close the session explicitly. BTW such things are clearly documented.
In yout catch, verify if the transaction isActive() too.

Categories