views:

94

answers:

2

I'm trying to persist 3 entities (exp,def,meng) in a transaction, and then persist another 2 (def', meng'), where meng' is related to exp.

However, as I attempt to persist meng' eclipselink/jpa2 is being told to:

Call: INSERT INTO EXPRESSION (EXPRESSION, GENDER) VALUES (?, ?)
    bind => [exp, null]

which will throw an expession since it's been already inserted and it's a key. So apparently persisting the entity meng' which includes updating exp itself would somehow make eclipselink think I asked to persist a new exp.

Here is the test:

@Test
public void testInsertWords() throws MultipleMengsException, Exception{
    final List<String[]> mengsWithSharedExp = new LinkedList<String[]>();
    mengsWithSharedExp.add(mengsList.get(3));
    mengsWithSharedExp.add(mengsList.get(4));

    insertWords(mengsWithSharedExp, null, mengsDB);
}

Here is the problematic code:

public void insertWords(EnumMap<Input, MemoEntity> input) throws MultipleMengsException {
    Expression def = (Expression) input.get(Input.definition);
    Expression exp = (Expression) input.get(Input.expression);
    beginTransaction();
    persistIfNew(def);
    persistIfNew(exp);
    persistNewMeng(null, exp, def);
    commitTransaction();
}

private void persistNewMeng(final MUser usr, Expression exp, final Expression def) throws RuntimeException {
    final Meaning meng = new Meaning(usr, exp, def);
    if (!persistIfNew(meng)) {
        throw new RuntimeException("Meng ." + meng.toString() + " was expected to be new.");
    }
    if (usr != null) {
        usr.addMeng(meng);
    }
}

public <Entity> boolean persistIfNew(final Entity entity) {
        final Object key = emf.getPersistenceUnitUtil().getIdentifier(entity);
        if (em.find(entity.getClass(), emf.getPersistenceUnitUtil().getIdentifier(entity)) != null) {
            return false;
        }
        em.persist(entity);
        return true;
    }

You can checkout the Maven source code (to test) from here.

Is this expected behavior? If so, why? And most importantly, how to solve?

It looks as if

@ManyToMany(cascade=CascadeType.ALL)
private Set<Expression> exps;

in Meaning is the culprit, although I don't understand why it should. The documentation says:

If the entity is already managed, the persist operation is ignored, although the persist operation will cascade to related entities that have the cascade element set to PERSIST or ALL in the relationship annotation.

A: 

Mostly likely you run into:

"[...] If the entity is detached [...] the transaction commit will fail." (same source that you are citing)

If you persist a new entity that references an already persistent entity, you must use "merge" instead of "persist". "merge" will persist new entities and update existing entities.

Also beware of the fact that the merge operation will return an attached data graph, that must be used for further operations within the same persistence context.

Frank
so if remove all the cascade annotation attribute from Meaning persist should work. It seems it does, but somehow the entity stays lurking around. Indeed when I come to delete all meanings that one is not (when I delete through the cascade operation of a holding entity). If I do try to explicitly delete it I get an exception from tying to delete the table that holds the relationship btw the exp and meng entities. Why?
simpatico
A: 

Frank is correct. You are not reading in the Expression, so when you call persist on Meaning, when it is referencing existing Expressions they are detached, which cause the transaction to fail. Calling merge will work, or you can remove the cascade persist on the exps relationship since you seem to persist new Expressions directly anyway its not needed.

Chris
so this could be more refined: @ManyToMany(cascade={CascadeType.MERGE, CascadeType.REFRESH, CascadeType.REMOVE}). However once I delete an Expression (after having deleted all mengs in the db, i get DELETE on table 'EXPRESSION' caused a violation of foreign key constraint 'MNNGXPRSSXPSXPRSSN'.
simpatico