views:

1441

answers:

1

I have a legacy database, which I am using EJB3 to model. The database is in quite a poor shape, and we have certain unusual restrictions on how we insert into the DB. Now I want to model the database in a hierarchy that fits in with the DB strucuture, but I want to be able to manually insert each entity individually without the persistence manager trying to persist the entities children.

I am trying something like the following (boilerplate left out):

@Entity
@Table(name = "PARENT_TABLE")
public class Parent {
    @Id
    @Column(name = "ID")
    int id;

    @OneToMany
    List<Child> children;
}


@Entity
@Table(name = "CHILD_TABLE")
public class Child {
    @Id
    @Column(name = "ID")
    int id;   
}

Now this throws an exception:

java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST

Now I know the entity isn't marked PERSIST - I don't want the EntityManager to persist it! I want to be able to persist the parent first, and then the child - but not together. There are good reasons for wanting to do it this way, but it doesn't seem to want to play.

+3  A: 

Heh welcome to the hair-pulling that is JPA configuration.

In your case you have two choices:

  1. Manually persist the new object; or
  2. Automatically persist it.

To automatically persist it you need to annotate the relationship. This is a common one-to-many idiom:

@Entity
@Table(name = "PARENT_TABLE")
public class Parent {
  @Id private Long id;

  @OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
  private Collection<Child> children;

  public void addChild(Child child) {
    if (children == null) {
      children = new ArrayList<Child>();
    }
    child.setParent(parent);
    children.add(child);
  }
}

@Entity
@Table(name = "CHILD_TABLE")
public class Child {
  @Id private Long id;

  @ManyToOne
  private Parent parent;

  public void setParent(Parnet parent) {
    this.parent = parent;
  }
}

Parent parent = // build or load parent
Child child = // build child
parent.addChild(child);

Because of the cascade persist this will work.

Note: You have to manage the relationship at a Java level yourself, hence manually setting the parent. This is important.

Without it you need to manually persist the object. You'll need an EntityManager to do that, in which case it is as simple as:

entityManager.persist(child);

At which point it will work correctly (assuming everything else does).

For purely child entities I would favour the annotation approach. It's just easier.

There is one gotcha I'll mention with JPA:

Parent parent = new Parent();
entityManager.persist(parent);
Child child = new Child();
parent.addChild(child);

Now I'm a little rusty on this but I believe that you may run into problems if you do this because the parent was persisted before the child was added. Be careful and check this case no matter what you do.

cletus
So if I set the cascade property to PERSIST, and use the entity manager to persist the parent, will the EntityManager also try to persist the Child? If so, this is not what I want, as I need to be able to persist Parent and Child independently.
Ant
Incidentally, the backlink ommission was deliberate - I only want to model the realationships one-way. Would this cause problems, i.e. are two way relationships necessary?
Ant
One way to parent to child may be problematic. Little rusty. One way from child to parent is easier. The problem with P->C is the foreign key resides in the child so needs to be mapped. Can you elaborate on your situation?
cletus
By the way "persist" doesn't mean quite what you think in JPA. It just makes the object a managed object that can be saved if the transaction is committed (but the save won't happen until commit assuming this is all in a transaction). Why independent save? Curious.
cletus
For one way parent to child relationship you need a @JoinTable for the mapping between them.
Timo