我想通过 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)
基于代理的 AOP(这是 Spring 的默认设置,用于
@Async
和 @Transactional
)不适用于内部方法调用。仅仅是因为你在物体内部。
您可以通过多种方式解决此问题:
@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;
}
}