简体   繁体   中英

Hibernate Cascading problems with OneToMany

I'm trying to understand how to properly annotate classes to get cascading to work.

Here is a simple parent/children scenario.

Child:

@Entity
@Table(name = "testChild")
public class testChild implements Serializable {
    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    @Column(name = "uuid", unique = true, nullable = false)
    @JsonProperty
    private String uuid;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name="parent_uuid", nullable=false)
    @Cascade(value={CascadeType.ALL})
    @JsonIgnore
    private testParent parent;

    @Column(name = "name")
    @JsonProperty
    private String name;    

    public testChild (testParent parent, String name) {
        this.uuid = UUID.randomUUID().toString();
        this.name = name;
        this.parent = parent;
    }

    public String getUuid() {
        return this.uuid;
    }

    public void setUuid(String uuid) {
        this.uuid = uuid;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public testParent getParent () {
        return this.parent;
    }

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

Parent:

    @Entity
@Table(name = "testParent")
public class testParent implements Serializable {
    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    @Column(name = "uuid", unique = true, nullable = false)
    @JsonProperty
    private String uuid;        // This is the figure uuid:<number> 

    @Column(name = "name")
    @JsonProperty
    private String name;    

    @Column(name = "children")
    @OneToMany(fetch = FetchType.EAGER, mappedBy = "parent", orphanRemoval=true, cascade = javax.persistence.CascadeType.ALL)
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE})
    @JsonProperty
    private List <testChild> children;    

    public testParent (String name) {
        this.uuid = UUID.randomUUID().toString();
        this.name = name;
        this.children = new ArrayList<>();
    }

    public String getUuid() {
        return this.uuid;
    }

    public void setUuid(String uuid) {
        this.uuid = uuid;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List <testChild> getChildren () {
        return this.children;
    }

    public void setChildren (List <testChild> children) {
        this.children = children;
    }

    public testChild addChild(String childname) {
        testChild c = new testChild(this, childname);
        this.children.add(c);
        return c;
    }
}

And the test code:

testParent p = new testParent("Dan");
sid = p.getUuid();

testChild c1 = p.addChild("Dan jr.");
testChild c2 = p.addChild("Dannielle");
session.save(p);

My problem is that it's resulting in an update to the (first) child instead of an insert:

DEBUG org.hibernate.SQL - insert into testParent (name, uuid) values (?, ?)
DEBUG org.hibernate.SQL - update testChild set name=?, parent_uuid=? where uuid=?

Of course this throws an error. I have tried quite a few combinations for Cascade in both the parent and child classes.

The only thing that does work is if I explicitly "session.save(object)" all the time. ie Create the parent - save, create child1 - save, create child2 - save, finally save the parent again.

I'd really like to know how to tell Hibernate to simply save everything from the top down.

Tried a lot of different suggestions from this site and others, but nothing seems to be working.

You have to explicitly use the setter for parent and child.

Here is what you have to do:

 testParent p = new testParent("Dan");

 testChild c1 = new testChild("Dan");
 testChild c2 = new testChild("Dannielle");

 c1.setParent(p);
 c2.setParent(p);

 p.getChildren().add(c1);
 p.getChildren().add(c2);
 session.save(p);

You've also probably mixed up the entity cascades. On your OneToMany side, ie testParent , you need to have CascadeType.ALL or include CascadeType.PERSIST. You want to say that whenever you want to 'insert' or 'update' or 'delete' a parent, please also do the same action on the children.

The save of the parent should cascade the save to its referenced children as well.

I have a partial answer. It fixes the problem, but I'm still a little confused. The problem has to do with using the generated ID's. I was also trying to set them manually and that I think was breaking the mappings between parent and child.

Here is some working code:

    try {
        testParent p = new testParent("Dan");
        sid = p.getUuid();

        testChild c1 = new testChild("Dan Jr.");
        testChild c2 = new testChild("Danielle");

        c1.setParent(p);
        c2.setParent(p);
        p.getChildren().add(c1);
        p.getChildren().add(c2);

        session.save(p);            
        tx.commit();

    } catch(Exception e) {
        tx.rollback();
        log.error("Exception occured. "+e.getMessage());
        assertTrue(false);
    }

But it only works when I do two things:

  1. Remove the @GeneratedValue and @GenericGenerator annotations from the class definitions.
  2. Make sure that my constructors manually create these id fields.

If I don't do that, then it seems that setUuid does not update the uuid field when persisted to the DB. The object gets the new id, but only the original generated ID is saved.

I wonder if this is the best way to do this.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM