197

I am new to Spring Transaction. Something that I found really odd, probably I did understand this properly.

I wanted to have a transactional around method level and I have a caller method within the same class and it seems like it does not like that, it has to be called from the separate class. I don't understand how is that possible.

If anyone has an idea how to resolve this issue, I would greatly appreciate. I would like to use the same class to call the annotated transactional method.

Here is the code:

public class UserService {

    @Transactional
    public boolean addUser(String userName, String password) {
        try {
            // call DAO layer and adds to database.
        } catch (Throwable e) {
            TransactionAspectSupport.currentTransactionStatus()
                    .setRollbackOnly();

        }
    }

    public boolean addUsers(List<User> users) {
        for (User user : users) {
            addUser(user.getUserName, user.getPassword);
        }
    } 
}
catch23
  • 17,519
  • 42
  • 144
  • 217
Mike
  • 1,981
  • 2
  • 12
  • 4
  • 2
    Take a look at the `TransactionTemplate` approach: https://stackoverflow.com/a/52989925/355438 – Ilya Serbis May 05 '19 at 21:03
  • 3
    About why self invocation doesn't work, see [8.6 Proxying mechanisms](https://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch08s06.html). – Jason Law Nov 08 '19 at 09:33
  • @Mike, how you discovered that the transaction does not work? An exception is thrown? – Marco Sulla Jan 26 '23 at 13:34

9 Answers9

135

It's a limitation of Spring AOP (dynamic objects and cglib).

If you configure Spring to use AspectJ to handle the transactions, your code will work.

The simple and probably best alternative is to refactor your code. For example one class that handles users and one that process each user. Then default transaction handling with Spring AOP will work.


Configuration tips for handling transactions with AspectJ

To enable Spring to use AspectJ for transactions, you must set the mode to AspectJ:

<tx:annotation-driven mode="aspectj"/>

If you're using Spring with an older version than 3.0, you must also add this to your Spring configuration:

<bean class="org.springframework.transaction.aspectj
        .AnnotationTransactionAspect" factory-method="aspectOf">
    <property name="transactionManager" ref="transactionManager" />
</bean>
Community
  • 1
  • 1
Espen
  • 10,545
  • 5
  • 33
  • 39
  • Thank you for the information. I refactored the code for now, but could you please send me an example using AspectJ or provide me with some helpful links. Thanks in advance. Mike. – Mike Aug 09 '10 at 16:54
  • Added transaction specific AspectJ configuration in my answer. I hope it helps. – Espen Aug 10 '10 at 14:42
  • 15
    That's good! Btw: It would be nice if you can mark my question as the best answer to give me some points. (green checkmark) – Espen Aug 16 '10 at 18:31
  • 10
    Spring boot config : @EnableTransactionManagement(mode = AdviceMode.ASPECTJ) – VinyJones May 11 '20 at 10:03
  • 1
    Why AspectJ tis not default for handling transactions? – Alex78191 Oct 28 '21 at 08:11
  • @Alex78191 I know this is not a full answer to your question, but I had some very mysterious LazyInitializationExceptions in my codebase after adding the line mentioned by VinyJones, so it does not seem like a pure upgrade that can just be applied thoughtlessly. – julaine Oct 12 '22 at 15:08
100

In Java 8+ there's another possibility, which I prefer for the reasons given below:

@Service
public class UserService {

    @Autowired
    private TransactionHandler transactionHandler;

    public boolean addUsers(List<User> users) {
        for (User user : users) {
            transactionHandler.runInTransaction(() -> addUser(user.getUsername, user.getPassword));
        }
    }

    private boolean addUser(String username, String password) {
        // TODO call userRepository
    }
}

@Service
public class TransactionHandler {

    @Transactional(propagation = Propagation.REQUIRED)
    public <T> T runInTransaction(Supplier<T> supplier) {
        return supplier.get();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public <T> T runInNewTransaction(Supplier<T> supplier) {
        return supplier.get();
    }
}

This approach has the following advantages:

  1. It may be applied to private methods. So you don't have to break encapsulation by making a method public just to satisfy Spring limitations.

  2. Same method may be called within different transaction propagations and it is up to the caller to choose the suitable one. Compare these 2 lines:

    transactionHandler.runInTransaction(() -> userService.addUser(user.getUserName, user.getPassword));

    transactionHandler.runInNewTransaction(() -> userService.addUser(user.getUserName, user.getPassword));

  3. It is explicit, thus more readable.

Bunarro
  • 1,550
  • 1
  • 13
  • 8
  • 5
    This is great! It avoids all the pitfalls that Spring introduces with its annotation otherwise. Love it! – Frank Hopkins Jul 24 '20 at 14:45
  • If I extend `TransactionHandler` as a subclass, and the subclass calls on these two methods in the `TransactionHandler` super class, will I still be able to get the benefits of `@Transactional` as intended? – tom_mai78101 Jul 28 '20 at 14:23
  • 2
    Sounds wonderful! I wonder whether there are some caveats? – ch271828n Sep 04 '20 at 01:07
  • 1
    Excellent. I used this solution too, with a small difference: I named the methods in TransactionHandler `runInTransactionSupplier` and `runInNewTransactionSupplier`. This leaves open the possibility for adding later similar but void returning methods in TransactionHandler. – burebista Mar 11 '21 at 10:51
  • 3
    @burebista, you do it wrongly, it's possible to define two methods with same name, one of which accepts supplier and return T and other accept runnable and returns void. – roma2341 Jul 12 '21 at 09:15
  • Spring already has `TransactionTemplate` which is basically the same thing. – cambunctious Dec 22 '21 at 22:47
  • 1
    not exactly the same thing. transactionTempalte is to make it more manual. (which is not that bad by itself and more flexible) but it's still more verbose than just @Transactional annotation. – ses Jan 21 '22 at 19:34
  • Nice approach, especially since Spring Boot 2.6.x and > wants me to get rid of circular dependencies, which means I can't self-inject services anymore and call other transactional methods in the same service. Question, if one of the methds throws an Exception, I am unable to call it due to "no instance(s) of type variable(s) T exist so that void conforms to T" error. I tried adding a new method that throws, but not working – Geyser14 Jul 20 '22 at 23:08
  • same goes re: my previous comment for transaction methods that return void – Geyser14 Jul 20 '22 at 23:31
  • For what it's worth, the approach does not work in multithreading – Jarvis Sep 07 '22 at 13:01
  • anyone who wants to use in void returning function, use below. ``` @Transactional(propagation = Propagation.REQUIRED) public void runInTransaction(Runnable runnable) { runnable.run(); } ``` – Implermine Mar 24 '23 at 05:57
  • @Jarvis: What specific problems do you see with this approach in multithreading? – Honza Zidek Apr 27 '23 at 16:53
75

The problem here is, that Spring's AOP proxies don't extend but rather wrap your service instance to intercept calls. This has the effect, that any call to "this" from within your service instance is directly invoked on that instance and cannot be intercepted by the wrapping proxy (the proxy is not even aware of any such call). One solutions is already mentioned. Another nifty one would be to simply have Spring inject an instance of the service into the service itself, and call your method on the injected instance, which will be the proxy that handles your transactions. But be aware, that this may have bad side effects too, if your service bean is not a singleton:

<bean id="userService" class="your.package.UserService">
  <property name="self" ref="userService" />
    ...
</bean>

public class UserService {
    private UserService self;

    public void setSelf(UserService self) {
        this.self = self;
    }

    @Transactional
    public boolean addUser(String userName, String password) {
        try {
        // call DAO layer and adds to database.
        } catch (Throwable e) {
            TransactionAspectSupport.currentTransactionStatus()
                .setRollbackOnly();

        }
    }

    public boolean addUsers(List<User> users) {
        for (User user : users) {
            self.addUser(user.getUserName, user.getPassword);
        }
    } 
}
Kai
  • 759
  • 5
  • 2
  • 3
    If you do choose to go this route (whether this is good design or not is another matter) and don't use constructor injection, make sure you also see [this question](http://stackoverflow.com/questions/5152686/self-injection-with-spring) – Jeshurun Apr 11 '12 at 22:25
  • What if `UserService` has singleton scope? What if it is the same object? – Yan Khonski May 30 '19 at 11:11
40

With Spring 4 it's possible to Self autowired

@Service
@Transactional
public class UserServiceImpl implements UserService{
    @Autowired
    private  UserRepository repository;

    @Autowired
    private UserService userService;

    @Override
    public void update(int id){
       repository.findOne(id).setName("ddd");
    }

    @Override
    public void save(Users user) {
        repository.save(user);
        userService.update(1);
    }
}
Almas Abdrazak
  • 3,209
  • 5
  • 36
  • 80
  • 2
    BEST ANSWER !! Thx – mjassani Feb 28 '19 at 18:27
  • 7
    Correct me if I'm wrong but such a pattern is really error-prone, although it works. It's more like a showcase of Spring capabilities, right? Someone not familiar with "this bean call" behavior might accidentally remove the self-autowired bean (the methods are available via "this." after all) which might cause issue that are hard to detect at first glance. It could even make it to the prod environment before it was found). – pidabrow Jun 16 '20 at 14:43
  • 6
    @pidabrow you are right, it's a huge anti pattern and it's not obvious in the first place. So if you can you should avoid it. If you have to use method of the same class then try to use more powerful AOP libraries such as AspectJ – Almas Abdrazak Jun 16 '20 at 15:48
  • still looks artificial to me.. – ses Jan 21 '22 at 18:27
  • Defining a class as purely `@Transactional` can cause some unpredictable errors. Therefore, it should be used carefully. – fatih Jun 29 '22 at 20:34
7

This is my solution for self invocation:

public class SBMWSBL {
    private SBMWSBL self;

    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void postContruct(){
        self = applicationContext.getBean(SBMWSBL.class);
    }

    // ...
}
Ilya Serbis
  • 21,149
  • 6
  • 87
  • 74
Hlex
  • 833
  • 8
  • 16
  • Gives me org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'xy': Requested bean is currently in creation: Is there an unresolvable circular reference? – JRA_TLL Jul 14 '23 at 15:24
1

You can autowired BeanFactory inside the same class and do a

getBean(YourClazz.class)

It will automatically proxify your class and take into account your @Transactional or other aop annotation.

LionH
  • 194
  • 6
  • 3
    It is considered as a bad practice. Even injecting the bean recursively into itself is better. Using getBean(clazz) is a tight coupling and strong dependency on spring ApplicationContext classes inside of your code. Also getting bean by class may not work in case of spring wrapping the bean (the class may be changed). – Vadim Kirilchuk Sep 28 '15 at 08:48
1

Here is what I do for small projects with only marginal usage of method calls within the same class. In-code documentation is strongly advised, as it may look strange to colleagues. But it works with singletons, is easy to test, simple, quick to achieve and spares me the full blown AspectJ instrumentation. However, for more heavy usage I'd advice the AspectJ solution as described in Espens answer.

@Service
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
class PersonDao {

    private final PersonDao _personDao;

    @Autowired
    public PersonDao(PersonDao personDao) {
        _personDao = personDao;
    }

    @Transactional
    public void addUser(String username, String password) {
        // call database layer
    }

    public void addUsers(List<User> users) {
        for (User user : users) {
            _personDao.addUser(user.getUserName, user.getPassword);
        }
    }
}
Mario Eis
  • 2,724
  • 31
  • 32
0

The issue is related to how spring load classes and proxies. It will not work , untill you write your inner method / transaction in another class or go to other class and then again come to your class and then write the inner nested transcation method.

To summarize, spring proxies does not allow the scenarios which you are facing. you have to write the 2nd transaction method in other class

-1

There is no point to use AspectJ or Other ways. Just using AOP is sufficient. So, we can add @Transactional to addUsers(List<User> users) to solve current issue.

public class UserService {
    
    private boolean addUser(String userName, String password) {
        try {
            // call DAO layer and adds to database.
        } catch (Throwable e) {
            TransactionAspectSupport.currentTransactionStatus()
                    .setRollbackOnly();

        }
    }

    @Transactional
    public boolean addUsers(List<User> users) {
        for (User user : users) {
            addUser(user.getUserName, user.getPassword);
        }
    } 
}
  • This changes the original logic! Now adding the whole list of users runs in a single transaction, which is not what the OP intended! – Honza Zidek Apr 27 '23 at 16:50