2013-06-04 126 views
2

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

<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类。

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注释,这些注释也失败了。欢迎任何有关如何解决问题的合理建议。

+0

你确定事务注解在onapplicationevent方法上工作吗?如果您在服务bean内部创建作业并将事务注释移到那里,会发生什么? –

+0

我倾向于认为这个注释正在工作,因为Job嵌入了执行过程中需要的延迟加载收集。关于第二个问题,我设法通过用服务替换Spring Data存储库来解决这个问题。然而,为了这个工作,我不得不用'@Transactional(propagation = Propagation.REQUIRES_NEW)'注释初始化方法(其中Job对象首先创建并保存)。如果这是解决此问题的正确方法,我不是100%确定的。为什么我只能在听众中使用存储库? – pilot

+0

这可能不是原因,但无论如何 - 您似乎在退出事务性块并提交事务之前调用executor.execute(job)。您不能100%保证在执行onApplicationEvent之前事务将被提交。 –

回答

2

由于来自@Kresimir Nesek的提示,我设法解决了这个问题。所以解决方案是用适当的服务来替换Spring Data存储库。 这里是修改的类。

听者

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

虽然这虽是略显老套,我跑在这个同样的问题,但现在春4.1.1.RELEASE春数据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)

希望这有助于一些迟来的人。