Why use @Transactional with @Service instead of with @Controller
Asked Answered
L

4

26

I have seen many comments in stack-overflow articles I found certain things about either @Transactional use with @Service or with @Controller

"Usually, one should put a transaction at the service layer."

"The normal case would be to annotate on a service layer level"

"Think transactions belong on the Service layer. It's the one that knows about units of work and use cases. It's the right answer if you have several DAOs injected into a Service that need to work together in a single transaction." [Source]

Drawback to use @transactional with @service layer

If I had 2 methods for example saveUser() and saveEmail() (because I store the emails in a database to send them later - like a queue) I would create in my service a method saveUserAndSendEmail(User user) which would be transactional. [Source]

It means I create many methods in service layer instead of one Save Generic Method as follow

public <T> long save(T entity) throws DataAccessException {
    Session session = sessionFactory.getCurrentSession();
    long getGenVal=(Long) session.save(entity);
    return getGenVal;
}

According to the above solution , it means we have many methods like following LOL..

public <T> long saveAccount(T entity)....

public <T> long saveWithAuditLog(T entity, K entity1)....

public <T> long saveWithAuditLogAndEntries(T entity, K entity, M entity)....

OVERCOME this situation

I USE THE @Transactional in @Controller and Just make a Generic Save Method and save all the entities/ model using this simple save method. and if any method fail to save then all the transactions in controller rollback successfully.

Other situation that ensure that @Transactional should be use with @Controller

In @Controller:

pt.save(entity1);
pt.save(entity2);
int a = 2/0;
pt.save(entity3);

In case , @Transactional on Service, first 2 entity successfully saved but third not it not rollback all the transaction

In case , @Transactional on @Controller all the transaction rollback as exception occur

why stack-overflow asked , "Don't do transactions in your controller. Put them in your service layer classes."? [source]

Luca answered 28/8, 2013 at 21:1 Comment(2)
I think the best solution in the case is to create another level layer between the controller and the service. As i see it the controller only handles calls and prepare vars to handle and do logic on them. The service handle all the DB stuff. and the model which should be in between the controller and the service can do some logic. In your example doing few saves actions is a logic actions on data . which means put them in model and warp model with transactional if neededFan
Your saveUser() and saveEmail() methods should be in the DAO layer. Then you make you saveUserAndSendEmail(User user) method in the Service layer transactional.Abbottson
B
35

You are asking about best practice, and best practice is to mark @Transactional in the service layer because a @Controller should not be aware of data persistence in a MVC logic.
@Service is constructed on use-case generated from analysis and knows about unit of works and is also realized thinking in terms of reuse: if you switch from a web context to a desktop one (for example, or some other visual frontend) where @Controller layer doesn't exist you don't have problems because all is encapsulated in service layer.
A @Service is a contract and a modification in presentation layer should not require a rewrite of @Service code.
But Spring don't care about where you put your your transaction boundaries, you can put on @Controller but your application may will be harder to be maintained.

Behrens answered 28/8, 2013 at 21:51 Comment(0)
C
9

Just so others know, interface annotations are discouraged.

Spring recommends that you only annotate concrete classes (and methods of concrete classes) with the @Transactional annotation, as opposed to annotating interfaces. You certainly can place the @Transactional annotation on an interface (or an interface method), but this works only as you would expect it to if you are using interface-based proxies. The fact that Java annotations are not inherited from interfaces means that if you are using class-based proxies (proxy-target-class="true") or the weaving-based aspect (mode="aspectj"), then the transaction settings are not recognized by the proxying and weaving infrastructure, and the object will not be wrapped in a transactional proxy, which would be decidedly bad.

Colorific answered 10/2, 2016 at 3:4 Comment(0)
M
3

Controller is firmly in the view layer, which can be changed at any time. The service still owns units of work and should operate correctly regardless of which view is accessing. My answer here still stands.

Mckenney answered 28/8, 2013 at 23:36 Comment(1)
Its means I have to create many method for save such as saveAccount saveAccountAudit saveAccountAuditEntries ?Luca
T
2

I created an upper-layer service which uses other (non-transactional) services. And the upper-layer service is transactional.

@Service
public class Service1Impl implements Servcie1 {
    public Object method11(){...}
    public Object method12(){...}
}

@Service
public class Service2Impl implements Service2 {
    public method21(){...}
}

public interface Service1 {
    public Object method11();
    public Object method12();
}

public interface Service2 {
    public Object method21();
}

@Transactional
public interface UpperLayerService {}

@Service
public class UpperLayerServiceImpl implements UpperLayerService {
    @Autowired
    private Service2 service2;
    @Autowired
    private Service3 service3;

    public Object doWork() {
        Object o1 = service1.method11();
        Object o2 = service1.method12();
        Object o3 = service2.method21();
        ....
        Object res = null;//...Any code
        return res;
    }
}
Tiphani answered 20/11, 2015 at 14:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.