处理数据库约束的 Hibernate

7
我的项目使用带有Spring事务管理器的Hibernate,数据库是Postgres(可能不相关)。
我正在尝试读取大型XML文件并从中构建对象(对象不大但数量很多),然后将它们插入数据库。
如果我的某个对象违反了数据库约束,则整个过程会停止。如何跳过违反数据库约束的对象?或者记录它们的ID或其他信息到日志文件中?
问题更新:
我已经在SO上浏览过,发现对于批量插入最好使用无状态session,但我仍然遇到同样的问题,插入操作停止:
May 26, 2012 4:45:47 PM org.hibernate.util.JDBCExceptionReporter logExceptions
SEVERE: ERROR: duplicate key value violates unique constraint "UN_FK"
  Detail: Key (fid)=(H1) already exists.

以下是我用于解析XML并将其插入数据库的代码相关部分,为简单起见,假设我正在插入电影:
//class field
@Autowired
private SessionFactory sessionFactory;

@Override
public void startDocument() throws SAXException {
    session = sessionFactory.getCurrentSession();
}

@Override
public void endElement(String uri, String localName, String qName) throws SAXException  {
if (qName.equalsIgnoreCase("FILM")) {
        movie.setCategory(category);
        movie.setAdded(new Date());
        session.insert(movie);
    }
}

我在应用上下文中设置了属性hibernate.jdbc.batch_size为100。是否真的需要在插入之前进行选择以避免这种情况发生?如果我使用StatelessSession而不是session,我会得到大约20个插入,然后处理会无限期地停止,没有任何异常或其他信息。我假设数字20是因为我正在使用tomcat池化连接并且有maxActive="20"。我真的很希望看到有人提供解决方案(如果可能的话,不要使用防御性选择)。使用statelessSession或只是session。
6个回答

4
大多数类型的限制条件,例如是否可以为空或具有最大宽度,您可以使用Hibernate Validator进行检查。在尝试持久化对象之前,请手动执行验证。

对于某些内容,特别是唯一约束条件,您需要执行“防御性”选择以查看是否存在冲突,或者维护一个已插入的内存值集。


4

我认为完全验证以确保成功插入是不可能的。在某些情况下,无论你做什么,其他人都可以在验证和插入之间向数据库中插入内容,从而导致约束违规。

在大多数情况下,我建议像处理其他异常一样处理。


2

如果你需要从一个xml文件中插入大量对象,可以考虑使用spring batch。参数skip-limit允许你在批处理过程停止之前告诉它有多少个错误行。还要检查skip-policy和skippable-exception;请参阅Spring文档中的配置步骤

如果你不想使用spring batch,只需使用简单的try catch来使你的进程一直运行到结束。


一个简单的try catch语句是不够的。一旦Hibernate抛出异常,会导致会话状态不一致,必须回滚事务并关闭会话。此外,异常只会在刷新时间后才被抛出,而这个时间很长,可能已经将错误记录持久化了。 - JB Nizet
这就是为什么建议在这种情况下使用无状态的Hibernate会话。它将防止不一致的状态,并减少内存消耗(或者您不必驱逐已处理的实体)。 - Sebastien Lorber

2

为什么您认为这一切都必须是一个大交易?实际上,您所描述的一切都表明您实际上有许多交易。对于“出错”的对象,只需驱逐该实体并回滚事务即可。如果“FILM”元素定义了一组对象的图形,则会变得更加复杂,但思想是相同的。


1

0

虽然这是一个老问题,但我最近遇到了类似的情况,这里是我的做法。

我也使用无状态会话,但我所做的不同之处在于:我发出了一个 session.insert,它要么成功,要么由于约束违规失败。如果由于约束违规而失败,则会抛出 ConstraintViolationException 异常,捕获它将为您提供失败的插入对象,然后您可以对失败的对象执行任何操作。除了事务之外,还为每个插入使用保存点 (save points),保存点更便宜,不会造成太大的性能影响,因此当事务由于某些问题而失败时,提交将进行到最后一个保存点 (在 catch 语句中)。

我的代码看起来像这样:

try {
  session.connection().setSavePoint("lastSaved");
  session.insert(obj);
}
catch(ConstraintViolationException) {
  log.error(obj.getUserId());
  ....
}
tx.commit();
....
catch(TransactionException e) {
  tx.rollback();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接