Notes on Java

MANAGING THE BIDIRECTIONAL RELATIONSHIP

Assume that there is a one-to-many relationship between A pojo and B pojo.

 

A can contain multiple B objects.

 

Class A can be designed as follows:

public class A {
    
    @OneToMany(cascade={CascadeType.ALL}, mappedBy="a")
    private List<B> bList =
        new ArrayList<B>();

    public A() {
    }

    public void setBList(List<B> bList) {
        this.bList = bList;
    }

    public List<B> getBList() {
        return bList;
    }

    public void addB(B b) {
        getBList().add(b);
        b.setA(this);
    }

    public void removeB(B b) {
        getBList().remove(b);
        b.setA(null);
    }    

    public boolean equals(Object object) {
        if (object == this)
            return true;
        if ((object == null) || !(object instanceof A))
            return false;

        final A a = (A)object;

        if (id != null && a.getId() != null) {
            return id.equals(a.getId());
        }
        return false;
    }

}

and Class B can be designed as follows:

public class B {

    @ManyToOne
    @JoinColumn(name = "A_ID", referencedColumnName = "ID")
    private A a;
    public B() {
    }
    
    public void setA(A a) {
        this.a = a;
    }

    public A getA() {
        return a;
    }

    public boolean equals(Object object) {
        if (object == this)
            return true;
        if ((object == null) || !(object instanceof B))
            return false;

        final B b = (B)object;

        if (id != null && b.getId() != null) {
            return id.equals(b.getId());
        }
        return false;
    }

}

As you can see, I’ve added the add and remove methods. These methods help us to add an element to the bList or remove an element from the bList.

 

And in order to maintain the bidirectional relationship correctly, inside the add and remove methods, I am calling the setA of class B. For the add operation, this call gives the A reference to the newly created B reference and for the remove operation, this call removes the A reference from the already existing B reference.

 

If we don’t do this cross referencing, then in the data model cache, the objects will not represent the tables correctly.

 

But is it enough? 

 

No. We should also do this cross referencing operation from the setA method as well, because the relationship can be established via B reference, too. So let’s change the classes like this:

public class A {
    
    @OneToMany(cascade={CascadeType.ALL}, mappedBy="a")
    private List<B> bList =
        new ArrayList<B>();

    public A() {
    }
    
    public void addB(B b) {
        addB(b, true);
    }

    void addB(B b, boolean set) {
        if (b != null) {
            getBList().add(b);
            if (set) {
                b.setA(this, false);
            }
        }
    }
    
    public void removeB(B b) {
        getBList().remove(b);
        b.setA(null);
    }    

    public void setBList(List<B> bList) {
        this.bList = bList;
    }

    public List<B> getBList() {
        return bList;
    }

    public boolean equals(Object object) {
        if (object == this)
            return true;
        if ((object == null) || !(object instanceof A))
            return false;

        final A a = (A)object;

        if (id != null && a.getId() != null) {
            return id.equals(a.getId());
        }
        return false;
    }


}

public class B {

    @ManyToOne
    @JoinColumn(name = "A_ID", referencedColumnName = "ID")
    private A a;
    public B() {
    }
    
    public void setA(A a) {
        setA(a, true);
    }
    
    void setA(A a, boolean add) {
        this.a = a;
        if (a != null && add) {
            a.addB(this, false);
        }
    }

    public A getA() {
        return a;
    }

    public boolean equals(Object object) {
        if (object == this)
            return true;
        if ((object == null) || !(object instanceof B))
            return false;

        final B b = (B)object;

        if (id != null && b.getId() != null) {
            return id.equals(b.getId());
        }
        return false;
    }

}

With the help of boolean variables (boolean add and boolean set), we prevent infinite loop.

So this code seems right, because it can successfully apply the bidirectional relationship from both sides.
 
But actually this code is partly right!
 
We forgot something…
 
Assume that we add a B object having id=1 to the bList of an A object.  I will call this B object B1 and A object A1.

 

At this point; A1.bList -> {B1} and B1.a = A1

 

Then later at one point of time, we add a B reference with id=1 again. Nothing prevents us from doing that, so we can send the B reference with the same id attribute to the addB method again.

 

At this point; A1.bList -> {B1, B1} and B1.a = A1

 

Did you see the inconsistency? There are two same B references in bList but there is only one B1 actually. So the object cache becomes inconsistent, does not represent the database correctly. Some ORM implementation can try to insert this second B1 object to the B table and you can get db exceptions like primary key violations.

 

We could use Set interface instead of List but it will not be enough, because we may want to update the B1 reference with the new B1 reference. The second B1 reference may have some changes and actually our aim is to update the B1 reference that is residing in bList. So, using Set will not be enough, we should replace the first B1 reference with the new B1 reference.

 

In order to do that the code should be as follows:

public class A {
    
    @OneToMany(cascade={CascadeType.ALL}, mappedBy="a")
    private List<B> bList =new ArrayList<B>();
    @Id
    @Column(nullable = false)
    private Long id;
    public A() {
    }
    
    public void addB(B b) {
        addB(b, true);
    }

    void addB(B b, boolean set) {
        if (b != null) {
            if(getBList().contains(b)) {
                getBList().set(getBList().indexOf(b), b);
            }
            else {
                getBList().add(b);
            }
            if (set) {
                b.setA(this, false);
            }
        }
    }
    
    public void removeB(B b) {
        getBList().remove(b);
        b.setA(null);
    }    

    public void setBList(List<B> bList) {
        this.bList = bList;
    }

    public List<B> getBList() {
        return bList;
    }
    
    public boolean equals(Object object) {
        if (object == this)
            return true;
        if ((object == null) || !(object instanceof A))
            return false;

        final A a = (A)object;

        if (id != null && a.getId() != null) {
            return id.equals(a.getId());
        }
        return false;
    }

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

    public Long getId() {
        return id;
    }
}

public class B {

    @ManyToOne
    @JoinColumn(name = "A_ID", referencedColumnName = "ID")
    private A a;
    @Id
    @Column(nullable = false)
    private Long id;
    
    public B() {
    }
    
    public void setA(A a) {
        setA(a, true);
    }
    
    void setA(A a, boolean add) {
        this.a = a;
        if (a != null && add) {
            a.addB(this, false);
        }
    }

    public A getA() {
        return a;
    }

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

    public Long getId() {
        return id;
    }
    
    public boolean equals(Object object) {
        if (object == this)
            return true;
        if ((object == null) || !(object instanceof B))
            return false;

        final B b = (B)object;

        if (id != null && b.getId() != null) {
            return id.equals(b.getId());
        }
        return false;
    }
}

In addB method, we are checking whether there is a B object with the same id and if there is we are replacing it with the new B object parameter.

This implementation is perfectly fine and can be used for bidirectional relationships between pojos.

About these ads

November 3, 2008 - Posted by | JPA |

No comments yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: