Search This Blog

Monday 12 December 2011

Creating a many to many association - BiDirectional

Continuing from the last post, I shall now make the Person Chocolate relation bidirectional. This means that chocolate will also hold a collection of its fans. Instead of using a set here, I decided to use a bag. The updated java files are as below:
public class People {
    private Integer id;
    private String name;
    private Set<Chocolate> favouriteChocolates = new HashSet<Chocolate>();
    
    public synchronized void addChocolateFanRelation(final Chocolate chocolate) {
        chocolate.addNewFanToChocolate(this);
        this.getFavouriteChocolates().add(chocolate);
    }
    
    private synchronized void removeLink(final Chocolate chocolate) {
        this.getFavouriteChocolates().remove(chocolate);
        chocolate.removeFanFromChocolate(this);
    }
    
    public synchronized void removeFanChocolateRelation(final Chocolate chocolate) {
        this.removeLink(chocolate);
    }
    @Override
    public int hashCode() {
        int hash = this.getName().hashCode();
        hash = hash * 17 + this.getName().hashCode();
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        boolean isEqual = false;
        if (obj instanceof People) {
            People people = (People) obj;
            isEqual = people.getName().equals(this.getName());
        }
        return isEqual;
    }
//setter getter methods
}
As can be seen the code to delete and add is more complex than before. The code for Chocolate class is as below
public class Chocolate {
    private Integer id;
    private String name;
    private String brand;
    private Collection<People> fans = new ArrayList<People>();
    
    protected synchronized void addNewFanToChocolate(People fan) {
        boolean alreadyPresent = false;
        Collection<People> fanCollection = this.getFans();
        for (Iterator<People> fanIterator = fanCollection.iterator(); 
                fanIterator.hasNext();) {
            People people= fanIterator.next();
            if (people.equals(fan) ) {
                alreadyPresent = true;
                break;
            }            
        }
        if(!alreadyPresent) {
            fanCollection.add(fan);
        }
    }
    
    protected synchronized void removeFanFromChocolate(People fan) {
        Collection<People> fanCollection = this.getFans();
        for (Iterator<People> fanIterator = fanCollection.iterator(); 
                 fanIterator.hasNext();) {
            People people= fanIterator.next();
            if (people.equals(fan) ) {
                fanIterator.remove();
                break;
            }            
        }
    }
    
    @Override
    public int hashCode() {
        int hash = this.getName().hashCode();
        hash = hash * 17 + this.getBrand().hashCode();
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        boolean isEqual = false;
        if (obj instanceof Chocolate) {
            Chocolate chocolate = (Chocolate) obj;
            isEqual = chocolate.getName().equals(this.getName())
                    && chocolate.getBrand().equals(this.getBrand());
        }
        return isEqual;
    }
//setter and getter methods
}
The mapping files are as below:
Chocolate.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.association.many_to_many.bidir">
    <class name="Chocolate" table="CHOCOLATE">
        <id name="id" type="integer">
            <column name="ID" />
            <generator class="identity" />
        </id>
        <property name="name" type="string">
            <column name="NAME" />
        </property>

        <property name="brand" type="string">
            <column name="BRAND" />
        </property>

        <bag name="fans" table="CHOCOLATE_FAN" inverse="true">
            <key column="CHOCOLATE_ID" />
            <many-to-many class="People" column="FAN_ID" />
        </bag>

    </class>
</hibernate-mapping>
The collection is defined with inverse attribute set to true. So the People entity is the owner of the association. The mapping for People is:
People.hbm.xml 
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.association.many_to_many.bidir">
    <class name="People" table="PEOPLE">
        <id name="id" type="integer">
            <column name="ID" />
            <generator class="identity" />
        </id>
        <property name="name" type="string">
            <column name="NAME" />
        </property>

        <set name="favouriteChocolates" table="CHOCOLATE_FAN" cascade="save-update">
            <key column="FAN_ID" foreign-key="CHOCOLATE_FAN_FK2"/>
            <many-to-many class="Chocolate" column="CHOCOLATE_ID" foreign-key="CHOCOLATE_FAN_FK1"/>
        </set>
    </class>
</hibernate-mapping>
As can be seen, the only change here is the cascade setting. The tables created here are same as the last post. I tried to access the records present in the database
static void testLoad() {
    Session session = sessionFactory.openSession();
    Chocolate chocolate1 = (Chocolate) session.get(Chocolate.class, 1);

    System.out.println("Chocolate " + chocolate1.getName()
            + " is from brand " + chocolate1.getBrand());
    System.out.println("Number of Fans: " + chocolate1.getFans().size());
    if (0 != chocolate1.getFans().size()) {
        for (People people : chocolate1.getFans()) {
            System.out.println("Fan is " + people.getId() 
                            + " ,Name: " + people.getName());
        }
    }
    Chocolate chocolate2 = (Chocolate) session.get(Chocolate.class, 2);
    System.out.println("Chocolate " + chocolate2.getName()
            + " is from brand " + chocolate2.getBrand());
    System.out.println("Number of Fans: " + chocolate2.getFans().size());
}
The output of above code is:
Chocolate Eclairs is from brand Cadburys
Number of Fans: 2
Fan is 1 ,Name: Naina
Fan is 2 ,Name: Naresh
Chocolate Melody is from brand Parles    
Number of Fans: 0
The select query that used to retrieve the chocolates is :
    select
        chocolate0_.ID as ID2_0_,
        chocolate0_.NAME as NAME2_0_,
        chocolate0_.BRAND as BRAND2_0_ 
    from
        CHOCOLATE chocolate0_ 
    where
        chocolate0_.ID=?
To display the names of the fans the records were fetched using a single query:
    select
        fans0_.CHOCOLATE_ID as CHOCOLATE2_1_,
        fans0_.FAN_ID as FAN1_1_,
        people1_.ID as ID0_0_,
        people1_.NAME as NAME0_0_ 
    from
        CHOCOLATE_FAN fans0_ 
    left outer join
        PEOPLE people1_ 
            on fans0_.FAN_ID=people1_.ID 
    where
        fans0_.CHOCOLATE_ID=?
As can be seen a join was needed between PEOPLE table and our join table to get all the chocolate fans. Similar thing happens when we navigate from the direction of People:
static void testLoadViaPeople() {
    Session session = sessionFactory.openSession();
    People people = (People) session.get(People.class, 1);
    System.out.println("Chocolate " + people.getName());
    System.out.println("Number of Fans: " + people.getFavouriteChocolates().size());
    session.close();
}
The queries generated are:
2313 [main] DEBUG org.hibernate.SQL  - 
    select
        people0_.ID as ID0_0_,
        people0_.NAME as NAME0_0_ 
    from
        PEOPLE people0_ 
    where
        people0_.ID=?
2375 [main] DEBUG org.hibernate.SQL  - 
    select
        favouritec0_.FAN_ID as FAN1_1_,
        favouritec0_.CHOCOLATE_ID as CHOCOLATE2_1_,
        chocolate1_.ID as ID2_0_,
        chocolate1_.NAME as NAME2_0_,
        chocolate1_.BRAND as BRAND2_0_ 
    from
        CHOCOLATE_FAN favouritec0_ 
    left outer join
        CHOCOLATE chocolate1_ 
            on favouritec0_.CHOCOLATE_ID=chocolate1_.ID 
    where
        favouritec0_.FAN_ID=?
I tried the above example with a Set instead of the bag option and it worked just as fine.

No comments:

Post a Comment