【问题标题】:Spring Data JPA repositories, transactions and eventing in SpringSpring Data JPA 存储库、事务和 Spring 中的事件
【发布时间】:2013-05-30 16:34:18
【问题描述】:

在尝试解决以下问题时,过去几天白发的数量急剧增加。我在使用简单 Spring 3.2 事件机制的自定义事件侦听器中使用 Spring Data JPA 存储库。我遇到的问题是,如果ListenerA 创建一个实体并调用assetRepository.save(entity)assetRepository.saveAndFlash(entity),那么随后从另一个侦听器检索同一实体的调用将失败。原因似乎是ListenerB在数据库中找不到原始实体,它似乎还在Hibernate的缓存中。 ListenerB 锁定实体的触发器是由于线程池中的可运行任务执行而触发的事件。 这是我的配置:

<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="persistenceUnitName" value="spring-jpa" />
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
            <property name="generateDdl" value="false" />
            <property name="database" value="#{appProps.database}" />
        </bean>
    </property>
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
            <prop key="hibernate.hbm2ddl.auto">#{appProps['hibernate.hbm2ddl.auto']}</prop>
            <prop key="hibernate.show_sql">#{appProps['hibernate.show_sql']}</prop>
            <prop key="hibernate.format_sql">#{appProps['hibernate.format_sql']}</prop>
            <prop key="hibernate.search.default.directory_provider">org.hibernate.search.store.impl.FSDirectoryProvider</prop>
            <prop key="hibernate.search.default.indexBase">#{appProps.indexLocation}</prop>
            <prop key="hibernate.search.lucene_version">#{appProps['hibernate.search.lucene_version']}</prop>
        </props>
    </property>
</bean>

<tx:annotation-driven transaction-manager="transactionManager" />

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
    <property name="jpaDialect">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
    </property>
</bean>

我省略了dataSource 配置,它是ComboPooledDataSource 的一个实例,它定义了与Oracle 数据库的连接。附带说明一下,使用了组件扫描,并且项目是 Spring MVC。 现在是 Java 类。

听众A

@Sevice
public class ListenerA implements ApplicationListener<FileUploadedEvent> {

    @Autowired
    private AssetRepository assetRepository;

    @Autowired
    private ExecutorService executor; // Triggers runnable task on a Job in Spring's TaskExecutor

    @Override
    @Transactional
    public void onApplicationEvent(FileUploadedEvent event) {

    Asset target = event.getTarget();
    Job job = new Job(target);
    assetRepository.save(job);

    executor.execute(job);
}

监听器B

@Sevice
public class ListenerB implements ApplicationListener<JobStartedEvent> {

    @Autowired
    private AssetRepository assetRepository;


    @Override
    @Transactional
    public void onApplicationEvent(JobStartedEvent event) {

    String id = event.getJobId();
    Job job = assetRepository.findOne(id); // at this point we can not find the job, returns null
    job.setStartTime(new DateTime());
    job.setStatus(Status.PROCESSING);

    assetRepository.save(job);
}

JobStartedEventTaskExecutor 内的可运行任务触发。 我在这里做错了什么?我曾尝试使用具有事务意识的自定义事件发布者,但这似乎并不能解决问题。我还尝试连接适当的服务而不是数据存储库,并从侦听器中删除 @Transactional 注释,这也失败了。欢迎任何关于如何解决问题的合理建议。

【问题讨论】:

  • 您确定事务注释在 onapplicationevent 方法上起作用吗?如果您在服务 bean 中推送作业的创建并将事务注释移到那里会发生什么?
  • 我倾向于认为注释正在工作,因为 Job 已经嵌入了执行期间需要的延迟加载集合。关于第二个问题,我设法通过用服务替换 Spring Data 存储库来解决这个问题。然而,为了让它工作,我必须用@Transactional(propagation=Propagation.REQUIRES_NEW) 注释初始化方法(Job 对象首先创建和保存的地方)。我不是 100% 确定这是否是解决此问题的正确方法。为什么我只能在侦听器中使用存储库?
  • 这可能不是原因,但无论如何 - 您似乎在退出事务块并提交事务之前调用了 executor.execute(job) 。您不能 100% 保证事务会在 onApplicationEvent 执行之前提交。

标签: spring hibernate spring-mvc event-handling spring-data-jpa


【解决方案1】:

感谢@Kresimir Nesek 的提示,我已经设法解决了这个问题。所以解决方案是用适当的服务替换 Spring Data 存储库。 这是修改后的类。

听众 A

@Sevice
public class ListenerA implements ApplicationListener<FileUploadedEvent> {

    @Autowired
    private JobService service;

    @Autowired
    private ExecutorService executor; // Triggers runnable task on a Job in Spring's TaskExecutor

    @Override
    public void onApplicationEvent(FileUploadedEvent event) {

    Job job = service.initJobForExecution(event.getTarget());

    executor.execute(job);
    }
 }

JobService 方法中,initJobForExecution(Asset target) 必须用 @Transactional(propagation=Propagation.REQUIRES_NEW) 注释才能正常工作。

听众 B

@Sevice
public class ListenerB implements ApplicationListener<JobStartedEvent> {

@Autowired
private JobService service;


@Override
public void onApplicationEvent(JobStartedEvent event) {
    service.updateStatus(event.getJobId(), Status.PROCESSING); 
 }
}

【讨论】:

    【解决方案2】:

    虽然这有点旧,但我遇到了同样的问题,但现在使用 Spring 4.1.1.RELEASESpring Data JPA 1.7.0休眠 4.3.5.Final

    我的场景发生在测试过程中,一些测试失败了。在测试过程中,我们的问题是H2在单连接模式下,广播异步事件和事件事务性造成的。

    解决方案

    1. 第一个问题是由于事务超时,通过将MVCC=true 添加到 H2 URL 字符串来解决。见:https://stackoverflow.com/a/6357183/941187

    2. 异步事件在测试期间导致问题,因为它们在不同的线程上执行。在事件配置中,使用了任务执行器和线程池。为了解决这个问题,只需提供一个使用SyncTaskExecutor 作为任务执行器的覆盖配置bean。这将导致所有事件同步发生。

    3. 事件事务性很棘手。在我们的框架中,事件从事务中广播 (@Transactional)。然后在事务上下文之外的另一个线程上处理该事件。这引入了竞争条件,因为处理程序通常依赖于事务中的对象已被提交。我们没有注意到我们的 Windows 开发机器上的问题,但是当在 Linux 上部署到生产环境时它变得很明显。该解决方案使用TransactionSynchronizationManager.registerSynchronization()TransactionSynchronization.afterCommit() 的实现在提交后广播事件。有关更多信息和示例,请参阅 http://www.javacodegeeks.com/2013/04/synchronizing-transactions-with-asynchronous-events-in-spring.html

    4. 与 #3 相关,我们必须为从某些事件处理程序调用的某些服务方法添加 @Transactional(propagation = REQUIRES_NEW)

    希望这对后来者有所帮助。

    【讨论】:

      猜你喜欢
      • 2020-10-08
      • 2015-04-07
      • 2017-02-11
      • 2018-08-06
      • 2017-12-23
      • 1970-01-01
      • 2020-09-06
      • 1970-01-01
      • 2013-09-13
      相关资源
      最近更新 更多