Spring Data JPA存储库,事务和Spring事件

时间:2013-06-04 10:11:29

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

在尝试解决以下问题时,过去几天的白发量急剧增加。我在使用简单的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配置,它是定义与Oracle数据库连接的ComboPooledDataSource实例。作为旁注,使用组件扫描,项目是Spring MVC。 现在是Java类。

ListenerA

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

ListenerB

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

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

2 个答案:

答案 0 :(得分: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)进行注释才能使所有内容发挥作用。

听众B

@Sevice
public class ListenerB implements ApplicationListener<JobStartedEvent> {

@Autowired
private JobService service;


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

答案 1 :(得分:1)

虽然这稍微过时,但我遇到了同样的问题,但现在使用 Spring 4.1.1.RELEASE Spring Data JPA 1.7.0 Hibernate 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)

  5. 希望这有助于一些后来者。