views:

47

answers:

2

I have several mapped objects in my JPA / Hibernate application. On the network I receive packets that represent updates to these objects, or may in fact represent new objects entirely.

I'd like to write a method like

<T> T getOrCreate(Class<T> klass, Object primaryKey)

that returns an object of the provided class if one exists in the database with pk primaryKey, and otherwise creates a new object of that class, persists it and returns it.

The very next thing I'll do with the object will be to update all its fields, within a transaction.

Is there an idiomatic way to do this in JPA, or is there a better way to solve my problem?

+3  A: 

1) Create an EntityManager instance (let's call it "em"), unless you already have an active one
2) Create a new transaction (let's call it "tx")
3) Call em.find(Object pk)
4) Call tx.begin()
5a) If find() returned a non-null entity reference then you need to do an update. Apply your changes to the returned entity and then call em.merge(Object entity).
5b) if find() returned a null reference, then that PK does not exist in the database. Create a new entity and then call em.persist(Object newEntity).
6) Call em.flush()
7) Call tx.commit()
8) Return your entity reference, per your method signature.

Jim Tough
+1  A: 

I'd like to write a method like <T> T getOrCreate(Class<T> klass, Object primaryKey)

This won't be easy.

A naive approach would be to do something like this (assuming the method is running inside a transaction):

public <T> T findOrCreate(Class<T> entityClass, Object primaryKey) {
    T entity = em.find(entityClass, primaryKey);
    if ( entity != null ) {
        return entity;
    } else {
        try {
            entity = entityClass.newInstance();
            /* use more reflection to set the pk (probably need a base entity) */
            return entity;
        } catch ( Exception e ) {
            throw new RuntimeException(e);
        }
    }
}

But in a concurrent environment, this code could fail due to some race condition:

T1: BEGIN TX;
T2: BEGIN TX;

T1: SELECT w/ id = 123; //returns null
T2: SELECT w/ id = 123; //returns null

T1: INSERT w/ id = 123;
T1: COMMIT; //row inserted

T2: INSERT w/ name = 123;
T2: COMMIT; //constraint violation

And if you are running multiple JVMs, synchronization won't help. And without acquiring a table lock (which is pretty horrible), I don't really see how you could solve this.

In such case, I wonder if it wouldn't be better to systematically insert first and handle a possible exception to perform a subsequent select (in a new transaction).

You should probably add some details regarding the mentioned constraints (multi-threading? distributed environment?).

Pascal Thivent