3

在尝试解决以下问题时,过去几天白发的数量急剧增加。我在使用简单 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>

我省略了定义连接到 Oracle 数据库dataSource的实例的配置。ComboPooledDataSource附带说明一下,使用了组件扫描,并且项目是 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从侦听器中删除注释,这也失败了。欢迎任何关于如何解决问题的合理建议。

4

2 回答 2

2

感谢@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)才能使一切正常工作。

听众乙

@Sevice
public class ListenerB implements ApplicationListener<JobStartedEvent> {

@Autowired
private JobService service;


@Override
public void onApplicationEvent(JobStartedEvent event) {
    service.updateStatus(event.getJobId(), Status.PROCESSING); 
 }
}
于 2013-07-18T13:27:46.470 回答
1

虽然这有点旧,但我遇到了同样的问题,但现在使用Spring 4.1.1.RELEASESpring Data JPA 1.7.0Hibernate 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)从某些事件处理程序调用的某些服务方法添加。

希望这可以帮助一些后来者。

于 2014-11-27T05:53:25.427 回答