我试图从Spring应用程序调用一个长时间运行的方法。此方法通过JPA读取数据库,并在调用方法完成并返回时执行其任务。问题是Spring应用程序需要这种方法是Transactional,但我不能这样做。我得到的是
Exception in thread "bulk_task_executor_thread1" org.springframework.dao.InvalidDataAccessApiUsageException: You're trying to execute a streaming query method without a surrounding transaction that keeps the connection open so that the Stream can actually be consumed. Make sure the code consuming the stream uses @Transactional or any other way of declaring a (read-only) transaction.
at org.springframework.data.jpa.repository.query.JpaQueryExecution$StreamExecution.doExecute(JpaQueryExecution.java:343)
at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:87)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:116)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:106)
以下是我的代码片段:
@Component
public class BulkLoadingService {
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Autowired
private LoadingWorkerFactory loadingWorkerFactory;
@Autowired
@Qualifier("bulkTaskExecutor")
private TaskExecutor taskExecutor;
@Autowired
ModelMapper modelMapper;
@Transactional(readOnly = true)
public void loadAllData() {
LOG.info("loadAllData() started.");
LoadingWorker loadingWorker = loadingWorkerFactory.getLoadingWorker();
taskExecutor.execute(loadingWorker);
LOG.info("loadAllData() finished.");
}
}
-
@Configuration
public class BulkFrameworkConfiguration {
public static final int NTHREADS = 10;
@Bean
@Qualifier("bulkTaskExecutor")
public TaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(NTHREADS);
executor.setThreadNamePrefix("bulk_task_executor_thread");
executor.initialize();
return executor;
}
}
-
import org.modelmapper.ModelMapper;
import org.springframework.messaging.MessageChannel;
@Autowired
private MessageChannel actionBulkOutboundChannel;
@Autowired
private ActionRepository actionRepository;
@Component
public class LoadingWorkerFactory {
@Autowired
ModelMapper modelMapper;
public LoadingWorker getLoadingWorker() {
return new LoadingWorker(actionRepository, channel, modelMapper);
}
}
-
public class LoadingWorker implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private ModelMapper modelMapper;
private ActionRepository dataRepository;
private MessageChannel outboundChannel;
public LoadingWorker(ActionRepository repository, MessageChannel channel, ModelMapper modelMapper) {
this.modelMapper = modelMapper;
this.dataRepository = repository;
this.outboundChannel = channel;
}
@Override
@Transactional(readOnly = true)
public void run() {
LOG.info("LoadingWorker.run() started.");
long startTime = System.currentTimeMillis();
long counter = 0;
try(Stream<ActionEntity> entityStream = dataRepository.getAll()) {
counter = entityStream.peek(entity -> {
ActionDocument ad = modelMapper.map(entity, ActionDocument.class);
LOG.debug("About to build a message '{}'", ad);
Message<ActionDocument> message = MessageBuilder.withPayload(ad).build();
try {
outboundChannel.send(message);
} catch (MessagingException me) {
LOG.error("Exception encountered while writing request message to queue: {}", me.getRootCause());
LOG.debug("Exception encountered while writing request message to queue", me);
} catch (Exception e) {
LOG.error("Some exception encountered while writing request message to queue", e);
}
}).count();
}
LOG.info("LoadingWorker.run() finished: {} Documents ({} ms)", counter, System.currentTimeMillis() - startTime);
}
protected Stream<ActionEntity> getEntityStream() {
return dataRepository.getAll();
}
}
-
@Repository
public interface ActionRepository extends JpaRepository<ActionEntity, UUID> {
@Query("SELECT oa FROM OfficeAction oa ORDER By oa.id")
Stream<OfficeAction> getAll();
...
-
@Entity
@Table(
name = "oa_office_action")
public class ActionEntity implements Serializable {
...
简而言之,BulkLoadingService使用LoadingWorkerFactory创建新工作程序(LoadingWorker实现Runnable)并使用TaskExecutor运行此工作程序。 LoadingWorker尝试访问ActionRepository以获取所有内容的Stream,这就是事情发生的时候。
如何在Spring中创建一个在另一个Thread @Transactional中运行的方法(不属于@Bean或@Component的方法)? 在我的情况下,声明式交易支持是不可能的吗?
P.S。通过直接使用TransactionTemplate(而不是尝试使用@Transactional注释),我能够使这个工作。这是不同的:
public class LoadingWorker implements Runnable {
...
private PlatformTransactionManager transactionManager;
...
@Override
public void run() {
LOG.info("LoadingWorker.run() started.");
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setReadOnly(true);
transactionTemplate.execute(status -> {
LOG.info("Anonymous TransactionCallback started.");
inner();
return null;
});
}
protected void inner() {
long startTime = System.currentTimeMillis();
...
虽然我仍然想知道是否可以使用注释来完成。
答案 0 :(得分:3)
不重组整个应用程序。我要说,因为您已经将bean传递给Runnable
实现,所以您可以传递TransactionTemplate并在事务中执行代码。
您必须使用execute
方法并将实现放在其中。