I have a question. I use Spring+Hibernate and I can't handle exceptions in methods marked as @Transactional. Previously, when I used Spring JDBC everything worked just fine.
DAO classes are marked with @Repository.
Here is snipper of my old code.
in service:
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW, rollbackFor = {Exception.class})
public boolean bookTickets(Integer userId, List<Integer> ticketsId) throws TicketsServiceException {
Integer currId = null;
try {
for (Integer ticketId : ticketsId) {
currId = ticketId;
Ticket ticket = ticketDAO.getTicketById(ticketId);
bookingDAO.bookTicket(ticket, userId);
}
} catch (DuplicateKeyException e) {
throw new TicketsServiceException(String.format(MESSAGE_TICKET_ALREADY_BOOKED, currId));
} catch (EmptyResultDataAccessException e) {
throw new TicketsServiceException(String.format(MESSAGE_NO_SUCH_TICKET, currId));
}
return true;
in dao:
@Transactional(propagation = Propagation.MANDATORY)
public void bookTicket(Ticket ticket, final int userId) {
MapSqlParameterSource map = new MapSqlParameterSource();
map.addValue(Constants.TABLE_BOOKING.FIELD_TICKET_ID, ticket.getId());
map.addValue(Constants.TABLE_BOOKING.FIELD_USER_ID, userId);
int rowsAffected = template.update(QUERY_INSERT_BOOKING, map);
if (LOG.isTraceEnabled()) {
LOG.trace("Affected " + rowsAffected + " rows.");
}
}
Now I'm moving my DAO from Spring JDBC onto hibernate 3.
That's what I have now.
in service:
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW, rollbackFor = {Exception.class})
public boolean bookTickets(Integer userId, List<Integer> ticketsId) throws TicketsServiceException {
Integer currId = null;
try {
for (Integer ticketId : ticketsId) {
currId = ticketId;
Ticket ticket = ticketDAO.getTicketById(ticketId);
bookingDAO.bookTicket(new Booking(ticketId, userId));
}
} catch (Exception e) {
throw new TicketsServiceException(String.format(MESSAGE_THERE_IS_NO_SUCH_TICKET_OR_ALREADY_BOOKED, currId));
}
return true;
}
in dao:
@Override
public void bookTicket(Booking booking) {
getHibernateTemplate().save(booking);
}
The problem is that I can't handle exceptions inside try-catch block in service method. They are throwing directly into my controller.
I think the problem is in difference between transaction managers org.springframework.jdbc.datasource.DataSourceTransactionManager in Spring JDBC and org.springframework.orm.hibernate3.HibernateTransactionManager in hibernate, but I dont have any ideas how to overcome this problem, so I need your help.
AFTER UPDATE. ADDED EXAMPLE OF EXCEPTION.
2013-10-21 14:51:17 ERROR BookingController:89 - Unhandled exception
org.springframework.dao.DataIntegrityViolationException: Could not execute JDBC batch update; SQL [insert into booking (ticket_id, user_id, booking_id) values (?, ?, ?)]; constraint ["CONSTRAINT_INDEX_2 ON PUBLIC.BOOKING(TICKET_ID) VALUES ( /* key:1 */ 2, 1, 1)"; SQL statement:
insert into booking (ticket_id, user_id, booking_id) values (?, ?, ?) [23505-173]]; nested exception is org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
at org.springframework.orm.hibernate3.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:643)
at org.springframework.orm.hibernate3.HibernateTransactionManager.convertHibernateAccessException(HibernateTransactionManager.java:793)
at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:664)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:755)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:724)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:475)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:270)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:631)
at cdp.tarasenko.springmvc.task3.service.TicketsService$$EnhancerByCGLIB$$fabdc899.bookTickets(<generated>)
at cdp.tarasenko.springmvc.task3.controller.BookingController.bookTickets(BookingController.java:54)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:219)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:745)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:686)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:936)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:838)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:755)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:812)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:848)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:594)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:486)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:119)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:524)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:233)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1065)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:413)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:192)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:999)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:117)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:250)
at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:149)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:111)
at org.eclipse.jetty.server.Server.handle(Server.java:351)
at org.eclipse.jetty.server.AbstractHttpConnection.handleRequest(AbstractHttpConnection.java:454)
at org.eclipse.jetty.server.AbstractHttpConnection.content(AbstractHttpConnection.java:900)
at org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.content(AbstractHttpConnection.java:954)
at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:857)
at org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:235)
at org.eclipse.jetty.server.AsyncHttpConnection.handle(AsyncHttpConnection.java:77)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:609)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:45)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:599)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:534)
at java.lang.Thread.run(Thread.java:662)
Caused by: org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:71)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:253)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:237)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:141)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:298)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:338)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:656)
... 51 more
Caused by: org.h2.jdbc.JdbcBatchUpdateException: Нарушение уникального индекса или первичного ключа: "CONSTRAINT_INDEX_2 ON PUBLIC.BOOKING(TICKET_ID) VALUES ( /* key:1 */ 2, 1, 1)"
Unique index or primary key violation: "CONSTRAINT_INDEX_2 ON PUBLIC.BOOKING(TICKET_ID) VALUES ( /* key:1 */ 2, 1, 1)"; SQL statement:
insert into booking (ticket_id, user_id, booking_id) values (?, ?, ?) [23505-173]
at org.h2.jdbc.JdbcPreparedStatement.executeBatch(JdbcPreparedStatement.java:1167)
at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:48)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:246)
... 59 more
Is there any ways to handle exceptions inside service layer?
Thank you.
Take a look of hibernate FlushMode
Default HiberanateTemplate
FlushMode
is FlusMode.AUTO
, so session synchronization will occur on commit or before some queries to prevent stale state.
You could change HibernateTemplate flush mode to FlushMode.ALWAYS, inefficient and not recomended (but will work as you expect now), or call Session.flush() at some points.
see HiberanateTemplate.setFlushMode()
and Session.flush()
You don't get DB exceptions like constraint violations before committing the TX and TX gets committed in transaction manager. The best solution would be handling TX manager exceptions using an afterThrowing
aspect (AOP). Alternatively, you can call your @Transactional
methods inside another non-transactional method in your service and have it catch the exceptions. Personally, I don't like the later as you will have to create a wrapper method for all your services.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With