@Version column gets updated even though persist was never called
I've a database with two tables (more actually, but that shouldn't matter in this case). One of the two has a foreign key reference to the other. These could be the mappings:
@Entity @Table public class A {
@Id public String id;
@Version public int version;
@OneToMany(targetEntity = B.class,
mappedBy = "a",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY)
public Collection<B> bs;
...
}
@Entity @Table public class B {
@Id public String id;
@Version public int version;
@ManyToOne(targetEntity = A.class,
cascade = CascadeType.ALL,
fetch = FetchType.LAZY)
public A a;
...
}
I've an intermediate data structure which holds information on the relationship between A
and B
which I iterate over before persisting my data inside an active transaction, checking if an A
or B
already exists in the database or not using EntityManager.find(Class<T>, Object)
:
// get an entity manager and begin transaction
for (C c : cs) {
A a = em.find(A.class, c.getAId()); // an `A` doesn't exist in the database
if (null == a) { // on first iteration so a new one will
a = new A(); // be created
a.id = c.getAId(); // however in a 100 `C` there are 4-5
} // unique `A` exists
B b = em.find(B.class, c.getBId()); // a `B` generally doesn't exist in
if (null == b) { // the database, but the opposite
b = new B(); // could happen often
b.id = c.getBId();
b.a = a;
a.bs.add(b);
}
if (!em.contains(b)) // if a `B` was found in the database
em.persist(b); // persist won't execute here
}
// commit transaction
This works great: new data gets persisted, already existing stuff don't.
H开发者_StackOverflow社区owever! The version column in my database always gets updated no matter what happens in the for-each loop. I've taken a set of sample data, persisted it in my empty tables (versions was at 0); I've taken this same set of sample data and persisted it again (EntityManager.persist(Object)
never got called) and the version fields (in A
) got updated, but I can't imagine why.
I'm aware the fact that even though EntityManager.persist(Object)
never got called my persistence provider (Hibernate) generated the update statements to increment the version column.
The questions: Why? How can I prevent this? What should I change in my method of persisting my data in order to avoid this? Thanks.
I think that's what's happening here is that you have the lock mode set to OPTIMISTIC_FORCE_INCREMENT. If you were to set it to plain OPTIMISTIC, then you would not see the update. The javadoc explanation of the behaviour is precise but rather dense; there is also a blog post which might help.
I think the point of OPTIMISTIC_FORCE_INCREMENT is that it protects consistency in the database. Imagine you had two As, a1 and a2, each with a single B. Two threads load both of them. Thread 1 sets a1 to have two Bs, and a2 to have a single B (the status quo). Thread 2 sets a1 to have one B (the status quo) and a2 to have two Bs. Thread 1 commits before thread 2. With OPTIMISTIC_FORCE_INCREMENT, thread 2 will get an exception, because the objects have already been modified by thread 1. With OPTIMISTIC, thread 2 would succeed, because each thread would only bump the version on the object it was adding a B to. If thread 2 succeeds, the database now represents a state that neither of the threads committed - a1 and a2 both have two Bs.
Now, it might be that this inconsistency - a sort of implicit merging of states - is acceptable, and indeed exactly what you want. But JPA can't assume that in general, so OPTIMISTIC_FORCE_INCREMENT is available. I don't know if it's the default (i can't find any documentation saying so, but it would be sensible), or if you're setting it explicitly somewhere else.
As for solutions, in the case where nothing at all is modified, you could simply not commit the session. If you are dealing with cases where some objects are modified and some not, you could process each A object in its own session. Or you could just use OPTIMISTIC locking. If i'm right, that is!
B b = em.find(B.class, c.getBId()); // a `B` generally doesn't exist in
if (null == a) {
I suppose you made a small mistake while posting this two lines of code, the variable in if condition should be b instead of a. If this is the case below line causes update of the version field.
a.bs.add(b);
精彩评论