@Transactional on @PostConstruct method
Asked Answered
H

8

51

I want to read text data fixtures (CSV files) at the start on my application and put it in my database.

For that, I have created a PopulationService with an initialization method (@PostConstruct annotation).

I also want them to be executed in a single transaction, and hence I added @Transactional on the same method.

However, the @Transactional seems to be ignored : The transaction is started / stopped at my low level DAO methods.

Do I need to manage the transaction manually then ?

Hexose answered 27/6, 2013 at 15:1 Comment(1)
This approach worked for me: https://mcmap.net/q/102416/-transaction-management-for-starter-bean-in-spring-4-hibernateSphygmoid
P
97

Quote from legacy (closed) Spring forum:

In the @PostConstruct (as with the afterPropertiesSet from the InitializingBean interface) there is no way to ensure that all the post processing is already done, so (indeed) there can be no Transactions. The only way to ensure that that is working is by using a TransactionTemplate.

So if you would like something in your @PostConstruct to be executed within transaction you have to do something like this:

@Service("something")
public class Something {
    
    @Autowired
    @Qualifier("transactionManager")
    protected PlatformTransactionManager txManager;

    @PostConstruct
    private void init(){
        TransactionTemplate tmpl = new TransactionTemplate(txManager);
        tmpl.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                //PUT YOUR CALL TO SERVICE HERE
            }
        });
   }
}
Pericranium answered 13/9, 2013 at 15:53 Comment(7)
Is there anyway we can intercept the container's "post processing done" event so that we know transaction is now available?Forceps
@Forceps I believe that such a thing could be achieved via use of Spring's ApplicationListener which allows for listening on various events including Context lifecycle events.Pericranium
@Forceps Specifically ContextRefreshedEvent should satisfy your needs. Per documentation: "... all beans are loaded, post-processor beans are detected and activated, singletons are pre-instantiated, and the ApplicationContext object is ready for use..."Pericranium
@PlatonSerbin For a testing environment is there a way to make sure the @PostConstruct is called after the @Sql annotation so that during the @PostConstruct method I can access the database and load settings?Lacustrine
Authoritative source: github.com/spring-projects/spring-framework/issues/…Teenybopper
This answer saved me an issue that has been rattled all day. I needed to schedule a long running task when Spring boot starts up so it doesnt time out on aws gateway. Service method in different thread couldnt access session. However running it within the transation template saved the day. Thanks alot.Mutter
for me it runs much slower than @EventListener(ContextRefreshedEvent.class) + @Transactional while making 500k updates, 1200 in 6 mins vs 300k in 6 minsKaryokinesis
E
17

I think @PostConstruct only ensures the preprocessing/injection of your current class is finished. It does not mean that the initialization of the whole application context is finished.

However you can use the spring event system to receive an event when the initialization of the application context is finished:

public class MyApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
  public void onApplicationEvent(ContextRefreshedEvent event) {
    // do startup code ..
  }    
}

See the documentation section Standard and Custom Events for more details.

Enlace answered 27/6, 2013 at 16:22 Comment(4)
But then, how do I make my "init" method transactional ?Hexose
Have you tried calling your transactionsal service from the onApplicationEvent() method?Enlace
You are right, using @Transactional this is the best solutionHomework
Great answer! This should be the solution because it is a general way to initialize application when all proxies have been set and context has been fully initialized.Total
I
10

As an update, from Spring 4.2 the @EventListener annotation allows a cleaner implementation:

@Service
public class InitService {

    @Autowired
    MyDAO myDAO;

    @EventListener(ContextRefreshedEvent.class)
    public void onApplicationEvent(ContextRefreshedEvent event) {
        event.getApplicationContext().getBean(InitService.class).initialize();
    }

    @Transactional
    public void initialize() {
        // use the DAO
    }
}
Inurbane answered 31/12, 2018 at 1:28 Comment(1)
Note that this might call the initialize() method twice, see #6165073Galloway
P
8

Inject self and call through it the @Transactional method

public class AccountService {

    @Autowired
    private AccountService self;

    @Transactional
    public void resetAllAccounts(){
        //... 
    }

    @PostConstruct
    private void init(){
        self.resetAllAccounts();
    }

}

For older Spring versions which do not support self-injection, inject BeanFactory and get self as beanFactory.getBean(AccountService.class)

EDIT

It looks like that since this solution has been posted 1.5 years ago developers are still under impression that if a method, annotated with @Transactional, is called from a @PostContruct-annotated method invoked upon the Bean initialization, it won't be actually executed inside of Spring Transaction, and awkward (obsolete?) solutions get discussed and accepted instead of this very simple and straightforward one and the latter even gets downvoted.

The Doubting Thomases :) are welcome to check out an example Spring Boot application at GitHub which implements the described above solution.

What actually causes, IMHO, the confusion: the call to @Transactional method should be done through a proxied version of a Bean where such method is defined.

  1. When a @Transactional method is called from another Bean, that another Bean usually injects this one and invokes its proxied (e.g. through @Autowired) version of it, and everything is fine.

  2. When a @Transactional method is called from the same Bean directly, through usual Java call, the Spring AOP/Proxy machinery is not involved and the method is not executed inside of Transaction.

  3. When, as in the suggested solution, a @Transactional method is called from the same Bean through self-injected proxy (self field), the situation is basically equivalent to a case 1.

Polyclitus answered 19/6, 2018 at 18:45 Comment(0)
S
3

@Platon Serbin's answer didn't work for me. So I kept searching and found the following answer that saved my life. :D

The answer is here No Session Hibernate in @PostConstruct, which I took the liberty to transcribe:

@Service("myService")
@Transactional(readOnly = true)
public class MyServiceImpl implements MyService {

@Autowired
private MyDao myDao;
private CacheList cacheList;

@Autowired
public void MyServiceImpl(PlatformTransactionManager transactionManager) {

    this.cacheList = (CacheList) new TransactionTemplate(transactionManager).execute(new TransactionCallback(){

        @Override
        public Object doInTransaction(TransactionStatus transactionStatus) {

            CacheList cacheList = new CacheList();
            cacheList.reloadCache(MyServiceImpl.this.myDao.getAllFromServer());

            return cacheList;
        }

    });
}
Seminary answered 3/3, 2018 at 14:37 Comment(0)
T
1

The transaction part of spring might not be initialized completely at @PostConstruct.

Use a listener to the ContextRefreshedEvent event to ensure, that transactions are available:

@Component
public class YourService
    implements ApplicationListener<ContextRefreshedEvent> // <= ensure correct timing!
    {

    private final YourRepo repo;
    public YourService (YourRepo repo) {this.repo = repo;}

    @Transactional // <= ensure transaction!
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        repo.doSomethingWithinTransaction();
    }
}
Tympanic answered 26/4, 2021 at 10:29 Comment(0)
T
0

Using transactionOperations.execute() in @PostConstruct or in @NoTransaction method both works

@Service
public class ConfigurationService implements  ApplicationContextAware {
    private static final Logger LOG = LoggerFactory.getLogger(ConfigurationService.class);
    private ConfigDAO dao;
    private TransactionOperations transactionOperations;

    @Autowired
    public void setTransactionOperations(TransactionOperations transactionOperations) {
        this.transactionOperations = transactionOperations;
    }

    @Autowired
    public void setConfigurationDAO(ConfigDAO dao) {
        this.dao = dao;
    }


    @PostConstruct
    public void postConstruct() {
        try { transactionOperations.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(final TransactionStatus status) {
                    ResultSet<Config> configs = dao.queryAll();
                }
            });
        }
        catch (Exception ex)
        {
            LOG.trace(ex.getMessage(), ex);
        }
    }

    @NoTransaction
    public void saveConfiguration(final Configuration configuration, final boolean applicationSpecific) {
        String name = configuration.getName();
        Configuration original = transactionOperations.execute((TransactionCallback<Configuration>) status ->
                getConfiguration(configuration.getName(), applicationSpecific, null));


    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

    }
}
Tswana answered 28/3, 2018 at 7:46 Comment(0)
Z
0

I recommend method with @Transactional of CommandLineRunner, or ApplicationRunner.

Zooplasty answered 12/3, 2024 at 23:2 Comment(2)
Please, edit and try for How to Answer, describe the effect of what you propose and explain why it helps to solve the problem. Consider taking the tour.Supersession
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Carboniferous

© 2022 - 2025 — McMap. All rights reserved.