Spring服务保存方法并启动线程

问题描述 投票:0回答:1

我想通过 Rest 端点保存实体并立即返回 DTO。之后,我想将一些需要一些时间的数据附加到实体并保存它。

这就是我的问题真正归结为,在 Spring Boot 项目中是否有首选方法来做到这一点?

仅供参考,我想出了两种方法,但都不能完全发挥作用:

方法 1 - 异步

@Service
public class EntityServiceImpl implements EntityService {

    @Override
    @Transactional
    public Entity save(Entity entity) {
        Entity saved = repository.save(entity);
        asyncCalculation(entity.getId());
        return saved;
    }

    @Async
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    private void asyncCalculation(Long id) {
        Entity entity = repository.findById(id).orElseThrow( () ... );
        entity.setData(longCalculation(entity));
        repository.save(entity);
    }


}

我的

@EnableAsync
类之一上有
@Configuration
,并且在所述配置类中还有一个 Bean:

@Configuration
@ComponentScan
@EnableAsync
@EnableJpaRepositories(basePackages = "org.repository")
@EnableTransactionManagement
public class SpringConfiguration{

    @Bean
    public ThreadPoolTaskExecutor asyncExecutor() {
        // setting a breakpoint here gets this called, so this should mean the configuration
        // gets triggered?
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5); 
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(25);
        executor.setThreadNamePrefix("AsyncThread-");
        executor.initialize();
        return executor;
    }

但是,这不会异步运行,它只是顺序运行。是否缺少某些配置?另外,我不太确定这里的事务,因为 save() 方法中的提交直到返回后才出现,所以我不完全理解为什么这甚至有效,因为我会在真正保存实体之前访问该实体到数据库。

方法 2 - 线程和自己的事务管理

与之前相同的服务类,只是:

        @Autowired
        private PlatformTransactionManager transactionManager;

        @Override
        // no transactional
        public Entity save(Entity entity) {
            TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);

            Entity savedEntity = transactionTemplate.execute(status -> {
                return repository.save(entity)
            });
            // now it is commited
            
            new Thread(() -> {
                asyncCalculation(savedEntity.getId());
            }).start();

            return savedEntity ;
        }

        // no @Async
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        private void asyncCalculation(Long id) {
            Entity entity = repository.findById(id).orElseThrow( () ... );
            entity.setData(longCalculation(entity));
            // longCalculation triggers LazyInitializations
            repository.save(entity);
        }
        
        

longCalculation()
触发实体的初始化,但是尽管我用
@Transactional(propagation = Propagation.REQUIRES_NEW)
注释了该方法,但我得到了
LazyInitializationException
:无法初始化代理[org.project.domain.SubEntity#767204] - 没有会话。
SubEntity
通过
Entity
 连接到 
@ManyToOne(fetch = FetchType.LAZY)

java spring multithreading asynchronous
1个回答
1
投票

基于代理的 AOP(这是 Spring 的默认设置,用于

@Async
@Transactional
)不适用于内部方法调用。仅仅是因为你在物体内部。

您可以通过多种方式解决此问题:

  1. 将此方法移至另一个类并调用它
  2. 通过执行自调用,意味着将服务实例注入自身并调用其方法(导致外部调用)。
  3. 对于这种特殊情况:创建一个
    @TransactionalEventListener
    来侦听事件并将其绑定到
    AFTER_COMMIT
    阶段(这样数据就存在)。
public record MyEvent(long id) { }
public class MyEventListener {

  @Async
  @TransactionalEventListener(phase = AFTER_COMMIT)
  public void calculation(MyEvent evt) {
    Entity entity = repository.findById(id).orElseThrow( () ... );
    entity.setData(longCalculation(entity));
    // longCalculation triggers LazyInitializations
    repository.save(entity);
  }
}
@Service
public class EntityServiceImpl implements EntityService {

    private final ApplicationEventPublisher publisher;

    public EntityServiceImpl(ApplicationEventPublisher publisher) {
      this.publisher = publisher;
    }

    @Override
    @Transactional
    public Entity save(Entity entity) {
        Entity saved = repository.save(entity);
        publisher.publishEvent(new MyEvent(entity.getId());
        return saved;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.