ARTICLE AD BOX
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.
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>6 Comments
In Java 8+ there's a 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:
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.
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));It is explicit, thus more readable.
14 Comments
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.
2021-03-11T10:51:47.537Z+00:00
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); } } }2 Comments
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
2012-04-11T22:25:50.607Z+00:00
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); } }6 Comments
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).
2020-06-16T14:43:06.26Z+00:00
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); } // ... }22.5k8 gold badges92 silver badges78 bronze badges
2 Comments
Tested with Spring 2.5.15, you do a self invocation using @Resource. See the example bellow
@Service @RequiredArgsConstructor public class Example { private final EntityManager entityManager; @Resource private Example self; @Transactional(propagation = Propagation.REQUIRES_NEW) public void testSelf(){ self.transaction(); } @Transactional(propagation = Propagation.SUPPORTS) public void transaction(){ entityManager.persist(new MyEntity(28L)); }Test class:
@SpringBootTest class ExampleTest { @Autowired private Example example; @Autowired private EntityManager entityManager; @Test void testSelf(){ transactionHandler.testSelf(); assertNotNull(entityManager.find(Estado.class, 28L)); } }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.
1 Comment
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).
2015-09-28T08:48:18.41Z+00:00
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); } } }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
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); } } }Explore related questions
See similar questions with these tags.







