【问题标题】:Using the Current Sesssion after an exception is thrown抛出异常后使用当前会话
【发布时间】:2014-09-11 05:42:50
【问题描述】:

根据Hibernate documentation,Hibernate 抛出异常后使用 Session 是不安全的。

如果 Session 抛出异常,包括任何 SQLException,立即回滚数据库事务,调用 Session.close() 并丢弃 Session 实例。 Session 的某些方法不会使会话保持一致状态。 Hibernate 抛出的任何异常都不能被视为可恢复的。通过在 finally 块中调用 close() 确保 Session 将被关闭。

在我的代码中,我正在执行批量插入。我正在使用sessionFactory.getCurrentSession() 方法来获取会话。我的代码是这样的。

try {
  //some code here....
  ....
  Table1Entity table1Entity = .......
  List<Table2Entity> table2Entities = .......
  Session currentSession = sessionFactory.getCurrentSession();
  for (int i = 0; i < table2Entities; i++) {
      .............
      currentSession.save(table2Entities.get(i));
      if(i % batchSize == 0 || i + 1 == table2Entities) {
        currentSession.flush();
        currentSession.clear();
    }
  }
} catch (Exception e) {
    currentSession.getTransaction().rollback();
    //currentSession.close(); //According to documentation Session should be closed here
    table1Entity.setError(true);
    currentSession.save(table1Entity);//According to documentation Session should not be used here
    .......
}

正如文档所说,在引发异常后不应使用会话。我的问题是因为我使用的是currentSession,如何将table1Entity 保存在catch 块中?我应该使用openSession() 方法还是任何其他方式打开一个新会话?


编辑: 更清楚地说,我要问的是我是否可以在现有的 currentSession 关闭后检索新的 currentSession

【问题讨论】:

  • this 是否在某处为您提供帮助
  • @ankur-singhal 感谢您的链接。但这并不能解决我的问题。在那个答案中,他回滚了整个事务。在我的情况下,我需要在抛出异常后保留另一个实体。

标签: java spring hibernate session bulkinsert


【解决方案1】:

这是一个单独的 try..catch 块。您如何确定在 table1Entity 上进行的任何处理都不会引发 Hibernate 异常?如果在处理 table1Entity 时确实出现异常,那么尝试将其保存在当前会话中可能会产生更大的问题。

我的建议是将表上的处理分成单独的 try..catch 块,并在每个块中分别调用 save。抛出异常后,尝试在 catch 块内的同一会话中保存事务是不安全的,并且假设 table1Entity 中没有问题有时可能是违反直觉的。

【讨论】:

  • 感谢您的回答。上面的代码不是来自我的实际课程。无论如何,正如我所见,您还没有理解我的问题。正如文档所说,如果 Hibernate 在保存 table2Entities 时抛出异常,则会话不应再用于将来的事务。应调用 Session.close() 并丢弃 Session 实例。关闭会话后我的问题是如何检索新会话以保存 table1Entity?
  • 为什么不先保存 table1Entity,然后再转到 table2Entities?因为据了解,即使在处理 table2Entities 时出现故障,您也希望 table1Entity 被持久化。这也是我在回答中提出的建议。
  • 不,实际上我想调用table1Entity的setError()方法,只有在保存table2Entities时抛出异常时才保存。请看我上面的代码。
  • 如果我是你,我会重新考虑我的实施。使用“脏”会话持久化数据可能会产生不可预知的后果。考虑保存 table1Entity 数据并继续处理 table2Entity 的替代方法。如果没有抛出异常,则在存储table2Entities数据后删除已保存的table1Entity数据,否则处理异常并保存table1Entity。在这种方法中,在抛出异常后不会对会话进行数据持久化。
  • @CodeNewbie 你的最后一条评论基本上是个好主意,但缺点是你不能用它写任何错误消息(因为它们还没有出现)。
【解决方案2】:

如果与数据库的连接出现异常,我建议不要写入数据库。所以基本上,我认为你的方法有严重的问题,修复它很可能对你没有任何好处。

在这种情况下,您应该非常小心,无论如何您的批次都不会引发异常。因此,如果确实发生异常,您可以假设数据库无法访问,或者无论如何都不会接受任何尝试。将错误写入其他地方(已证明日志文件对此很有用)。

我可以看到您的方法似乎可行的情况(比如我更新了所有有效的方法,我标记了其他方法。为了识别“其他”我使用了一个例外)但它几乎永远不会出现在事实上是一个好主意,而是一种“机智”的情况。 - 不要机智,不会有回报的。

而是以一种方式编写您的批处理,让那些无法按首选方式运行的批处理返回一个正确的结果,显示“我没有工作”并适当地处理它。

所以在你的情况下:

try {
  //some code here....
  ....
  Table1Entity table1Entity = .......
  List<Table2Entity> table2Entities = .......
  Session currentSession = sessionFactory.getCurrentSession();
  for (int i = 0; i < table2Entities; i++) {
      .............

      // HERE you need to make sure that this call would succeed
      currentSession.save(table2Entities.get(i));
      if(i % batchSize == 0 || i + 1 == table2Entities) {
        currentSession.flush();
        currentSession.clear();
    }
  }
  currentSession.save(table1Entity);
  currentSession.getTransaction().commit();
} catch (Exception e) {
  currentSession.getTransaction().rollback();
  log.warn("Could not do what I wanted", e);
}

这要么全部解决,要么根本不解决,并为您提供适当的堆栈跟踪,其中包含消息,并且全部位于它所属的位置(日志)。

也许你想在调用之前先检测一下调用是否成功。

如果你认为你必须做你想做的事,请阅读 CodeNewbie 在他的回答下的 cmets:使用setGoingToWriteBatch(true) 创建 table1Entry 然后在批处理的末尾setBatchWritten(true)。这样您就可以找到第一个为真但第二个不为真的那些。

【讨论】:

  • 感谢您的建议。如果在 DB 中找到重复的 ID 值,基本上我的批次会失败。在这种情况下,我想回滚 table2Entities 的所有插入并用“true”填充 table1(table1Entity) 的“错误”列。您能否建议我在批处理失败后如何将 table1Entity 的此属性设置为 true 并将其保存到 DB。谢谢。
  • 我其实不相信CodeNewbies关于插入和删除Table1记录的建议是好的。例如,当我使用序列时,它调用 nextVal(),然后分配一个 ID,如果抛出异常,则删除该 ID。所以这个ID永远不能用。另一方面,我正在使用大批量,这需要一些时间才能插入。因此,当我的批次仍在插入时,如果有人查询 Table1,他会看到“错误”列已设置为“真”。这是误导。与其使用该解决方案,我更愿意避免使用 getCurrentSession() 并改用 openSession()。
  • @prageeth 如何做到这一点很大程度上取决于你现在如何做到这一点。我通常将 SQL 用于此类事情,因为它可以让您更直接地控制(并且更快)发生的事情,而不是使用面向对象的包装。但是,如果您想坚持使用 OO 方法,只需在插入之前进行选择。
  • @prageeth 我认为插入和删除的想法也不好,但原则 - 先写一些东西,然后在成功而不是失败时写一些不同的东西是好的。事先写好property1,如果property2永远不会被设置,你就知道它坏了。但是不要写异常,那肯定会坏的。
  • @prageeth 有一件事:序列就是为了这个特定的原因。如果您错过了一个(或一千个),那无论如何都不重要!该 ID 将永远不会被使用,这是一件好事 - 不要解决这个问题。如果您需要一个完全没有缺失编号的序列(例如发票编号),您应该插入具有不同 ID 字段的发票。然后在成功插入发票后,将其更新为包含下一个发票编号。我很确定你真正的问题是避免重叠的 id。你应该问问自己(和谷歌)你为什么这样做。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-09-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多