Java – find or insert based on Hibernate’s unique key

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 I don't want to create a new instance and save it before returning

Update: let me clarify that the application I'm writing is basically an input file for a batch processor The system needs to read the file line by line and insert the record into the database The file format is basically a denormalized view of several tables in our schema, so what I have to do is parse the parent record or insert it into the database so that I can get a new composite key or select it if it already exists Then I can add additional associated records in other tables where foreign keys return to the record

The reason for this difficulty is that each file needs to be imported completely or not at all, that is, all inserts and updates made to a given file should be part of a transaction This is simple if only one process is doing all imports, but if possible, I want to decompose on multiple servers Due to these limitations, I need to be able to keep in a transaction, but deal with exceptions with existing records

The mapping class of the parent record is as follows:

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

I initially tried to write this method 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;
}

The problem is that when the name I'm looking for exists, org hibernate. Assertionfailure exception is called to uniqueresult() and thrown The complete stack trace is as follows:

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 caused this exception to be thrown? Does hibernate support a better way to implement it?

Let me explain first why I inserted first, and then choose whether and when to fail This needs to work in a distributed environment, so I can't synchronize by checking to see if the record already exists and is inserted The easiest way is to let the database handle this synchronization by checking each inserted constraint violation

Solution

I have a similar batch requirement that the process run on multiple JVMs My approach is as follows This is very much like jtahlborn's suggestion However, as vbence pointed out, if you use nested transactions, the session is invalid when you get a constraint violation exception Instead, I use requirements_ New, which pauses the current transaction and creates a new independent transaction If the new transaction is rolled back, the original transaction will not be affected

I'm using spring's transactiontemplate, but if you don't want to have a dependency on spring, I'm sure you can translate it easily

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;
}
The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>