使用Hibernate根据唯一键查找或插入 [英] Find or insert based on unique key with Hibernate

查看:207
本文介绍了使用Hibernate根据唯一键查找或插入的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试编写一种方法,该方法将基于唯一但非主键返回一个Hibernate对象.如果该实体已经存在于数据库中,我想返回它,但是如果不存在,我想创建一个新实例并在返回之前保存它.

I'm trying to write a method that will return a Hibernate object based on a unique but non-primary key. If the entity already exists in the database I want to return it, but if it doesn't I want to create a new instance and save it before returning.

更新:让我澄清一下,我为此编写的应用程序基本上是输入文件的批处理程序.系统需要逐行读取文件并将记录插入数据库.该文件格式基本上是我们架构中几个表的非规范化视图,因此我要做的是解析父记录,或者将其插入db中,这样我就可以获得一个新的合成键,或者如果它已经存在,则选择它.然后,我可以在具有外键的其他表中添加其他关联记录,回到该记录.

UPDATE: Let me clarify that the application I'm writing this for is basically a batch processor of input files. The system needs to read a file line by line and insert records into the db. The file format is basically a denormalized view of several tables in our schema so what I have to do is parse out the parent record either insert it into the db so I can get a new synthetic key, or if it already exists select it. Then I can add additional associated records in other tables that have foreign keys back to that record.

之所以变得棘手,是因为每个文件都需要完全导入或根本不导入,即,为给定文件完成的所有插入和更新都应该是一个事务的一部分.如果只有一个进程完成所有导入,那么这很容易,但是我想尽可能在​​多个服务器之间进行分解.由于这些限制,我需要能够留在一个事务中,但是要处理已经存在记录的异常.

The reason this gets tricky is that each file needs to be either totally imported or not imported at all, i.e. all inserts and updates done for a given file should be a part of one transaction. This is easy enough if there's only one process that's doing all the imports, but I'd like to break this up across multiple servers if possible. Because of these constraints I need to be able to stay inside one transaction, but handle the exceptions where a record already exists.

父记录的映射类如下:

@Entity
public class Foo {
    @Id
    @GeneratedValue(strategy = IDENTITY)
    private int id;
    @Column(unique = true)
    private String name;
    ...
}

我最初编写此方法的尝试如下:

My initial attempt at writting this method is as follows:

public Foo findOrCreate(String name) {
    Foo foo = new Foo();
    foo.setName(name);
    try {
        session.save(foo)
    } catch(ConstraintViolationException e) {
        foo = session.createCriteria(Foo.class).add(eq("name", name)).uniqueResult();
    }
    return foo;
}

问题是当我要查找的名称存在时,对uniqueResult()的调用将引发org.hibernate.AssertionFailure异常.完整的堆栈跟踪如下:

The problem is when the name I'm looking for exists, an org.hibernate.AssertionFailure exception is thrown by the call to uniqueResult(). The full stack trace is below:

org.hibernate.AssertionFailure: null id in com.searchdex.linktracer.domain.LinkingPage entry (don't flush the Session after an exception occurs)
    at org.hibernate.event.def.DefaultFlushEntityEventListener.checkId(DefaultFlushEntityEventListener.java:82) [hibernate-core-3.6.0.Final.jar:3.6.0.Final]
    at org.hibernate.event.def.DefaultFlushEntityEventListener.getValues(DefaultFlushEntityEventListener.java:190) [hibernate-core-3.6.0.Final.jar:3.6.0.Final]
    at org.hibernate.event.def.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:147) [hibernate-core-3.6.0.Final.jar:3.6.0.Final]
    at org.hibernate.event.def.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:219) [hibernate-core-3.6.0.Final.jar:3.6.0.Final]
    at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:99) [hibernate-core-3.6.0.Final.jar:3.6.0.Final]
    at org.hibernate.event.def.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:58) [hibernate-core-3.6.0.Final.jar:3.6.0.Final]
    at org.hibernate.impl.SessionImpl.autoFlushIfRequired(SessionImpl.java:1185) [hibernate-core-3.6.0.Final.jar:3.6.0.Final]
    at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1709) [hibernate-core-3.6.0.Final.jar:3.6.0.Final]
    at org.hibernate.impl.CriteriaImpl.list(CriteriaImpl.java:347) [hibernate-core-3.6.0.Final.jar:3.6.0.Final]
    at org.hibernate.impl.CriteriaImpl.uniqueResult(CriteriaImpl.java:369) [hibernate-core-3.6.0.Final.jar:3.6.0.Final]

有人知道导致此异常的原因是什么?休眠是否支持实现此目的的更好方法?

Does anyone know what is causing this exception to be thrown? Does hibernate support a better way of accomplishing this?

让我也先解释一下为什么我先插入然后选择是否以及何时失败.这需要在分布式环境中工作,所以我无法在检查之间进行同步以查看记录是否已存在以及插入是否存在.最简单的方法是让数据库通过检查每个插入上的约束冲突来处理此同步.

Let me also preemptively explain why I'm inserting first and then selecting if and when that fails. This needs to work in a distributed environment so I can't synchronize across the check to see if the record already exists and the insert. The easiest way to do this is to let the database handle this synchronization by checking for the constraint violation on every insert.

推荐答案

我有类似的批处理要求,进程运行在多个JVM上.我为此采取的方法如下.这很像jtahlborn的建议.但是,正如vbence所指出的,如果您使用NESTED事务,则当您遇到约束违例异常时,您的会话将无效.相反,我使用REQUIRES_NEW,它会挂起当前事务并创建一个新的独立事务.如果新交易回滚,则不会影响原始交易.

I had a similar batch processing requirement, with processes running on multiple JVMs. The approach I took for this was as follows. It is very much like jtahlborn's suggestion. However, as vbence pointed out, if you use a NESTED transaction, when you get the constraint violation exception, your session is invalidated. Instead, I use REQUIRES_NEW, which suspends the current transaction and creates a new, independent transaction. If the new transaction rolls back it will not affect the original transaction.

我正在使用Spring的TransactionTemplate,但是如果您不希望依赖于Spring,我敢肯定您可以轻松地对其进行翻译.

I am using Spring's TransactionTemplate but I'm sure you could easily translate it if you do not want a dependency on Spring.

public T findOrCreate(final T t) throws InvalidRecordException {
   // 1) look for the record
   T found = findUnique(t);
   if (found != null)
     return found;
   // 2) if not found, start a new, independent transaction
   TransactionTemplate tt = new TransactionTemplate((PlatformTransactionManager)
                                            transactionManager);
   tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
   try {
     found = (T)tt.execute(new TransactionCallback<T>() {
        try {
            // 3) store the record in this new transaction
            return store(t);
        } catch (ConstraintViolationException e) {
            // another thread or process created this already, possibly
            // between 1) and 2)
            status.setRollbackOnly();
            return null;
        }
     });
     // 4) if we failed to create the record in the second transaction, found will
     // still be null; however, this would happy only if another process
     // created the record. let's see what they made for us!
     if (found == null)
        found = findUnique(t);
   } catch (...) {
     // handle exceptions
   }
   return found;
}

这篇关于使用Hibernate根据唯一键查找或插入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆