简体   繁体   中英

@Transactional in REST layer or in Service layer? Which is better?

In my case, I want to create multiple tags by invoking the method "createTags()". For testing convenience, I just use @GET and no params in this method, please don't mind it. And I want all the operations rollback if one of them throw an exception, eg if t3's tagValue is empty, t1, t2 shouldn't exist in my database. Obviously, I should use @Transactional here, so my question is that can I use @Transactional this way? I mean to use it in my REST layer. Here is the code:

TestREST.class

@Path("tag/create")
@GET
@Transactional(rollbackOn = {NoTagException.class, TagAlreadyExistedException.class}))
public void createTags() throws NoTagException, TagAlreadyExistedException {
    // create all categories
    Tag t1 = new Tag(TagType.CATEGORY);
    t1.setTagValue("Edu");
    tagService.createTag(t1);
    Tag t2 = new Tag(TagType.CATEGORY);
    t2.setTagValue("ZJNU");
    tagService.createTag(t2);
    Tag t3 = new Tag(TagType.CATEGORY);
    // the value is empty here so I hope all previous operations rollback
    t3.setTagValue("");
    tagService.createTag(t3);
}

TagService.class

public void createTag(Tag tag) throws NoTagException, TagAlreadyExistedException {
    if (tag.getType() == null || !(tag.getType() instanceof TagType)) {
        throw new NoTagException("tag's type must be set");
    } else if (tag.getTagValue() == null || tag.getTagValue().equals("")) {
        throw new NoTagException("tag's value must be set!");
    } else {
        Optional<Tag> existedTag = retrieveTagByTypeAndValue(tag.getType(), tag.getTagValue());
        if (existedTag.isPresent()) {
            throw new TagAlreadyExistedException("one or more tags are already existed!");
        } else {
            tagDAO.create(tag);
        }
    }
}

Or should I always use @Transactional in my Service layer? I change the above methods to this:

TestREST.class

@Path("tag/create")
@GET
public void createTags() throws NoTagException, TagAlreadyExistedException {
    // create all categories
    Set<Tag> tags = new HashSet<>();
    Tag t1 = new Tag(TagType.CATEGORY);
    t1.setTagValue("Edu");
    Tag t2 = new Tag(TagType.CATEGORY);
    t2.setTagValue("ZJNU");
    Tag t3 = new Tag(TagType.CATEGORY);
    t3.setTagValue("");
    tags.add(t1);
    tags.add(t2);
    tags.add(t3);
    tagService.createTags(tags);
}

TagService.class

@Transactional(rollbackOn = {NoTagException.class, TagAlreadyExistedException.class}))
public void createTag(Tag tag) throws NoTagException, TagAlreadyExistedException {
    // just the same as above
}

public void createTags(Set<Tag> tags) throws NoTagException, TagAlreadyExistedException {
    if (tags.isEmpty()) {
        throw new NoTagException("tag must be set");
    } else {
        for (Tag tag : tags) {
            createTag(tag);
        }
    }
}

They can all achieve what I expected. So, which approach should I choose? Why? And any suggestions that I can improve these methods? BTW, I use CDI @RequestScoped in TestREST.class and TagService.class Thanks for your help!

I feel this question is less about where to use Transactional, and more about how code is structured.

In the example, adding @Transactional to the rest layer will achieve the exact goal you want (save all tags or none based on error thrown). The question becomes, "where should the business logic exist?".

If, the rest endpoint is just a data collection method, and there's a service method that takes the data and attempts to save it, then the Transactional annotation should exist at that level.

There are two concepts I've found people much smarter than me using on a regular basis. Move the Transactional annotation down to the finest layer that it make sense; and, annotate interface methods over concrete methods. The latter doesn't apply here, per se (but I hope it helps you in the future), but the former should be your guide.

To reiterate, the question is less about where @Transactional should be placed.. It should be, "where will the call to save the tags be performed"; and the @Transactional will follow.

(Hope this helps)

通常,您希望在Service Layer找到@Transactional批注,因为它是了解其他工作单元的层,通常是您在例如RuntimeException管理异常的层。

When designing transaction for an application below one of transaction design pattern can be used based on architecture/type of an application

Client Owner Transaction Design Pattern Domain Service Owner Transaction Design Pattern Sever Delegate Owner Transaction Design Pattern

For most java enterprise application Domain Service Owner Transaction Design Pattern is used in which Domain component of an application manage transaction

When we want to move transaction responsibility to presentation layer Client Owner Transaction Design Pattern is used

Sever Delegate Owner Transaction Design Pattern is used when we want to leverage Command Pattern in an application

Client Owner is used when an application does not have aggregate service and several client to remote call is require to fulfill one business action. In your code you are calling same service multiple time to create tags in REST layer so there is not reason to place transaction responsibility in presentation layer. For your business requirement it is better to move transaction responsibility on middle layer using Domain Service owner pattern Also using Client owner patter put too much infrastructure responsibility in client side which increase server chatting. Presentation layer should only responsible for presenting data but Client Owner patter force presentation layer to maintain server side responsibility also it is not good .

Domain Service owner is commonly used pattern for most java based enterprise an application. In this pattern Domain component of your application ie Item, Order, Inventory and in your case Tag own the transaction responsibility. So for business requirement this is pattern which should be used

Sever Delegate Owner Transaction Design Pattern is used when using Command Pattern in application and it solves regards to Client Owner pattern. In command Pattern functionality is put in Command and sent to server for execution. Command Processor component in server side manage transaction.

One simple rule to manage an exception an efficiently in transaction is the layer/Component which is managing transaction should handle exception and do business operation for that an exception. Managing transaction includes starting transaction, committing transaction, and roll back transaction so all these three an operation should be put in component which owns transaction responsibility

I suggest to go through pdf version Java Transaction Design Strategies which i have uploaded in mine below git repo to learn in depth about transaction design pattern.

https://github.com/anurag1986/Transaction-Material

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM