通过带回滚的 HQL 批量操作删除

Delete via HQL bulk operation with rollback

我正在寻找一种 eloquent 方法来删​​除事务中的多个实体。

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

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

一些陷阱:

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

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

不幸的是,如果不编写您自己的自定义框架来执行诸如基于注释的事务之类的操作,将很难做到 "nice"。如果您有权访问 AOP 库,您可以使用它来隐藏很多内容,但根据您的描述,这似乎令人怀疑。

我最终采用的解决方案包括 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.");
  }