JPA @OneToMany -> Parent - Child Reference (Foreign Key)
Asked Answered
L

5

46

i have a Question about referencing ParentEntities from Child Entites ir If i have something like this:

Parent.java:

@Entity(name ="Parent")
public class Parent {
    @Id
    @Generate.....
    @Column
    private int id;
   
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "parent")
    private Set<Child> children;

    simple ... getter and setter ...
}

And the Child.java:

@Entity(name ="Child")
public class Child{
    @Id
    @Generate....
    @Column
    private int id;

    @ManyToOne
    private Parent parent;

    ... simple getter an setter
}

Following Tables are going to be created:

Parent:
     int id

Child:
     int id
     int parent_id (foreign key: parent.id)

Ok, so far, everthings fine. But when it comes to using this Reference from Java, i would think, you can do something like this.

@Transactional
public void test() {
    Parent parent = new Parent();
    Child child = new Child();

    Set<Child> children = new HashSet<Child>();
    children.add(child);
    parent.setChildren(children);
    entityManager.persist(parent);
}

which leads to this in Database:

Parent:
     id
     100

Child
     id     paren_id
     101    100

But thats not the case, you have to explicity set the Parent to the Child (which, i would think, the framework could probably do by itself).

So whats really in the database is this:

Parent:
     id
     100

Child
     id     paren_id
     101    (null)

cause i haven't set the Parent to the Child. So my Question:

Do I really have to do sth. like this?

Parent.java:

...
setChildren(Set<Child> children) {
    for (Child child : children) {
        child.setParent.(this);
    }

    this.children = children;
}
...

Edit:

According to the fast Replies i was able to solve this Problem by using the @JoinColumn on the Reference-Owning Entity. If we take the Example from above, i did sth. like this:

Parent.java:

@Entity(name ="Parent")
public class Parent {
    @Id
    @Generate.....
    @Column
    private int id;
    
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinColumn(name= "paren_id")
    private Set<Child> children;
    
    simple ... getter and setter ...
}

And the Child.java:

@Entity(name ="Child")
public class Child{
    @Id
    @Generate....
    @Column
    private int id;

    ... simple getter an setter
}

Now if we do this:

@Transactional
public void test() {
    Parent parent = new Parent();
    Child child = new Child();

    Set<Child> children = new HashSet<Child>();
    children.add(child);

    parent.setChildren(children);
    entityManager.persist(parent);
}

The Reference is correctly set by the Parent:

Parent:
     id
     100

Child
     id     paren_id
     101    100
Locomotive answered 2/3, 2012 at 12:55 Comment(2)
did you have parent on child entity? if yes how they configured. With no parent I get error(StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1). If I add parent with no annotations it shows error also(Mapping error). With setting @manytoone on parent field of child i get error on save( ORA-00904: "REPORT_REPORT_ID": недопустимый идентификатор). my fk and its pk on id named report_id but I don't know where comes report_report_id.Kistler
(Using EclipseLink JPA implementation). It's worth noting you still need the @Column(name="paren_id")private long parenId; mapping on your child for this wot work.Compulsory
D
16

Do I really have to do sth. like this?

That is one strategy, yes.

On bi-directional relationships there is an "owning" and a "non-owning" side of the relationship. Because the owning side in your case is on Child, you need to set the relationship there for it to be persisted. The owning side is usually determined by where you specify @JoinColumn, but it doesn't look like you're using that annotation, so it's likely being inferred from the fact that you used mappedBy in the Parent annotation.

You can read a lot more about this here.

Doll answered 2/3, 2012 at 13:2 Comment(0)
H
7

It still seems to be the case. In parent Entity you can have something like

@PrePersist
private void prePersist() {
   children.forEach( c -> c.setParent(this));
}

in order to avoid repeating code for setting child/parent relationship elsewhere in code.

Hodgkins answered 22/11, 2017 at 12:44 Comment(6)
!! nice man after i stuck alomost 2 week. i try to sent json from client and convert to object using spring's @RequestBody then save to db i dont want to manage relation by my self (set parent to child -- is not make sense for me)Merce
!!! update !!! link about @PrePersist (you can manage by @EntityListener) see -- concretepage.com/java/jpa/…Merce
Nice man !! This has saved lot of effort and performance optimized too.Vitiate
For some reason I am getting NullPointerException on @PrePersist at children.forEach.Richierichlad
@Richierichlad Maybe your children is then null. Initialize it first?Hodgkins
@Hodgkins I have not just initialized but added about 70 children which only gets nullified by hibernate on @PrePersist. I see all of them even @PreUpdate @PostPersist. Hibernate sets them null by reflection as I am using kotlin and the child is not nullable mutableList.Richierichlad
K
4

Yes, that is the case. JPA does not keep care about consistency of your entity graph. Especially you have to set it to the owner side of bidirectional relationship (in your case to the parent attribute of Child).

In JPA 2.0 specification this is said with following words:

Note that it is the application that bears responsibility for maintaining the consistency of run- time relationships—for example, for insuring that the “one” and the “many” sides of a bidi- rectional relationship are consistent with one another when the application updates the relationship at runtime.

Kink answered 2/3, 2012 at 12:59 Comment(0)
L
3

We ran into a problem while persisting a simple object graph like the one shown above. Running in H2 everything would work, but when we ran against MySQL the "paren_id" in the child table (defined in the @JoinColumn annotation) wasn't getting populated with the generated id of the parent - even though it was set as a non-null column with a foreign key constraint in the DB.

We'd get an exception like this:

org.hibernate.exception.GenericJDBCException: Field 'paren_id' doesn't have a default value

For anyone else who might run into this, what we eventually found was that we had to another attribute to the @JoinColumn to get it to work:

@JoinColumn(name="paren_id", nullable=false)
Leaseholder answered 15/8, 2014 at 9:10 Comment(3)
Putting nullable = false also fixed my issues with OneToMany. For my OneToOne however putting that I still get the exception . Any advice for how to handle that case?Seigneury
I also tried this with no luck, I am also using MySQL if that's the problem. Can you share the code?Richierichlad
Hmm, sometimes people give a name to "parent_id" and run the app. After run db creates constraints. Later they change the name of "parent_id" field on the object and run the app. I think the old field remains in the be and since it is has constraint, and will mislead by throwing error message that parent id is missingStarstarboard
G
0

If I am getting you correctly, according to EntityManager, if you want it to manage the transaction's insert order your have to "tell him" that it should persist the children too. And you are not doing that, so "he" doesn't know what to persist, but your parent's child list is not empty so "he" takes it has correct but the stored value is null.

So you should consider do something like:

... begin, etc
em.persist(child)
em.persist(parent)

do what you want with the parent object here then commit and this should work for similar cases too.

Granlund answered 8/4, 2016 at 14:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.