1

I'm working on code that allows an object graph to be migrated from one database to another. The object graph represents configuration, we're essentially moving configuration from a staging environment to a production environment.

The graph is fetched from the source DB, detached, serialised, and then merged in to the target DB.

This has been working really well so far, but I have one pickle.

I have a graph that looks like this:

@Entity
class UoObject
{
  @Id
  private int uoObject;

  @OneToMany(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
  private Set<UoAttribute> uoAttribute;

  // Other irrelevant fields
}

@Entity
class UoAttribute
{
  @Id
  private int uoAttribute;

  @OneToOne(optional=true, fetch=FetchType.EAGER, cascade=CascadeType.ALL)
  private UoAttributeObject uoAttributeObject;

}

@Entity
class UoAttributeObject
{
  @Id
  private UoAttribute uoAttribute;

  @ManyToOne
  private UoObject uoObject;
}

So here UoObject has a collection of UoAttributes, and these UoAttributes may have a UoAttributeObject which refers to another UoObject.

The UoObjects referred to by UoAttributeObjects are expected to be in the target database already. If they're not, I want to raise an error. For clarity, I never want the target UoObject to have its state updated... essentially I want to only set up the relationship.

When I implemented this, I expected that without a cascade=MERGE on the relationship, JPA would raise an error saying that the object doesn't exist. Or, even, the database layer would end up complaining about foreign keys. Instead I'm finding that JPA attempts to insert a mostly empty UoObject instance (only the key is set). I found this in the JPA 2.0 specification:

If X is an entity merged to X', with a reference to another entity Y, where cascade=MERGE or cascade=ALL is not specified, then navigation of the same association from X' yields a reference to a managed object Y' with the same persistent identity as Y.

It seems that the source DB finds that the target UoObject isn't there, schedules an instance for insertion (bad), but does not merge the state of the source UoObject in (good!).

In the source DB end, is there a way that I can detect that the entity wasn't in the DB already and raise an error? I suspect that lifecycle callbacks @PrePersist and @PreUpdate might help, but I can't see how.

Royce
  • 532
  • 3
  • 11
  • I suspect I've been hit by this bug: https://bugs.eclipse.org/bugs/show_bug.cgi?id=247662 – Royce Feb 29 '12 at 13:44

1 Answers1

0

What happens if you add an @Version to the UoObject?

Basically you are merging a corrupt object graph, so the result can be difficult to determine. Usage of locking can help in these situations. Basically you can look at it as you are merging changes from one transaction (your old database), and have inconsistent state (because the object was deleted on the new database in another transaction), so the only way to resolve such concurrency issues is through locking.

It does seem like a bug, in that if the object does not exist, and not cascade merge or cascade persist, it seems invalid and an error should be thrown from the merge. But knowing whether something is a new object, or an existing object that was deleted is very difficult without the usage of locking.

You could always just check the relationship yourself as well. If the UoObject must exist, then do a find for it before the merge, and throw an error if it does not exist, or insert it, or whatever.

James
  • 17,965
  • 11
  • 91
  • 146