web-dev-qa-db-fra.com

@Transactional sur la méthode @PostConstruct

Je veux lire les données textuelles (fichiers CSV) au début de mon application et les mettre dans ma base de données.

Pour cela, j'ai créé un PopulationService avec une méthode d'initialisation ( @PostConstruct annotation).

Je veux aussi qu'ils soient exécutés dans une seule transaction, et c'est pourquoi j'ai ajouté @Transactional sur la même méthode.

Cependant, le @Transactional semble être ignoré: La transaction est démarrée/arrêtée avec mes méthodes DAO de bas niveau.

Dois-je gérer la transaction manuellement alors?

37
Raphael Jolivet

cela pourrait être utile ( http://forum.springsource.org/showthread.php?58337-No-transaction-in-transactional-service-called-from-PostConstruct ):

Dans @PostConstruct (comme avec afterPropertiesSet à partir de l'interface InitializingBean), il n'y a aucun moyen de s'assurer que tout le post-traitement est déjà fait, donc (en effet) il ne peut y avoir aucune transaction. Le seul moyen de s’assurer que cela fonctionne consiste à utiliser un TransactionTemplate. 

Donc, si vous voulez que quelque chose dans votre @PostConstruct soit exécuté dans une transaction, vous devez faire quelque chose comme ceci:

@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
            }
        });
   }
}
77
Platon

Je pense que @PostConstruct garantit uniquement que le prétraitement/injection de votre classe actuelle est terminé. Cela ne signifie pas que l'initialisation de l'ensemble du contexte de l'application est terminée. 

Cependant, vous pouvez utiliser le système d'événements spring pour recevoir un événement lorsque l'initialisation du contexte de l'application est terminée:

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

Voir la section documentation Evénements standard et personnalisés pour plus de détails.

14
micha

La réponse de @Platon Serbin n'a pas fonctionné pour moi. Alors j'ai continué à chercher et j'ai trouvé la réponse suivante qui m'a sauvé la vie. :RÉ

La réponse est ici Aucune session Hibernate dans @PostConstruct , que je me suis permis de transcrire:

@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;
        }

    });
}
2
carolnogueira

En guise de mise à jour, à partir de Spring 4.2, l'annotation @EventListener permet une implémentation plus propre:

@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
    }

}

1
DBE

S'injecter soi-même et appeler par la méthode @Transactional 

public class AccountService {

    @Autowired
    private AccountService self;

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

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

}

Pour les versions plus anciennes de Spring qui ne prennent pas en charge l'auto-injection, injectez BeanFactory et obtenez self comme beanFactory.getBean(AccountService.class)

0
igor.zh

Utiliser transactionOperations.execute() dans la méthode @PostConstruct ou dans la méthode @NoTransaction fonctionne dans les deux cas

@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 {

    }
}
0
ikarayel