【问题标题】:Annotate propagation and isolation in Transaction using spring properly使用spring正确注释Transaction中的传播和隔离
【发布时间】:2018-09-19 06:30:16
【问题描述】:

我编写了一个任务调度程序作业,它每 30 分钟为符合条件的患者生成账单。在这里,我很困惑我保持的传播和隔离级别是否按照标准是正确的。我应该始终使用 REQUIRES_NEW 作为传播的一部分吗?

关于以下部分的任何建议。

private void startBilling() throws Exception {
    List<Integer> patientIds = null;
    try {
        patientIds = getPatientsForBilling();
        if(null != patientIds) {
            for(Integer patient : patientIds) {
                updatePatientDetails(patient, "STARTED", jdbcTemplate);
                makeBillForPatient(patient, jdbcTemplate);
                updatePatientDetails(patient, "COMPLETED", jdbcTemplate);
            }
        }
    } catch (Exception e) {
         //LOG HERE
         updatePatientDetails(patient, "TERMINATED", jdbcTemplate);
    }
}

@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class,isolation = Isolation.SERIALIZABLE)
private void makeBillForPatient(Integer patient, JdbcTemplate jdbcTemplate2) {
    // A bill for the patient will be generated and printed
}

@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class,isolation = Isolation.SERIALIZABLE)
private void updatePatientDetails(Integer patient,
        String status, JdbcTemplate jdbcTemplate) {
    // Update the patient billing status STARTED
}

@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class,isolation = Isolation.SERIALIZABLE)
private List<Integer> getPatientsForBilling() {
    return new ArrayList<Integer>();
}

这是我实现的逻辑

  1. 一旦任务调度程序启动,它将获取所有符合条件的患者进行计费(即出院的患者)
  2. 它会将状态更新为已启动。
  3. 它将生成账单
  4. 它会将状态更新为 COMPLETED

任何想法将不胜感激。

【问题讨论】:

  • 除非您使用 AspectJ 或类似的东西,否则注释私有方法几乎是无用的:据我所知,Spring 默认使用代理 AOP,它不会拦截自我调用。

标签: spring spring-transactions


【解决方案1】:

其实这取决于你的逻辑。据我了解,您将患者标记为已开始,然后执行一些可能需要长时间运行的复杂任务(创建账单),然后释放患者。

这些操作应该是隔离的,因此您需要为每个操作单独事务,在这种情况下 REQUIRES_NEW 就可以了。

但是您的逻辑(显示在发布的代码中)有一些漏洞。

想象一下 makeBillForPatient() 方法调用会引发异常。你遇到的麻烦:

  1. 患者类型卡在 STARTED 状态。您需要以某种方式处理保持 STARTED 状态的患者以重置它们。

  2. 没有处理所有其余患者,因为您在异常发生时离开了循环。

这样的代码

    patientIds = getPatientsForBilling();
    if(null != patientIds) {
        for(Integer patient : patientIds) {
            try {
                updatePatientDetails(patient, "STARTED", jdbcTemplate);
                try {
                    makeBillForPatient(patient, jdbcTemplate);
                    updatePatientDetails(patient, "COMPLETED", jdbcTemplate);
                } catch (Exception e) {
                    //print something in the log here and reset patient status
                    updatePatientDetails(patient, "INITIAL", jdbcTemplate);
                }
            } catch (Exception e) {
                //print something in the log here
            }
        }
    }

也不要留下空的 catch 块。万一出现问题,真的很难找到源头。至少在日志中打印一些内容。

【讨论】:

  • 是的 StanislavL,我实际上正在处理 catch 块并更新状态以及 TERMINATED 并向患者添加原因。这是唯一的小sn-p。你看到任何其他泄漏吗?喜欢隔离级别还是传播级别?
  • 可序列化级别也可能过于严格。你为什么决定使用它?我认为已提交阅读就足够了
  • Serializable,因为它是一个任务调度器,并且应用程序部署在多个节点中。
  • 您可以将select for update 用于单行(患者)。而不是检索患者列表,而是选择下一个患者进行计费(用于更新)以仅锁定一个表行,将患者标记为 STARTED 等。在相反的情况下,多个节点没有好处。患者将在数据库级别排队(可序列化实际上锁定了整个表)
  • 我检索患者列表的原因是,它的任务调度程序不应该每次都针对单个患者访问数据库。在时间间隔内,它将检索所有符合条件的患者并一次更新每个患者。因为当第二个作业在 15 分钟后运行时,该作业不应该选择最初的患者
猜你喜欢
  • 2018-05-14
  • 2012-01-19
  • 1970-01-01
  • 2017-08-15
  • 1970-01-01
  • 2013-11-06
  • 2019-12-07
  • 2016-09-19
  • 2019-03-31
相关资源
最近更新 更多