views:

41

answers:

1

EDIT: Solved, but I don't know what was different now. If anyone can explain what happened differently, I'd appreciate it.

The way it's working now is by merging only the Parent, not the Child at all, like so:

Parent parent = child.getParent();
parent.getChildren().add(child);
getJpaTemplate().merge(parent);

Now, it seems to be working, but I don't quite get why this would work while my previous attempt didn't.

Original attempt/question:

I'm having an issue with Spring JPA and IndirectSets. I have two entities, Parent and Child, defined below. I have a Spring form in which I'm trying to create a new Child and link it to an existing Parent, then have everything reflected in the database and in the web interface. What's happening is that it gets put into the database, but the UI doesn't seem to agree.

The two entities that are linked to each other in a OneToMany relationship like so:

@Entity
@Table(name = "parent", catalog = "myschema", uniqueConstraints = @UniqueConstraint(columnNames = "ChildLinkID"))
public class Parent {
    private Integer id;
    private String childLinkID;

    private Set<Child> children = new HashSet<Child>(0);

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    public Integer getId() {
        return this.id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Column(name = "ChildLinkID", unique = true, nullable = false, length = 6)
    public String getChildLinkID() {
        return this.childLinkID;
    }

    public void setChildLinkID(String childLinkID) {
        this.childLinkID = childLinkID;
    }

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "parent")
    public Set<Child> getChildren() {
        return this.children;
    }

    public void setChildren(Set<Child> children) {
        this.children = children;
    }
}

@Entity
@Table(name = "child", catalog = "myschema")
public class Child extends
    private Integer id;
    private Parent parent;

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    public Integer getId() {
        return this.id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "ChildLinkID", referencedColumnName = "ChildLinkID", nullable = false)
    public Parent getParent() {
        return this.parent;
    }

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

And of course, assorted simple properties on each of them. Now, the problem is that when I edit those simple properties from my Spring interface, everything works beautifully. I can persist new entities of these types and they'll appear when using the JPATemplate to do a find on, say, all Parents (getJpaTemplate().find("select p from Parent p")) or on individual entities by ID or another property.

The problem I'm running into is that now, I'm trying to create a new Child linked to an existing Parent through a link from the Parent's page. Here's the important bits of the Controller (note that I've placed the JPA foo in the controller here to make it clearer; the actual JpaDaoSupport is actually in another class, appropriately tiered):

protected Object formBackingObject(HttpServletRequest request) throws Exception {
    String parentArg = request.getParameter("parent");
    int parentId = Integer.parseInt(parentArg);
    Parent parent = getJpaTemplate().find(Parent.class, parentId);
    Child child = new Child();
    child.setParent(parent);
    NewChildCommand command = new NewChildCommand();
    command.setChild(child);
    return command;
}

protected ModelAndView onSubmit(Object cmd) throws Exception {
    NewChildCommand command = (NewChildCommand)cmd;
    Child child = command.getChild();
    child.getParent().getChildren().add(child);
    getJpaTemplate().merge(child);
    return new ModelAndView(new RedirectView(getSuccessView()));
}

Like I said, I can run through the form and fill in the new values for the Child -- the Parent's details aren't even displayed. When it gets back to the controller, it goes through and saves it to the underlying database, but the interface never reflects it. Once I restart the app, it's all there and populated appropriately.

What can I do to clear this up? I've tried to call extra merges, tried refreshes (which gave a transaction exception), everything short of just writing my own database access code. I've made sure that every class has an appropriate equals() and hashCode(), have full JPA debugging on to see that it's making appropriate SQL calls (it doesn't seem to make any new calls to the Child table) and stepped through in the debugger (it's all in IndirectSets, as expected, and between saving and displaying the Parent the object takes on a new memory address). What's my next step?

A: 

I'm not sure this has something to do with your problem but why do you have a childLinkID property in your Parent class? This seems really weird, especially since you're using the corresponding column for the join. I wonder how things can work with this mapping and I would fix this. Can you try without (also remove the referencedColumnName, you don't need this)?

And by the way, why don't you set both sides of the link between Parent and Child in the same method? This makes the code hard to read and to maintain IMO.


You edited the question while I was answering and changed the cascading from Parent to Child but this looks like a work around of the real issue and I'm not sure you won't face other problems. Please try to fix your mapping as I suggested.

Pascal Thivent
The reason is that in the real model that was causing the issue, which I simplified to this, I have real-world data that needs to be used on its own as a property of the parent class, which is the direct mapping to the child here. In fact, there are two other child classes I have to implement, each of which refer back to a /different/ property of the parent. (I didn't design the data model, to head off your next question/suggestion.)To answer the second question, it's because I added the link from parent to child as a fix to this problem (and while fixing it). That's changed in real code.
Jon
I also changed the entities so that I moved the JPA annotations from the getXXX methods to the variable declarations themselves, which I neglected to mention. Could that have fixed the issue as well?
Jon
@Jon 1) I'm not sure I understood the need for the `childLinkID` property and I wonder how JPA handles the whole thing 2) *"it's because I added the link from parent to child as a fix to this problem"* - that's **required** with bi-directional associations, you have to set both sides of the link. 3) *"I also changed the entities so that I moved the JPA annotations from the getXXX methods to the variable declarations themselves"* - this doesn't change anything. Actually I still think that there is something esoteric and that changing the cascading worked it around.
Pascal Thivent
@Pascal I may not be understanding your question. In my real model, childLinkID is a business logic variable. It also happens to be the foreign key reference for another table. What about it should cause difficulty?
Jon
Interestingly, removing the referencedColumnName, I get: Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`myschema`.`child`, CONSTRAINT `fk_child_parent` FOREIGN KEY (`ChildLinkID`) REFERENCES `parent` (`ChildLinkID`) ON DELETE NO ACTION ON UPDATE NO ACTION)
Jon