aime
aime

Reputation: 247

Handling exception in Service layer

Could you please advice how to overcome the problem which is described below:

There is an application (it involves using Hibernate and Spring frameworks) with 3 layers: DAO, Service and Controller. I want to avoid saving duplicate entities in DB. I made it by using constraints at DB-level and in annotation in bean (@Table(name = "artist", uniqueConstraints = {@UniqueConstraint(columnNames = "artist_name")})). The logic supposes that if I try to add duplicate entity an exception is thrown. I try to process exception in service layer but it fails to succeed. I mean that even when I explicitly notice all the exception which are thrown an application can't proceed to work because of exceptions. To be more clear I cite the code below:

DAO-layer

@Override
public void saveEntity(Client client) {
    Session session = sessionFactory.getCurrentSession();
    session.save(client);
}

Service-layer

@Transactional
@Override
public boolean saveEntity(Artist entity) {
    boolean completedState = false;
    try {
        //method from DAO-layer
        artistDAO.saveEntity(entity);
    } catch (ConstraintViolationException | UnexpectedRollbackException e) {
        return completedState;
    }
    return completedState = true;
}

Controller

@RequestMapping(value="addArtist", method=RequestMethod.POST)
public String processNewArtistForm(@ModelAttribute Artist artist, Model model) {
//If duplicated artistName prompts to be saved an saveEntity(entity) returns "false". Entity is failed to save.
    boolean completedState = false;
    completedState = artistService.saveEntity(artist);
    if (completedState == true) {
        List<Artist> listOfArtists = artistService.getListOfEntities();
        model.addAttribute("listOfArtists", listOfArtists);
    } else {
        model.addAttribute("errorMessage", "An artist under present name already exists");
        return "newArtist";
    }
    return "listOfArtists";
}

Tomcat's notification when I try to save duplicate entity

Type Exception report

message Request processing failed; nested exception is org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

description The server encountered an internal error that prevented it from fulfilling this request.

exception

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978)
    org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:868)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:644)
    org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
root cause

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
    org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:720)
    org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:521)
    org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:291)
    org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
    com.sun.proxy.$Proxy29.saveEntity(Unknown Source)
    web.ArtistsListController.processNewArtistForm(ArtistsListController.java:66)
    sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    java.lang.reflect.Method.invoke(Method.java:606)
    org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
    org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
    org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:777)
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:706)
    org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:943)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:877)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:966)
    org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:868)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:644)
    org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
note The full stack trace of the root cause is available in the Apache Tomcat/8.0.9 logs.

Could you please advice if these architectural solution is correct or not? thank you in advance.

Upvotes: 4

Views: 3598

Answers (3)

aime
aime

Reputation: 247

one of the solution - is to process exception in controller. Also (frankly, I still can't understand why it works) I could just flush the session.

Upvotes: 0

Wabi
Wabi

Reputation: 96

By default, @Transactional annotation results in a transaction which rollbacks on any RuntimeException (see the doc).

Use @Transactional(noRollbackFor = {ConstraintViolationException.class}) to change the behaviour.

Upvotes: 0

Alex Chernyshev
Alex Chernyshev

Reputation: 1745

Why not to check for duplications before saving?:

@Transactional
@Override
public void saveEntity(Client client) {
    Session session = sessionFactory.getCurrentSession();
    // session.get() will always query the database and will return null
    // if no rows found
    if (session.get(Client.class,client.getId()==null) {
    session.save(client);
    //flush session cache
    session.flush();
     }
}

Upvotes: 1

Related Questions