Hibernate : Saving Collections, Lazy and Eager loading

In the previous post we have seen how to Embed an object. Lets take a scenario of saving a collection, for eg. if there is a collection of address and we will be storing these addresses in a separate table with id as a foreign key. Lets change our DTO Employee:


 package com.hibernate.dto;

import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.AttributeOverrides;
import javax.persistence.AttributeOverride;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
@Table(name = "employee_details")
public class Employee {

    @Id
    @Column(name = "Id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    @Column(name = "Name")
    private String name;

    @ElementCollection
    private Set<Address> listOfAddress = new HashSet<Address>();

    public Set<Address> getListOfAddress() {
        return listOfAddress;
    }

    public void setListOfAddress(Set<Address> listOfAddress) {
        this.listOfAddress = listOfAddress;
    }

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

}

Our Address class remains unchanged, whih is marked Embeddable. Here in Employee.java we are now using a collection. Notice that we are defining the Collection not as a implementation but as a generic interface. We are creating the collection object as a implementation (HashSet). In order to tell hibernate that the listofAddress to be saved in a different table we use @ElementCollection. Also since the the Address is already marked Embeddable so no need to make this object embeded. Lets modify out HibernateTest class:


package com.hibernate.test;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import com.hibernate.dto.Address;
import com.hibernate.dto.Employee;

public class HibernateTest {
    static SessionFactory sessionFactory;
    static Session session;

    public static void main(String[] args) {
        System.out.println("HibernateTest");
        Employee aEmployee = new Employee();
        aEmployee.setName("First User");
        
        Address address1=new Address();
        address1.setStreet("Street1");
        address1.setState("State1");
        address1.setCity("City1");
        address1.setPincode("123456");
        
        Address address2=new Address();
        address2.setStreet("Street2");
        address2.setState("State2");
        address2.setCity("City2");
        address2.setPincode("654321");
        
        aEmployee.getListOfAddress().add(address1);
        aEmployee.getListOfAddress().add(address2);
        
        sessionFactory = new Configuration().configure().buildSessionFactory();
        System.out.println("session factory created");
            session = sessionFactory.openSession();
            session.beginTransaction();
            session.save(aEmployee);
            session.getTransaction().commit();
            session.close();
        
    }

}

Output:


session factory created
Hibernate: insert into employee_details (Name) values (?)
Hibernate: insert into Employee_listOfAddress (Employee_Id, City_Present, Pincode_Present, State_Present, Street_Present) values (?, ?, ?, ?, ?)
Hibernate: insert into Employee_listOfAddress (Employee_Id, City_Present, Pincode_Present, State_Present, Street_Present) values (?, ?, ?, ?, ?)

As you can see, hibernate now created 2 tables – Employee_Details(name which is mentioned in the @Table) and Employee_listOfAddress (concatenated the class name Employee + the variable name listOdAddress) Also notice that the Employee_Id (concatenated Employee class name + the variable name -id) is taken as the foreign key in the new table.

If you want to change the default name of the address table, you can use @JoinTable . For eg.


@ElementCollection
    @JoinTable(name="Employee_Address",
                joinColumns=@JoinColumn(name="Id")
               )
    private Set<Address> listOfAddress = new HashSet<Address>();

Now the table “Employee_Address” will be created instead with the foreign key named “Id”. If you see the DB table, there is no primary key for Employee_address table. In order to have an primary key we need to use an collection which can be parsed using index, like ArrayList. HashSet has no index.


@GenericGenerator(name = "hilo-gen", strategy = "hilo")
    @CollectionId(columns = { @Column(name="Address_Id") }, generator = "hilo-gen", type = @Type(type="long"))
    private Collection<Address> listOfAddress = new ArrayList<Address>();

    public Collection<Address> getListOfAddress() {
        return listOfAddress;
    }

    public void setListOfAddress(Collection<Address> listOfAddress) {
        this.listOfAddress = listOfAddress;
    }

Notice the @GenericGenerator and @CollectionId annotations. These are hibernate specific. Uptil now we were using annotation from java.persistance which is transferable to any JPA provider. “Address_Id” will be the name of the primary key in table. The way that primary key is generated is defined by @GenericGenerator. “hilo” is one of the strategy hibernate use to generate. The type of this column is defines a long.

Output:


session factory created
Hibernate: insert into employee_details (Name) values (?)
Hibernate: select next_hi from hibernate_unique_key for update
Hibernate: update hibernate_unique_key set next_hi = ? where next_hi = ?
Hibernate: insert into Employee_Address (Id, Address_Id, City_Present, Pincode_Present, State_Present, Street_Present) values (?, ?, ?, ?, ?, ?)
Hibernate: insert into Employee_Address (Id, Address_Id, City_Present, Pincode_Present, State_Present, Street_Present) values (?, ?, ?, ?, ?, ?)

Hibernate Proxy Object:  An object proxy is just a way to avoid retrieving an object until you need it.

Lets fetch the data from DB using hibernate. We will do a get() method call usually, like following:


Employee bEmployee = new Employee();
            bEmployee=(Employee) session.get(Employee.class,1);            
            System.out.println(bEmployee.getName());

 

When we get session.get(..), an object was returned which we casted to Employee. Now there are 2 ways this object would have returned. Either it will have the Address list complete when called first call itself (EAGER)  or the address list will be populated only when a getListOfAddress() method is called(LAZY). By default hibernate used LAZY FETCH. Lets confirm this by an closing the session before calling getListOfAddress(). Here is the full HibernateTest class:


package com.hibernate.test;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import com.hibernate.dto.Address;
import com.hibernate.dto.Employee;

public class HibernateTest {
static SessionFactory sessionFactory;
static Session session;

public static void main(String[] args) {
System.out.println("HibernateTest");
Employee aEmployee = new Employee();
aEmployee.setName("First User");

Address address1=new Address();
address1.setStreet("Street1");
address1.setState("State1");
address1.setCity("City1");
address1.setPincode("123456");

Address address2=new Address();
address2.setStreet("Street2");
address2.setState("State2");
address2.setCity("City2");
address2.setPincode("654321");

aEmployee.getListOfAddress().add(address1);
aEmployee.getListOfAddress().add(address2);

sessionFactory = new Configuration().configure().buildSessionFactory();
System.out.println("session factory created");
session = sessionFactory.openSession();
session.beginTransaction();
session.save(aEmployee);
session.getTransaction().commit();
session.close();

session = sessionFactory.openSession();
Employee bEmployee = new Employee();
bEmployee=(Employee) session.get(Employee.class,1);
System.out.println("Name=>"+bEmployee.getName());
session.close();
System.out.println("SIZE=>"+bEmployee.getListOfAddress().size());
}

}

Output:


session factory created
Hibernate: insert into employee_details (Name) values (?)
Hibernate: select next_hi from hibernate_unique_key for update
Hibernate: update hibernate_unique_key set next_hi = ? where next_hi = ?
Hibernate: insert into Employee_Address (Id, Address_Id, City_Present, Pincode_Present, State_Present, Street_Present) values (?, ?, ?, ?, ?, ?)
Hibernate: insert into Employee_Address (Id, Address_Id, City_Present, Pincode_Present, State_Present, Street_Present) values (?, ?, ?, ?, ?, ?)
Hibernate: select employee0_.Id as Id1_1_0_, employee0_.Name as Name2_1_0_ from employee_details employee0_ where employee0_.Id=?
Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.hibernate.dto.Employee.listOfAddress, could not initialize proxy - no Session

This confirms that hibernate uses LAZY initialization by default. In order to make hibernate load everything in one get() call, we have to use EAGER fetch like following:


@ElementCollection(fetch=FetchType.EAGER)
@JoinTable(name="Employee_Address",
joinColumns=@JoinColumn(name="Id")
)
@GenericGenerator(name = "hilo-gen", strategy="hilo")
@CollectionId(columns = { @Column(name="Address_Id") }, generator = "hilo-gen", type = @Type(type="long"))
private Collection<Address> listOfAddress = new ArrayList<Address>();

Now everything will be loaded in one go. In both cases, what hibernate does at the backend is that it doesnt give you the actual Employee object, rather gives you a proxy object with similar methods.

Grab the code for this tutorial from my GitHub repository.

 

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

%d bloggers like this: