【问题标题】:Delete via HQL bulk operation with rollback通过带有回滚的 HQL 批量操作删除
【发布时间】:2016-11-01 21:35:09
【问题描述】:

我正在寻找一种雄辩的方式来删除事务中的多个实体。

给定一个 id 列表,如果受影响的行数与列表数不同,我想抛出一个异常。目前我使用下面的sn-p,但它涉及到很多样板:

  private int deleteMyEntities(final List<Integer> ids) {
    final Session session = SomeHelper.getOpenSession();
    Transaction tx = null;
    try {
      tx = session.beginTransaction();
      final int affectedCount = session.createQuery("delete MyEntity where id in (:ids)")
          .setParameterList("ids", ids)
          .executeUpdate();
      if (affectedCount == ids.size()) {
        tx.commit();
        tx = null;
        return affectedCount;
      } else {
        throw new HibernateException("Delete count does not match target count.");
      }
    } finally {
      if (tx != null) {
        tx.rollback();
      }
    }
  }

一些陷阱:

  • 这是一个缺乏依赖注入、注释驱动事务和其他细节的遗留应用程序。类似于“使用弹簧”的答案并不是特别有帮助。
  • 我们编译成 java 1.6。

【问题讨论】:

  • 这段代码有什么问题?
  • 有大量的样板。它有效,只是丑陋
  • 代替 MyEntity 对象创建一个只有 id 字段的 DTO

标签: java hibernate hql java-6


【解决方案1】:

我试了一下。在这种特定情况下,您不需要在 try 语句中启动事务,因为如果您无法启动它,那么您可能无法回滚它,但即使您可以也没有任何意义,因为你还没有对它做任何事情。如果交易无法打开,就没有什么可以关闭的了。换句话说,连接池中不会有孤立线程。

private int deleteMyEntities(final List<Integer> ids) {
  final Session session = SomeHelper.getOpenSession();
  Transaction tx = session.beginTransaction();
  try {
    final int affectedCount = session.createQuery("delete MyEntity where id in (:ids)")
        .setParameterList("ids", ids)
        .executeUpdate();
    if (affectedCount == ids.size()) {
      tx.commit();
      return affectedCount;
    } else {
      throw new HibernateException("Delete count does not match target count.");
    }
  } catch (Exception e) {
     tx.rollback();
     throw e;
  } 
}

不幸的是,如果不编写自己的自定义框架来执行诸如基于注释的事务之类的事情,就很难让它变得“好”。如果您有权访问 AOP 库,则可以使用它来隐藏很多内容,尽管根据您的描述,这似乎令人怀疑。

【讨论】:

  • 使用这个解决方案,我的方法签名必须是public int deleteMyEntities(final List&lt;Integer&gt; ids) throws Exceptionthrows Exception 至少可以说是令人不快。
  • 您可以将“e”包装在 RuntimeException 中,例如 throw new RuntimeException(e),这样可以消除方法签名更改,但这不是最优雅的解决方案。
【解决方案2】:

我最终采用的解决方案包括 Alex 的建议。我还提取了很多逻辑来保持代码有点干燥。注意:休眠会话在过滤器中打开并在请求期间保持(在视图中打开会话),因此recoverFromFailedTransaction中的会话重置

  public int deleteMyEntity(final List<Integer> ids) {
    return deleteEntities("MyEntity", ids);
  }

  private int deleteEntities(final String entityName, final List<Integer> ids) {
    final Session session = SomeHelper.getOpenSession();
    final Query query = session.createQuery("delete " + entityName + " where id in (:ids)")
        .setParameterList("ids", ids);
    return performBatchOperation(query, ids.size());
  }

  private int performBatchOperation(final Query query, final int expectedAffectedCount) {
    final Session session = SomeHelper.getOpenSession();
    final Transaction tx = session.beginTransaction();
    try {
      final int affectedCount = query.executeUpdate();
      if (affectedCount == expectedAffectedCount) {
        tx.commit();
        return affectedCount;
      } else {
        throw new HibernateException(String.format(
            "Affected count [%d] does not match expected count [%d].",
            affectedCount,
            expectedAffectedCount));
      }
    } catch (RuntimeException e) {
      logger.error(e);
      recoverFromFailedTransaction(tx);
      throw e;
    }
  }

private void recoverFromFailedTransaction(final Transaction tx) {
    try {
      if (tx != null) {
        tx.rollback();
      }
    } catch (HibernateException e) {
      logger.error("Exception when rolling back failed transaction. ", e);
    }
    try {
      SomeHelper.getOpenSession().close();
    } catch (HibernateException e) {
      logger.error("Exception when closing session . ", e);
    }
    SomeHelper.resetOpenSession();
    logger.warn("Session discarded.");
  }

【讨论】:

    猜你喜欢
    • 2013-01-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-18
    • 2018-01-01
    • 1970-01-01
    相关资源
    最近更新 更多