在过去的几天里,当我试图解决以下问题时,白头发的数量急剧增加。我在自定义事件侦听器中使用Spring Data JPA存储库,它利用了简单的Spring 3.2事件机制。我遇到的问题是,如果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类。
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
注释,但也失败了。欢迎就如何解决这个问题提出任何合理的建议。
多亏了@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);
}
}
虽然这有点旧,但我遇到了同样的问题,但现在使用Spring 4.1.1。RELEASE, Spring Data JPA 1.7.0和Hibernate 4.3.5.Final
我的情况发生在测试期间,有些测试失败。在测试过程中,我们的问题是由单连接模式下的H2,广播异步事件和事件事务性引起的。
<<p> 解决方案/strong>第一个问题是由于事务超时,并通过将
MVCC=true
添加到H2 URL字符串来解决。参见:https://stackoverflow.com/a/6357183/941187异步事件在测试期间导致问题,因为它们在不同的线程上执行。在事件配置中,使用了任务执行器和线程池。要解决这个问题,只需提供一个覆盖的配置bean,使用
SyncTaskExecutor
作为任务执行器。这将导致所有事件同步发生。事件事务性很棘手。在我们的框架中,事件从事务(
@Transactional
)中得到广播。然后在事务上下文之外的另一个线程上处理该事件。这引入了一个竞争条件,因为处理程序通常依赖于已提交的事务中的对象。我们在Windows开发机器上没有注意到这个问题,但在Linux上部署到生产环境时,这个问题就很明显了。该解决方案使用TransactionSynchronizationManager.registerSynchronization()
和TransactionSynchronization.afterCommit()
的实现在提交后广播事件。与#3相关,我们必须为某些事件处理程序调用的一些服务方法添加
@Transactional(propagation = REQUIRES_NEW)
。