线程

时间:2018-05-09 17:59:14

标签: java spring multithreading jpa

我试图从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的方法)? 在我的情况下,声明式交易支持是不可能的吗?

  • 如果我使用ActionRepository中使用List或Iterable而不是Stream的同步getAll()方法,问题就会消失。但我想加快数据加载,所以我使用Stream。
  • 如果我直接从BulkLoadingService(没有线程)执行数据加载,问题就会消失。但我想要进行异步,多线程执行。
  • 在LoadingWorker的run()方法上放置@Async注释没有任何区别。
  • 我不想要的是在BulkLoadingService中启动事务并将其扩展为生成的线程。

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();
...

虽然我仍然想知道是否可以使用注释来完成。

1 个答案:

答案 0 :(得分:3)

不重组整个应用程序。我要说,因为您已经将bean传递给Runnable实现,所以您可以传递TransactionTemplate并在事务中执行代码。

您必须使用execute方法并将实现放在其中。