views:

452

answers:

3

I have the following class

public class ElementBean {
 private String link;
 private Set<ElementBean> connections;
}

I need to create a map table where elements are mapped to each other in a many-to-many symmetrical relationship.

@ManyToMany(targetEntity=ElementBean.class)
@JoinTable(
    name="element_elements",
    joinColumns=@JoinColumn(name="FROM_ELEMENT_ID", nullable=false),
    inverseJoinColumns=@JoinColumn(name="TO_ELEMENT_ID", nullable=false)
)
public Set<ElementBean> getConnections() {
    return connections;
}

I have the following requirements

When element A is added as a connection to Element B, then element B should become a connection of Element A. So A.getConnections() should return B and B.getConnections() should return A. I do not want to explicitly create 2 records one for mapping A to B and another for B to A.

Is this possible?

UPDATE : Thank you for all the suggestions.
When I try @Pascal's suggestion two records are created as follows when I try to connect element 1 with element 2.

FROM_ELEMENT_ID, TO_ELEMENT_ID
1, 2
2, 1

I want the records to be symmetrical where 1,2 is the same as 2,1

How is this issue dealt with?

Update I explicitly created a map bean

class ConnectionBean {
    ElementBean from;
    ElementBean to;
}

@NotNull
@ManyToOne
@JoinColumn(name="FROM_ELEMENT_ID", nullable=false)
public ElementBean getFrom() {
    return from;
}

@NotNull
@ManyToOne
@JoinColumn(name="TO_ELEMENT_ID", nullable=false)
public ElementBean getTo() {
    return to;
}

I updated the ElementBean to

public class ElementBean {
    private String link;
    private Set<ConnectionBean> from;
    private Set<ConnectionBean> to;
}

@OneToMany(mappedBy="from", fetch=FetchType.LAZY)
public Set<ConnectionBean> getFrom() {
    return from;
}

@OneToMany(mappedBy="to", fetch=FetchType.LAZY)
public Set<ConnectionBean> getTo() {
    return to;
}

Here I can control the insertion and deletion. For example before inserting a new ConnectionBean, I check to see if a connection exists between elements A & B by checking the Connection table for records where
((from == A && to == B) || (from == B && to == A))
before insertion.

A: 

This will be solved by hibernate's first level cache to keep track of the references as long as you map the m-2-m from both sides.

cws
@cws - Not happening here.
sammichy
A: 

When working with many to many, you don't have to worry too much about duplication as hibernate will handle that for you. You do have to worry about making sure you object model is consistent and hibernate won't necessarily help with that.

In your "addConnection" method - you should not have a getConnections method that returns the actual connection, rather return a ready only version, you'll need to make sure you handle both sides of the relationship

addConnection(ElementBean element) {
    if (element ==null) { 
        throw new IllegalArgumentException("cannot add null element"); 
    }
    connections.add(element);
    element.getConnections0().add(this);
}

This means your requirement will hold and when it saves it will save okay. It will not save twice as it will notice that the one is a back reference for the other. You should probably make the getConnections0() private or package.

Michael Wiles
@Michael - Not sure what you mean by 'ready only version'. Do you suggest to override the getConnections() with getConnections'0'()? I have not defined a DAO class for the element_elements, it is taken care of by hibernate. Can you please elaborate?
sammichy
Firstly, you should configure hibernate to rather use field access than method access. Field access will mean hibernate will write directly to the field and not go via the method. This will allow you to customise the method.The problem with many-to-many and many-to-one is always the back reference. If someone adds only the forward reference without putting the back reference in as well your object model is going to be out of sync.That's why you return a read only version of the collection via getConnections. So they are forced to use your controlled methods when they want to add something
Michael Wiles
A: 

When working with bi-directional links, you need to take care of the link on both sides and, quoting the documentation:

Many developers program defensive and create a link management methods to correctly set both sides.

So in your case, I'd suggest to add the following methods:

public Set<ElementBean> getConnections() {
    return connections;
}

public void setConnections(Set<ElementBean> connections) {
    this.connections = connections;
}

public void addToConnection(ElementBean connection) {
    this.getConnections().add(connection);
    connection.getConnections().add(this);
}

public void removeFromConnection(ElementBean connection) {
    this.getConnections().remove(connection);
    connection.getConnections().remove(this);
}
Pascal Thivent
@Pascal - See update for comments.
sammichy