我正在尝试编写一种方法,该方法将基于唯一但非主键返回一个Hibernate对象.如果实体已经存在于数据库中,我想返回它,但是如果我不想创建一个新的实例并在返回之前保存它.
更新:让我澄清一点,我正在编写的应用程序基本上是一个批量处理器的输入文件.系统需要逐行读取文件并将记录插入数据库.文件格式基本上是在我们的模式中的几个表的非规范化视图,所以我要做的是解析父记录或将其插入到数据库中,以便我可以获得一个新的合成键,或者如果它已经存在选择它.然后我可以在其他表中添加额外的关联记录,这些表中有外键返回到该记录.
这个棘手的原因是,每个文件都需要完全导入,或者根本没有导入,即对于给定文件所做的所有插入和更新都应该是一个事务的一部分.如果只有一个进程正在进行所有导入,那么这很简单,但如果可能,我想在多个服务器上进行分解.由于这些限制,我需要能够保留在一个事务中,但是处理已经存在记录的异常.
父记录的映射类如下所示:
@Entity public class Foo { @Id @GeneratedValue(strategy = IDENTITY) private int id; @Column(unique = true) private String name; ... }
我最初尝试写这个方法如下:
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; }
问题是当我正在寻找的名称存在时,org.hibernate.AssertionFailure异常被调用到uniqueResult()抛出.完整的堆栈跟踪如下所示:
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]
有人知道是什么导致这个异常被抛出? hibernate是否支持更好的方式来实现?
让我先抢先解释为什么我先插入,然后选择是否和何时失败.这需要在分布式环境中工作,所以我无法通过检查进行同步,以查看记录是否已经存在并插入.最简单的方法是让数据库通过检查每个插入的约束违例来处理此同步.
解决方法
我有一个类似的批处理要求,流程在多个JVM上运行.我采取的方法如下.这很像jtahlborn的建议.但是,如vbence所指出的,如果使用NESTED事务,当您获得约束违例异常时,会话无效.相反,我使用REQUIRES_NEW,它暂停当前事务并创建一个新的独立事务.如果新交易回滚,则不会影响原始交易.
我正在使用Spring的TransactionTemplate,但是如果您不希望对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; }