如何让每个预定方法有一个 Hibernate 会话

How to have one Hibernate session per scheduled method

我有以下设置:

@Component
public class Scheduler {
    Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    BatchService batchService;

    @Scheduled(cron = "0 */1 * ? * *")
    void tick() {
        logger.info("Beginning of a batch tick");
        batchService.refundNotAssignedVisits();
        logger.info("End of the batch tick");
    }
}

BatchService 包含以下内容:

@Service
public class BatchServiceImpl implements BatchService {

    Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    VisitService visitService;

    @Override
    @Transactional
    public void refundNotAssignedVisits() {
        logger.info("Start automatic refund of past visits being assigned");

        Set<Visit> visits = visitService.findRefundableVisits();

        if(visits != null && visits.size() != 0) {
            logger.info("Found " + visits.size() + " visits to refund with IDs: " + visits.stream().map(x -> x.getId().toString()).collect(Collectors.joining(", ")));
            visits.forEach(x -> {
                logger.info("Refunding visit with ID: " + x.getId());
                try {
                    visitService.cancel(x);
                    logger.info("Visit successfully refunded!");
                }
                catch(Exception e) {
                    logger.error("Error while refunding visit...", e);
                }
            });
        }
        else {
            logger.info("Found no visit to refund.");
        }

        logger.info("End of automatic refund");
    }
}

cancel 方法定义如下:

@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Visit cancel(Visit visit) throws Exception {
    // Some business logic
}

出于业务目的,我需要 cancel 方法每次调用有一个事务,目前,refundNotAssignedVisits@Transactional 以便启用 Hibernate 会话,所以我可以在 cancel 方法中对相关实体使用延迟加载。

这会导致重复提交等问题,我想知道什么是实现我想要的好的模式:有一个 @Scheduled 方法启用 Hibernate 会话以便多次调用另一个方法每次通话一笔交易。

@TransactionalREQUIRES_NEW 将创建另一个新的 Hibernate 会话,因此 cancel() 中的会话将不同于用于加载看起来很尴尬的实体的会话大部头书。通常,我们使用同一个会话来加载和管理一个事务中的同一个实体。

我会将代码重构为以下内容:

VisitService:

//Cannel by visitorId and load the Visitor by Id in a new transaction
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Visit cancel(Integer visitorId) throws Exception {
    Visit visit=  session.get(Visit.class , visitorId); 
    cancel(visit); 
}

@Override
public Visit cancel(Visit visit) throws Exception {
    // Some business logic
}

//Add method to return the IDs only
@Transactional(readOnly=true)
public Set<Integer> findRefundableVisitId(){

}

批处理服务:

//@Transactional  (Do not require anymore)
public void refundNotAssignedVisits() {
    logger.info("Start automatic refund of past visits being assigned");

    Set<Integer> refundVisitIds = visitService.findRefundableVisitId();
    refundVisitIds.forEach( id-> {
           try {
                visitService.refund(id);
                logger.info("Visit successfully refunded!");
            }
            catch(Exception e) {
                logger.error("Error while refunding visit...", e);
            }        
     });
}    

这样,每个退款都在自己的事务中执行,用于加载退款访问者的事务不需要等待所有退款完成才能提交,不再需要 "duplicate commits"。