@PostConstruct 方法上的@Transactional

问题描述 投票:0回答:7

我想在我的应用程序开始时读取文本数据装置(CSV 文件)并将其放入我的数据库中。

为此,我创建了一个带有初始化方法(@PostConstruct注释)的PopulationService

我还希望它们在单个事务中执行,因此我在同一方法上添加了@Transactional

但是,@Transactional似乎被忽略了: 事务在我的低级 DAO 方法中启动/停止。

我需要手动管理交易吗?

spring transactions
7个回答
96
投票

引自遗留(已关闭)Spring 论坛:

在@PostConstruct(与InitializingBean接口中的afterPropertiesSet一样)中,无法确保所有后处理都已完成,因此(实际上)不能有事务。确保其正常工作的唯一方法是使用 TransactionTemplate。

因此,如果您希望在事务中执行

@PostConstruct
中的某些内容,您必须执行以下操作:

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

17
投票

我认为

@PostConstruct
仅确保当前课程的预处理/注入完成。这并不意味着整个应用程序上下文的初始化已经完成。

但是,当应用程序上下文初始化完成时,您可以使用 spring 事件系统来接收事件:

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

请参阅文档部分标准和自定义事件了解更多详细信息。


10
投票

作为更新,从 Spring 4.2 开始,

@EventListener
注释允许更清晰的实现:

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

8
投票

注入 self 并通过它调用

@Transactional
方法

public class AccountService {

    @Autowired
    private AccountService self;

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

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

}

对于不支持自注入的旧 Spring 版本,注入

BeanFactory
并获取
self
beanFactory.getBean(AccountService.class)

编辑

看起来自从这个解决方案在 1.5 年前发布以来,开发人员仍然认为如果有一个方法, 用

@Transactional
注释,从 Bean 初始化时调用的
@PostContruct
注释方法调用,它不会在 Spring Transaction 中实际执行,并且会讨论和接受笨拙(过时?)的解决方案,而不是这个非常简单的解决方案直接的一个,后者甚至会被否决。

持怀疑态度的 Thomases :) 欢迎查看 GitHub 上的 Spring Boot 示例应用程序,它实现了上述解决方案。

恕我直言,真正造成混乱的是:

@Transactional
方法的调用应该通过定义了此类方法的Bean的代理版本来完成

  1. 当从

    另一个Bean
    调用@Transactional方法时,另一个Bean通常会注入这个方法并调用它的代理(例如通过@Autowired)版本,一切都很好。

  2. 当直接从同一个 Bean

    调用
    @Transactional方法时,通过通常的 Java 调用,不涉及 Spring AOP/Proxy 机制,并且该方法不会在 Transaction 内部执行。

  3. 当按照建议的解决方案,通过自注入代理

    @Transactional
    字段)从同一个Bean调用self方法时,情况基本上等同于情况1。
    
    


3
投票
@Platon Serbin

的回答对我不起作用。所以我继续寻找并找到了以下拯救我生命的答案。 :D 答案就在这里

@PostConstruct中没有Session Hibernate

,我冒昧抄录一下: @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; } }); }



1
投票
@PostConstruct

没有完全初始化。

使用 

ContextRefreshedEvent

事件的侦听器来确保事务可用:

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



0
投票
transactionOperations.execute()

@PostConstruct
方法中使用
@NoTransaction
都可以

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

© www.soinside.com 2019 - 2024. All rights reserved.