【问题标题】:How to have one Hibernate session per scheduled method如何为每个计划的方法设置一个 Hibernate 会话
【发布时间】:2018-12-31 11:01:42
【问题描述】:

我有以下设置:

@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 会话,以便对另一个方法进行多次调用,每次调用一个事务.

【问题讨论】:

  • 我没听错吧:目前tick 方法有可能同时运行两次,您想避免吗?

标签: java spring hibernate spring-scheduled


【解决方案1】:

@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);
            }        
     });
}    

这样,每个退款都在他们自己的事务中执行,用于加载退款访问者的事务不需要等待所有退款完成才能提交,也不会再出现“重复提交”。

【讨论】:

  • 这对我来说看起来很干净,我会尽快尝试。您能否确认 findRefundableVisitId 和 cancel 将共享同一个会话?
  • 不,他们在单独的会话中,但没关系。关键是我们确保“加载和取消”访问者使用在 VisitService.cancel(vistorId) 中完成的同一会话。
  • 我更仔细地阅读了您的解决方案,但它不适用于我的情况,因为我需要传递访问实体。实际上,我在取消方法中加载了相关实体。我在我的问题中添加了这种精确度
  • 这就是为什么我建议您在同一事务下的取消方法中加载访问实体及其相关实体,以确保始终使用同一个会话来加载和更新它们。 ..
  • 这意味着加载每个实体一次以获取 Id 并在取消方法中一次?
猜你喜欢
  • 1970-01-01
  • 2019-01-28
  • 2012-11-13
  • 2012-10-13
  • 2020-05-16
  • 2011-10-27
  • 2013-03-23
  • 1970-01-01
  • 2015-03-09
相关资源
最近更新 更多