Hibernate - InshortsView

|
Divnesh BLOG
Hibernate is a framework which provides some abstraction layer, meaning that the programmer does not have to worry about the implementations, Hibernate does the implementations for you internally like Establishing a connection with the database, writing query to perform CRUD (Create, read, update and delete) operations etc.
Hibernate is an open-source, non-invasive, light-weight java ORM(Object-relational mapping) framework to develop objects which are independent of the database software and make independent persistence logic in all JAVA, JEE.
Functionalities:
  • Hibernate framework support Auto DDL operations.
  • Hibernate supports Auto Primary key generation. 
  • Hibernate framework is independent of Database because it supports HQL (Hibernate Query Language)
  • In Hibernate, Exception Handling is not mandatory
  • Hibernate supports Cache Memory
  • Hibernate is a ORM tool means it support Object relational mapping
  • In hibernate, each record is represented as a Object
Requirement 1: Hibernate CRUD
InfyBank asks you to build a Hibernate application to solve the below requirements.
R.1.1 Insert the customer details into CUSTOMER table
R.1.2 Retrieve the customer details from CUSTOMER table
R.1.3 Update the phone number of a customer in CUSTOMER table
R.1.4 Delete a customer from CUSTOMER table
Below is the CUSTOMER table structure used by InfyBank.
Below is the CustomerEntity class diagram which you should use to map it with CUSTOMER table.

Performing Persist Operation
implement the below files in a Hibernate project to solve the InfyBank requirement R.1.1, inserting(persisting) a customer record into CUSTOMER table.
CustomerEntity.java
@Entity
@Table(name="customer")
public class CustomerEntity {
  @Id
  private Integer customerId;
  private String customerName;
  @Temporal(TemporalType.DATE)
  private Calendar dateOfBirth;
  @Transient
  private Integer age;
  private String address;
  @Column(name="phoneNumber")
  private Long contactNumber;
  //getters and setters
}
hibernate.cfg.xml
CustomerDAO.java
public class CustomerDao {
    /*Inserting a new Customer details into database*/
    public Integer addCustomerDetails()throws Exception{
        SessionFactory sessionFactory=null;
        Session session=null;
        try {
            sessionFactory=HibernateUtility.createSessionFactory();    //creating session factory
            session=sessionFactory.openSession();              //creating session instance
            CustomerEntity ce=new CustomerEntity();        //creating the entity instace
            ce.setCustomerId(5004);                //populating the entity instance
            ce.setCustomerName("Kiran");
            Calendar dob = Calendar.getInstance();
            dob.set(1998,9,16);
            ce.setDateOfBirth(dob);
            ce.setAddress("Chennai");
            ce.setContactNumber(9878579596l);
            session.getTransaction().begin();             //Begin the transaction
            session.persist(ce);                          //persist the entity in db
            session.getTransaction().commit();            //commit the transaction
       }catch (HibernateException exception) {   
            throw new Exception("DAO.TECHNICAL_ERROR");
       }catch (Exception exception) {   
            throw exception;
       }finally {
            if(session.isOpen()|| session!=null)
                  session.close();                         //Closing session instance
            }
       } 
       return customer.getCustomerId();
   }
}
The steps used in this project are:
  1. Hibernate Mapping - Create and map the entity class CustomerEntity.java
  2. Hibernate Configuration - Configure the database details in hibernate.cfg.xml
  3. Performing persist operation - Create CustomerDAO.java to insert a record
Hibernate Mapping Using Annotations
R.1.1 The first step is mapping the regular Java class with database table by converting the Java class into an entity class.  
You have to convert the given CustomerEntity.java class as an entity class so that it can be mapped with CUSTOMER table. You are going to use the same javax.persistence annotations, which you have used in JPA project, to create the entity class
@Entity
@Table(name="customer")
public class CustomerEntity {
 @Id
 private Integer customerId;
 private String customerName;
 @Temporal(TemporalType.DATE)
 private Calendar dateOfBirth;
 @Transient
 private Integer age;
 private String address;
 @Column(name="phoneNumber")
 private Long contactNumber;
 //getters and setters
}
Mapped to

Hibernate Mapping Using XML
Apart from annotations, Hibernate supports the usage of an external XML file as well to map the entity class with table.
To map your CustomerEntity.java with CUSTOMER table, you need to create an xml file with extension .hbm.xml.  The file should be named as CustomerEntity.hbm.xml by following the naming convention .hbm.xml




Hibernate Configuration Using XML
Having mapped your entity class(CustomerEntity.java) with database table, your second step to solve R.1.1 is about configuration.
As your application need to interact with Oracle database to perform the CRUD opertaions, your application should be informed about the location of the database server, database credentials and the entity classes that are mapped with database tables.  Configure these details in a xml file named hibernate.cfg.xml as shown below. 
Some of the properties used in the property tag are:
There are two ways of using mapping tag they are:

Hibernate Configuration Using Code: Programmatic Configuration
Instead of using hibernate.cfg.xml, the hibernate configuration can be provided programmatically using org.hibernate.cfg.Configuration class's
setProperty() method as shown below:
//sample configuration considering that Oracle is installed in localhost
Configuration configuration = new Configuration();
configuration.setProperty("hibernate.dialect","org.hibernate.dialect.Oracle10gDialect");
configuration.setProperty("hibernate.connection.driver_class","oracle.jdbc.driver.OracleDriver");
configuration.setProperty("hibernate.connection.url","jdbc:oracle:thin:@10.120.79.57:1521:georli01");
configuration.setProperty("hibernate.connection.username", "username");
configuration.setProperty("hibernate.connection.password", "password");

//configures entity class
configuration.addAnnotatedClass(entity.CustomerEntity.class);  //OR
configuration.addResource(“resources/CustomerEntity.hbm.xml”);
Here if entity classes are mapped using annotations then these classes are configured using addAnnotatedClass() method or if mapping is done is using XML then addResource() method is used.
Note: Besides xml and programmatic configration, Hibernate configuration can be done with properties file as well which is not discussed in this course
Performing Persist Operation
The steps used in CustomerDAO.java to perform the persist operation are:
  1. Create an instance of SessionFactory
  2. Create an instance of Session
  3. Define a transaction context
  4. Populate and persist the entity object inside an active transaction
  5. Close Session and SessionFactory

Step 1 : Create an instance of SessionFactory

In order to create the Session objects which represents the database connection in Hibernate, you need an instance of org.hibernate.SessionFactory Interface.  The role of SessionFactory in Hibernate is same as the role of EntityManagerFactory in JPA. Ideally your code should have one SessionFactory instance defined for each database.  
Below hibernateUtility.java file demonstrates the steps to create an instance of SessionFactory:
hibernateUtility.java
public class HibernateUtility {
    private static final String CONFIGURATION_LOCATION = "resources/hibernate.cfg.xml";
    private static SessionFactory sessionFactory = getSessionFactory();

    private static SessionFactory getSessionFactory() {
       try {
            if (sessionFactory==null) {

                // Step1 : Loading the configuration details from hibernate.cfg.xml
                Configuration configuration = new Configuration().configure(CONFIGURATION_LOCATION);

                // Step2 : Creating ServiceRegistry using the StandardServiceRegistryBuilder and Configuration defined in Step1
                ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();

                // Step3 : Creating the SessionFactory using the Configuration and serviceRegistry
                sessionFactory = configuration.buildSessionFactory(serviceRegistry);
            }
        } catch (Exception exception) {
              throw exception;
        }
        return sessionFactory;
    }

    public static SessionFactory createSessionFactory() {
        return getSessionFactory();
    }

    public static void closeSessionFactory() {
        if(!sessionFactory.isClosed() ||  sessionFactory!=null ){
             sessionFactory.close();
        }
    }
}

  • Loading the configurations - we will be loading the configurations from hibernate.cfg.xml file
  • Setting up and managing all the dependent services - we will be starting and managing all the standard services using ServiceRegistry, which is built using StandardServiceRegistryBuilder
  • Building the session factory - once all the dependencies are set up, the session factory can be built
Now, creating an instance of SessionFactory can be done in CustomerDAO.java as below:
public class CustomerDao {
    /*Inserting a new Customer details into database*/
    public Integer addCustomerDetails(Customer customer)throws Exception{
        SessionFactory sessionFactory=null;
        Session session=null;
        Integer customerId=null;
        try {
            sessionFactory=HibernateUtility.createSessionFactory();
            //....
        }catch (HibernateException exception) {   
            throw new Exception("DAO.TECHNICAL_ERROR");
        }catch (Exception exception) {   
            throw exception;
        }finally {
            if(session!=null || session.isOpen()){
                  session.close();
            }
        }
        return customerId;
    }
}

Step 2: Create an instance of Session

org.hibernate.Session interface is the primary interface used by Hibernate application to perform the persistence-related operations. The role of Session is same as the role of EntityManager in JPA. Any number of Session objects can be spawned from a single SessionFactory object.  Ideally your application should create a separate Session object for each unit of database work.  
Add below highlighted code in CustomerDAO.java for creating Session object.
public class CustomerDao {
    /*Inserting a new Customer details into database*/
    public Integer addCustomerDetails(Customer customer)throws Exception{
        SessionFactory sessionFactory=null;
        Session session=null;
        try {
            sessionFactory=HibernateUtility.createSessionFactory();
            session=sessionFactory.openSession();
            //...
       }catch (HibernateException exception) {   
            throw new Exception("DAO.TECHNICAL_ERROR");
       }catch (Exception exception) {   
            throw exception;
       }finally {
            if(session.isOpen()|| session!=null)
                  session.close();
            }
       }
       return customerId;
   }
}
The openSession() method of SessionFactory is used to obtain the Session.  

Step 3: Define a transaction context

You have to use org.hibernate.Transaction interface to define a transaction context in the same way how you have used EntityTransaction in JPA. 
Transaction interface provides below methods to logically group database operations as a unit.
  • begin() : Used to begin a transaction.
  • commit() : Used to save the changes in the database permanently.
  • rollback() : Used to rollback the changes.
Add below highlighted code in your CustomerDAO.java to define the transaction context in order to persist the customer.
public class CustomerDao {
    /*Inserting a new Customer details into database*/
    public Integer addCustomerDetails(Customer customer)throws Exception{
        SessionFactory sessionFactory=null;
        Session session=null;
        try {
            sessionFactory=HibernateUtility.createSessionFactory();
            session=sessionFactory.openSession();

            session.getTransaction().begin();
            //..code for persisting customer
            session.getTransaction().commit();

       }catch (HibernateException exception) {   
            throw new Exception("DAO.TECHNICAL_ERROR");
       }catch (Exception exception) {   
            throw exception;
       }finally {
            if(session.isOpen()|| session!=null)
                  session.close();
            }
       }
       return customerId;
   }
}

Step 4: Populate and persist the entity object inside an active transaction

The persist() method of Session should be invoked to persist your entity object (CustomerEntity.java).  The persist() method will in turn insert a record into the corresponding table with values available in entity object.
Add the below highlighted code in your CustomerDAO.java to populate CustomerEntity and to persist it.
public class CustomerDao {
    /*Inserting a new Customer details into database*/
    public Integer addCustomerDetails(Customer customer)throws Exception{
        SessionFactory sessionFactory=null;
        Session session=null;
        try {
            sessionFactory=HibernateUtility.createSessionFactory();
            session=sessionFactory.openSession();
            CustomerEntity ce=new CustomerEntity();
            ce.setCustomerId(5004);
            ce.setCustomerName("Kiran");
            Calendar dob = Calendar.getInstance();
            dob.set(1998,9,16);
            ce.setDateOfBirth(dob);
            ce.setAddress("Chennai");
            ce.setContactNumber(9878579596l);
            session.getTransaction().begin();
            session.persist(ce);
            session.getTransaction().commit();
       }catch (HibernateException exception) {   
            throw new Exception("DAO.TECHNICAL_ERROR");
       }catch (Exception exception) {   
            throw exception;
       }finally {
            if(session.isOpen()|| session!=null)
                  session.close();
            }
       }
       return customer.getCustomerId();
   }
}

Session interface provides one more method called save() to do persist.  It persists the given entity object and returns the primary key of the persisted object. 
sessionFactory=HibernateUtility.createSessionFactory();
session=sessionFactory.openSession();
CustomerEntity ce=new CustomerEntity();
ce.setCustomerId(customer.getCustomerId());
ce.setCustomerName(customer.getCustomerName());
ce.setDateOfBirth(customer.getDateOfBirth());
ce.setAddress(customer.getAddress());
ce.setContactNumber(customer.getContactNumber());
session.getTransaction().begin();
Integer customerPK = (Integer)session.save(ce);
session.getTransaction().commit();
Since save() returns the identifier value as an object of type Serializable, you need to type cast it as shown in the code.
The persist() or save() method causes the execution of SQL INSERT when you commit() the Hibernate Transaction.  The INSERT statement will contain values that were held by your entity object at the time of invoking persist() or save().

Step 5: Close Session and SessionFactory

Once your database transaction is done, it is recommended to close the Session so that the resources held by it, such as database connection and entity objects, become free.  Similarly, the SessionFactory should also be closed when your application no longer needs it. 
Add the below highlighted code in your CustomerDAO.java to close the Session object:
public class CustomerDao {
    /*Inserting a new Customer details into database*/
    public Integer addCustomerDetails(Customer customer)throws Exception{
        SessionFactory sessionFactory=null;
        Session session=null;
        try {
            sessionFactory=HibernateUtility.createSessionFactory();
            session=sessionFactory.openSession();
            CustomerEntity ce=new CustomerEntity();
            ce.setCustomerId(5004); 
            ce.setCustomerName("Kiran");
            Calendar dob = Calendar.getInstance();
            dob.set(1998,9,16);
            ce.setDateOfBirth(dob);
            ce.setAddress("Chennai");
            ce.setContactNumber(9878579596l);
            session.getTransaction().begin();
            session.persist(ce);
            session.getTransaction().commit();
       }catch (HibernateException exception) {   
            throw new Exception("DAO.TECHNICAL_ERROR");
       }catch (Exception exception) {   
            throw exception;
       }finally {
            if(session.isOpen()|| session!=null)
                  session.close();
            }
       } 
       return customer.getCustomerId();
   }
}
Below is the code to close SessionFactory object in hibernateutility.java
public class HibernateUtility {
    private static final String CONFIGURATION_LOCATION = "resources/hibernate.cfg.xml";
    private static SessionFactory sessionFactory = getSessionFactory();

    private static SessionFactory getSessionFactory() {
       try {
            if (sessionFactory==null) {

                // Step1 : Loading the configuration details from hibernate.cfg.xml
                Configuration configuration = new Configuration().configure(CONFIGURATION_LOCATION);

                // Step2 : Creating ServiceRegistry using the StandardServiceRegistryBuilder and Configuration defined in Step1
                ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();

                // Step3 : Creating the SessionFactory using the Configuration and serviceRegistry
                sessionFactory = configuration.buildSessionFactory(serviceRegistry);
            }
        } catch (Exception exception) {
              throw exception;
        }
        return sessionFactory;
    }

    public static SessionFactory createSessionFactory() {
        return getSessionFactory();
    }

    public static void closeSessionFactory() {
        if(!sessionFactory.isClosed() ||  sessionFactory!=null ){
             sessionFactory.close();
        }
    }
}
Requirement 1.2: Performing Retrieve Operatio
In order to solve requirement R.1.2, your application is going to reuse the same CustomerEntity.java and hibernate.cfg.xml files as the CUSTOMER table remains same.
Your CustomerDAO.java to retrieve the customer details for the given customer id should be:
public class CustomerDao {
   public Customer getCustomerDetails(Integer customerId)throws Exception{
       SessionFactory sessionFactory=null;
       Session session=null;
       Customer customer=null;
       try {
            sessionFactory=HibernateUtility.createSessionFactory();
            session=sessionFactory.openSession();
            CustomerEntity ce=(CustomerEntity) session.get(CustomerEntity.class, customerId);
            if(ce!=null){
                  customer=new Customer();
                  customer.setCustomerId(ce.getCustomerId());
                  customer.setCustomerName(ce.getCustomerName());
                  customer.setDateOfBirth(ce.getDateOfBirth());
                  customer.setAddress(ce.getAddress());
                  customer.setContactNumber(ce.getContactNumber());
            }
       }catch (HibernateException exception) {   
            throw new Exception("DAO.TECHNICAL_ERROR");
       }catch (Exception exception) {   
            throw exception;
       }finally {
            if(session.isOpen()|| session!=null)
                  session.close();
            }
       } 
       return customer;
   }
}
The steps used in this code are:
  1. Create instance of SessionFactory and Session
  2. Retrieve the object
  3. Close SessionFactory and Session
As you already know how to create SessionFactory and Session instances and how to close them (step 1 and step 3), let us see how to retrieve the customerEntity object (step 2)..

Data can be retrieved from the database table in terms of corresponding entity instance by invoking the get() method of Session interface as shown in customerDAO.java
CustomerEntity ce=(CustomerEntity) session.get(CustomerEntity.class, customerId);
Session interface provides an alternative load()  method as well for retrieval. 
CustomerEntity ce=(CustomerEntity) session.load(CustomerEntity.class, customerId);
Let us understand the difference between these two methods:

get() method

System.out.println("before search");
CustomerEntity ce=(CustomerEntity) session.get(CustomerEntity.class, customerId); //Type casting is necessary as get() returns 
                                                                                 //the persistent instance as Object
System.out.println("after search");
if(ce!=null){
    System.out.println("accessing object");
    System.out.println("found customer with Id: "+ce.getCustomerId());
}

Output when required entity is available

Output when required entity is unavailable
By observing the output, you can understand the behaviour of get() method as given below:
  • It returns the instance of the given entity class with the given identifier value
  • It returns null if there is no such record exist in the database

load() method

System.out.println("before search");
CustomerEntity ce=(CustomerEntity) session.load(CustomerEntity.class, customerId);
System.out.println("after search");
if(ce!=null){
    System.out.println("accessing object");
    System.out.println("found customer with Id: "+ce.getCustomerId());
}

Output when required entity is available
Output when required entity is unavailable
By observing the output, you can understand the behaviour of the load() method as given below:
  • It returns the proxy instance of the given entity class with the given identifier value. This proxy instance will be initialized on demand, when a non-key property method of that instance is accessed
  • If there is no such record exist in the database, it throws ObjectNotFoundException when the proxy instance is accessed
Requirement 1.3: Performing Update Operation
In order to solve requirement R.1.3, your application is going to reuse the same CustomerEntity.java and hibernate.cfg.xml files as the CUSTOMER table remains same.
Implement CustomerDAO.java as shown below to update the phone number for the given customer id.
public class CustomerDao {
   public Integer updateCustomerDetails(Integer id, Long newPhoneNumber)throws Exception{
       SessionFactory sessionFactory= null;
       Session session=null;
       Integer customerId=null;
       try {
            sessionFactory=HibernateUtility.createSessionFactory();
            session=sessionFactory.openSession();
            CustomerEntity ce=(CustomerEntity) session.get(CustomerEntity.class, id);
            if(ce!=null){
                  customerId=ce.getCustomerId();
                  session.getTransaction().begin();
                  ce.setContactNumber(newPhoneNumber);
                  session.getTransaction().commit();
            }
    }catch (HibernateException exception) {   
            throw new Exception("DAO.TECHNICAL_ERROR");
       }catch (Exception exception) {   
            throw exception;
       }finally {
            if(session.isOpen()|| session!=null)
                  session.close();
            }
       } 
       return customerId;
   }
}
The steps used in CustomerDAO.java are:
  1. Create instance of SessionFactory and Session
  2. Retrieve the desired record from the database as an entity object for updation
  3. Begin a transaction
  4. Update the values of the retrieved object by invoking it’s setter methods
  5. Commit the transaction
  6. Close SessionFactory and Session
As you already know how to create SessionFactory and Session instances (step 1), how to close them (step 6),  how to retrieve a record (step 2) and how to define the transaction context (step 3 and 5), let us discuss performing the update operation (step 4).
You can use any one of the retrieval methods (get or load) to retrieve the record of the customer whose details are to be updated.
For updating the contactNumber (step 4), you just have to set new value to the retrieved customer within the transaction scope (between transaction begin and commit) as shown in the code below.
CustomerEntity ce=(CustomerEntity) session.get(CustomerEntity.class, id);
if(ce!=null){
      customerId=ce.getCustomerId();
      session.getTransaction().begin();
      ce.setContactNumber(newPhoneNumber);
      session.getTransaction().commit();
}
As the retrieved object is modified with new values, the SQL UPDATE will be executed when you commit() the Hibernate Transaction.  
Note: Observe that the code does not use any specific API methods other than get/load for updation. Other than @Id attribute, all other attributes can be updated.
Requirement 1.4: Performing Delete Operation
In order to solve requirement R.1.4, your application is going to reuse the same CustomerEntity.java and hibernate.cfg.xml files as the CUSTOMER table remains same.
Your CustomerDAO.java to delete the customer details for the given customer id should be:
public class CustomerDao {
   public Integer deleteCustomerDetails(Integer customerId)throws Exception{
       SessionFactory sessionFactory= null;
       Session session=null;
       Integer custId=null;
       try {
            sessionFactory=HibernateUtility.createSessionFactory();
            session=sessionFactory.openSession(); 
            CustomerEntity ce=(CustomerEntity) session.get(CustomerEntity.class, customerId);
            if(ce!=null){
                custId=ce.getCustomerId();
                session.getTransaction().begin();
                session.delete(ce);
                session.getTransaction().commit();
            }
    }catch (HibernateException exception) {   
            throw new Exception("DAO.TECHNICAL_ERROR");
       }catch (Exception exception) {   
            throw exception;
       }finally {
            if(session.isOpen()|| session!=null)
                  session.close();
            }
       } 
       return custId;
   }
}
The steps used in CustomerDAO.java are:
  1. Create instance of SessionFactory and Session
  2. Retrieve the desired record from the database as an entity object for updation
  3. Begin a transaction
  4. Delete the record
  5. Commit the transaction
  6. Close SessionFactory and Session
You already know how to create SessionFactory and Session instances and how to close them (step 1 and step 6), to begin and commit a transaction (step 3 and step 5).
You can use any one of the retrieval methods (get or load) to retrieve the customer record whose details are to be deleted (step 2).
Let us discuss how to delete the record (step 4)..
For deleting the customer details of the retrieved customer (step 4), you need to invoke delete() method of the Session interface. It accepts the object which needs to be deleted (retrieved entity object). The delete() method should be invoked inside an active transaction.
 CustomerEntity ce=(CustomerEntity) session.get(CustomerEntity.class, customerId);
 if(ce!=null){
     custId=ce.getCustomerId();
     session.getTransaction().begin();
     session.delete(ce);
     session.getTransaction().commit();
}
The delete() method causes the execution of SQL DELETE when you commit() the Hibernate Transaction.

Hibernate architecture

Hibernate API

The classes that you have used till now are put together in the below diagram
Transient object is an object which has just been instantiated using the new operator and has no representation in the database. 
Persistent object has a representation in the database in the form of records or primary key value. The transient and persistent objects are conversational states represented by Session API.
SessionFactory is used to create and manage session instances. The methods available here are:
Session represents the connection between the application and database. Few methods available under session object are
TransactionFactory is used to create and manage transaction object. This class will be internally used by hibernate
Transaction defines a transaction context in the same way how you have used EntityTransaction in JPA.
Requirement 2: BLOB and CLOB
InfyBank asks you to build a Hibernate application to solve the below requirements.
R.2.1 Insert the customer profile picture into CUSTOMERPROFILE table

R.2.2 Retrieve the customer profile picture from CUSTOMERPROFILE table
R.2.3 Store an incomeCertificate.txt file into CUSTOMERDOCS table
R.2.4 Retrieve incomeCertificate.txt file from CUSTOMERDOCS table

Implement the below files in a Hibernate project to solve the InfyBank requirement R.2.1.

Step 1: Annotate the entity class

Your entity class needs an property to map with ProfilePicture column of CUSTOMERPROFILE table.  The data type of ProfilePicture column in table is BLOB (Binary Large OBject) which can store upto 2 GB of binary string.  So, your entity class should use the Blob interface of Java and the @Lob annotation to work with SQL BLOB 
CustomerProfileEntity.java
@Entity
@Table(name="CustomerProfile")
public class CustomerProfileEntity {
        @Id 
        private Integer customerId;
        @Lob
        private Blob profilePicture;
       
        //getters and setters()
}

Step 2: Configure the entity class in hibernate.cfg.xml

property name="hbm2ddl.auto" update /property
The mapping class="CustomerProfileEntity"  tag should be added in hibernate.cfg.xml.

Step 3: Persist Blob

CustomerDAO.java
public class CustomerDAO {
       public Integer addCustomerimage(Integer customerid) throws Exception {
                Session session = null;
                Integer custId = null;

                File file = new File("src/resources/image"+customerid+".jpg");
                try (FileInputStream fis=new FileInputStream(file);){
                        SessionFactory sessionFactory = HibernateUtility.createSessionFactory();
                        session = sessionFactory.openSession();
                        CustomerProfileEntity entity = new CustomerProfileEntity();
                        entity.setCustomerId(customerid);
                        Blob image=Hibernate.getLobCreator(session).createBlob(fis, file.length());
                        entity.setProfilePicture(image);
                        session.beginTransaction();
                        custId = (Integer) session.save(entity);
                        session.getTransaction().commit();
                        image.free();
                }catch (HibernateException exception) {   
                        throw new Exception("DAO.TECHNICAL_ERROR");
                }catch (Exception exception) {   
                        throw exception;
                }finally {
                        if(session.isOpen()|| session!=null)
                        session.close();
                }
                return custId;
       } 
}
Persisting an image has following steps:
  1. Create an entity class with annotations specifying Binary Large OBject (BLOB)
  2. Map the entity class in hibernate.cfg.xml (as we discussed before)
  3. Persist the binary large object
It follows the below steps.
1. Read the image file using any of the byte stream class as *.jpg is a binary file.
File file = new File("src/resources/image"+customerid+".jpg");
FileInputStream fis=new FileInputStream(file);
2. Obtain the object of LobCreator interface for the current session by invoking the getLobCreator() method of Hibernate class.  Then, create the Blob object by sending the byte stream object and length of the file into createBlob() method of LobCreator.
Blob image=Hibernate.getLobCreator(session).createBlob(fis, file.length());
There is one more alternative way to create the Blob object, where you can send the byte array to createBlob() method as shown below.
File file = new File("src/resources/image"+customerid+".jpg");
FileInputStream fis=new FileInputStream(file);
byte [] byteArray = new byte[file.length()];
fis.read(byteArray);
Blob image=Hibernate.getLobCreator(session).createBlob(byteArray);
3. Set the blob object to the entity object and persist the entity object either using save() or persist() within an active transaction.
All these steps can be represented diagrammatically as below:

Requirement 2.2: Retrieving an Image

CustomerDAO.java
public class CustomerDAO {
       public void getCustomerProfilePicture(Integer customerId) throws Exception {
        Session session=null;
        try(FileOutputStream fos=new FileOutputStream("output/customer"+customerId+".jpg")) {
            SessionFactory sessionFactory=HibernateUtility.createSessionFactory();
            session=sessionFactory.openSession();
            CustomerProfileEntity entity = (CustomerProfileEntity ) session.get(CustomerProfileEntity.class, customerId);
                        
            Blob image=entity.getProfilePicture();
            byte[] data=image.getBytes(1, (int) image.length());
            fos.write(data);
            image.free();
        }catch (HibernateException exception) {   
                        throw new Exception("DAO.TECHNICAL_ERROR");
                }catch (Exception exception) {   
                        throw exception;
                }finally {
                        if(session.isOpen()|| session!=null)
                        session.close();
                }
       } 
}
It follows the below steps.
1. Retrieve the entity object using either get() or load() methods.
2. Convert the Blob object to byte array.
 Blob image=entity.getProfilePicture();
 byte[] data=image.getBytes(1, (int) image.length());
3. Write the byte array using an output stream object fos which represents the path where the retrieved Blob object to be stored as a file.

AppConfig.java
public class AppConfig {
 public static final Properties PROPERTIES;
 public static InputStream inputStream = null;
 static {
  try {
   inputStream = new FileInputStream("src/resources/configuration.properties");
  }
  catch (FileNotFoundException e) {
   Logger logger = Logger.getLogger(AppConfig.class);
   logger.error(e.getMessage(), e);

  }
  PROPERTIES = new Properties();
  try {
   PROPERTIES.load(inputStream);
  }
  catch (IOException e) {
   Logger logger = Logger.getLogger(AppConfig.class);
   logger.error(e.getMessage(), e);

  }
 }
}







Requirement 2.3: Persisting a Text File

Step 1: Annotate the entity class

Your entity class needs an property to map with IncomeCertificate column of CUSTOMERDOCS table. The data type of IncomeCertificate column in table is CLOB (Character Large OBject) which can store upto 2 GB of Unicode based characters.  So, your entity class should use the Clob interface of Java and the @Lob annotation to work with SQL CLOB
CustomerDocsEntity.java
@Entity
@Table(name="customerDocs")
public class CustomerDocsEntity {

 @Id
 private Integer customerId;
 @Lob
 private Clob incomeCertificate;
    //getters and setters
}
CustomerDAO.java
public class CustomerDAO {
       public Integer addCustomerdoc(Integer customerid) throws Exception {
                Session session = null;
                Integer custId = null;
                File file = new File("src/resources/incomeCertificate"+customerid+".txt");
                try (FileReader fr=new FileReader(file);){
                        SessionFactory sessionFactory = HibernateUtility.createSessionFactory();
                        session = sessionFactory.openSession();
                        CustomerDocsEntity entity = new CustomerDocsEntity();
                        entity.setCustomerId(customerid);
                        Clob doc=Hibernate.getLobCreator(session).createClob(fr, file.length());
                        entity.setIncomeCertificate(doc);
                        session.beginTransaction();
                        custId= (Integer) session.save(entity);
                        session.getTransaction().commit();
                        doc.free();
                }catch (HibernateException exception) {   
                        throw new Exception("DAO.TECHNICAL_ERROR");
                }catch (Exception exception) {   
                        throw exception;
                }finally {
                        if(session.isOpen()|| session!=null)
                        session.close();
                }
                return custId;
       } 
}
Persisting a text file (incomeCertificate.txt) has following steps:
  1. Create an entity class with annotations specifying Character Large OBject (CLOB)
  2. Map the entity class in hibernate.cfg.xml (as we discussed before)
  3. Persist the Character large object

 Requirement 2.4: Retriving a Text File
Reusing the same CustomerDocsEntity.java class, below is the CustomerDAO.java to solve the requirement R.2.4, retrieving the text file.
CustomerDAO.java
public class CustomerDAO {
       public void getCustomerDoc(Integer customerId) throws Exception {
        Session session=null;
        Customer customer=null;
        try(FileWriter fw=new FileWriter("output/incomeCertificate"+customerId+".txt")) {
            SessionFactory sessionFactory=HibernateUtility.createSessionFactory();
            session=sessionFactory.openSession();
            CustomerDocsEntity entity = (CustomerDocsEntity) session.get(CustomerDocsEntity.class, customerId);
                       
            Clob doc=entity.getIncomeCertificate();
            char[] data=doc.getSubString(1, (int) doc.length()).toCharArray();
            fw.write(data);
            doc.free();
        }catch (HibernateException exception) {   
                        throw new Exception("DAO.TECHNICAL_ERROR");
                }catch (Exception exception) {   
                        throw exception;
                }finally {
                        if(session.isOpen()|| session!=null)
                        session.close();
                }
       } 
}
It follows the below steps.
1. Retrieve the entity object using either get() or load() methods.
2. Convert the Clob object to character array.
Clob doc=entity.getIncomeCertificate();
char[] data=doc.getSubString(1, (int) doc.length()).toCharArray();
3. Write the byte array using an output stream object fw which represents the path where the retrieved Clob object to be stored as a file. 
Persistence Lifecycle – Persistence Context
Persistence context can be defined as an imaginary realm or a scope inside session, where it contains set of entity objects, each representing a row in the database.
Consider the below code snippet where you retrieve two entity objects entity1 and entity2 from CUSTOMER table:
CustomerEntity entity1 = session.get(CustomerEntity.class,5001);
CustomerEntity entity2 = session.get(CustomerEntity.class,5002);
In order to manage the entity objects, Hibernate will store the objects into a context called persistence context 

Entity objects are included in persistence context even when save() or persist() method is invoked. The entities will be placed into the persistence context as long as the entities are associated with the Session object. Based on the association between the entity and persistence context, an entity is said to be in different states in its lifetime.
In some situations, the object may or may not be eligible for persistence as the API method calls may trigger the state transitions on the objects. 
Hibernate defines different states and state transitions for a persistent object in its lifecyle.
The different states of a persistent object are:

  • New/Transient State: A newly created object which is not associated with persistence context.
  • Managed/Persistent State: An entity instance with a row representation in the database and it is currently associated with a persistence context.
  • Detached State: An entity instance with or without a row identity in the database and which is no longer associated with a persistence context, usually because the persistence context was closed or the instance was evicted from the context.
  • Removed State: An entity instance with a row identity in the database and associated with a persistence context, but scheduled for removal from the database.


  • State Transition During Insert Operation
Heap(entity[new])-->Persistence context(entity[Managed]) = entity remain in managed state after commit

  • State Transition During Update Operation
Persistence context(entity[Managed]) = entity remain in managed state after commit

  • State Transition During Delete Operation
Persistence context(entity[Managed]) --> entity[Removed state] = on commit Persistence context(entity[Removed]) --> entity[new state]

  • State Transition During Clearing Session 
Persistence context(entity[Managed]) --> entity[Detached state] operation close, clear, evict(e)

Requirement 3: Auto Generating Identifier Value
InfyBank is facing difficulty in manually assigning an unique customer id for their customers as their customer base is increasing at an exponential rate. So, InfyBank has come up with a requirement as below which you need to solve.
R.3.1 While inserting the customer details into CUSTOMER table, the customer id (identifier column) value should be auto generated by the application instead of assigning them manually.
Below is the CUSTOMER table structure used by InfyBank.
 class diagram
There are many strategies available in Hibernate to auto generate the identifier value. Few of them are sequenceincrementnativeassignedhilo, etc. 
Requirement 3: Auto Generating Identifier Value
CustomerEntity.java
@Entity
@Table(name="customer")
@GenericGenerator(name="generatorName",strategy="sequence")
public class CustomerEntity {
 @Id
 @GeneratedValue(generator="generatorName")
 private Integer customerId;
 private String customerName;
 @Temporal(TemporalType.DATE)
 private Calendar dateOfBirth;
 private String address;
        @Column(name="phoneNumber")
 private Long contactNumber;
 //getters and setters
}
Sequence strategy uses the database sequence objects to auto generate the identifier value.  A sequence in database is a set of integers that are generated in some order on demand.
The annotations used in CustomerEntity.java for auto generating the primary key values are:
  • @GenericGenerator(name="generatorName", strategy="strategyName")
It is a class level annotation, to be applied on the entity class. The generatorName can be any user defined name. strategyName specifies one of the primary key generation strategy types. In our code we are using the 'sequence' strategy.
  • @GeneratedValue(generator="generatorName")
It is a field level annotation, to be applied on the primary key attribute. The generatorName should be the same name defined in @GenericGenerator.
Sequence strategy uses the default sequence object, hibernate_sequence, of your database to auto generate the primary key value.
Instead of using the default hibernate_sequence object, you can instruct Hibernate to use user defined sequences as well.  The SQL query syntax to create a user defined sequence is as follows:
CREATE SEQUENCE START WITH INCREMENT BY ;
Example:
CREATE SEQUENCE DB_CustomerId_PK START WITH 5004 INCREMENT BY 1;
The hibernate_sequence can also be created with the desired start and increment value using CREATE query.
To use the user defined sequence DB_CustomerId_PK, you have to rewrite your CustomerEntity.java as given below.
@Entity
@Table(name="customer")
@GenericGenerator(name="generatorName",strategy="sequence",
parameters={@Parameter(name="sequence",value="DB_CustomerId_PK")})
public class CustomerEntity {
 @Id
 @GeneratedValue(generator="generatorName")
 private Integer customerId;
 private String customerName;
 @Temporal(TemporalType.DATE)
 private Calendar dateOfBirth;
 private String address;
        @column(name="phoneNumber")
 private Long contactNumber;
 //getters and setters
}
The parameters attribute of @GenericGenerator is used to specify the user defined sequence name. When CustomerDAO.java persists CustomerEntity, the record will be inserted with values 5004, 5005 and so on for customer id.
Note: Not all the databases support sequence strategy.  For example, MySQL does not support sequence.
Increment Strategy
Instead of using Sequence strategy, you can solve the requirement R.3.1 using increment strategy as shown below.
CustomerEntity.java
@Entity
@Table(name="customer")
@GenericGenerator(name="generatorName", strategy = "increment")
public class CustomerEntity {
 @Id
 @GeneratedValue(generator="generatorName")
 private Integer customerId;
 private String customerName;
 @Temporal(TemporalType.DATE)
 private Calendar dateOfBirth;
 private String address;
        @Column(name="phoneNumber")
 private Long contactNumber;
 //getters and setters
}
Observe that @GenericGenerator defines the strategy type as 'increment'. 
In increment strategy, Hibernate will take the maximum value present in the primary key column of the table, add 1 to it, and use this value as new primary key.
For example, assume that below are the records currently available in CUSTOMER table.
The next row which will be inserted into this table will  have 5004 (5003+1) as the value for CustomerId field.
If suppose the inserting row is the first row, then the primary key value will be 1 (0+1).

Note: This strategy is suitable for all databases, but if the systems are distributed and using same database and table, then this strategy is not suggested.
Native Strategy
As not all the databases support the strategies that you choose in your application (for e.g. Oracle does not support Identity, MySQL does not support sequence), you can let the framework decide upon the strategy by keeping your strategy type as native.  
Your CustomerEntity.java to solve the requirement R.3.1 using native strategy should be:
@Entity
@Table(name="customer")
@GenericGenerator(name="generatorName", strategy = "native")
public class CustomerEntity {
 @Id
 @GeneratedValue(generator="generatorName")
 private Integer customerId;
 private String customerName;
 @Temporal(TemporalType.DATE)
 private Calendar dateOfBirth;
 private String address;
        @Column(name="phoneNumber")
 private Long contactNumber;
 //getters and setters
}
In native strategy, Hibernate will use either identity or sequence or hilo based on the underlying database’s capability. In our case, since the underlying database is Oracle which does not support identity, it will use sequence strategy for generating the primary key.
Note: By default Hibernate will use the “hibernate_sequence” as the sequence for primary key generation. You can use custom sequence as well, by using parameters attribute of @GenericGenerator.
Assigned Strategy
Your CustomerEntity.java to solve the requirement R.3.1 using assigned strategy should be:
@Entity
@Table(name="customer")
@GenericGenerator(name="generatorName",strategy="assigned")
public class CustomerEntity {
 @Id
 @GeneratedValue(generator="generatorName")
 private Integer customerId;
 private String customerName;
 @Temporal(TemporalType.DATE)
 private Calendar dateOfBirth;
 private String address;
        @column(name="phoneNumber")
 private Long contactNumber;
        //getters and setters
}
Assigned the default strategy in Hibernate. So, if no strategy is defined for the identifier of an entity, Hibernate assumes it as assigned strategy.
In this type of strategy, the Hibernate will not generate the primary key automatically. Instead, your application must assign the value for identifier before invoking persist() or save() method. we need to set explicitly customer.setCustomerId(5004)
Requirement 4: Composite Primary Key

 
 
By observing the table structure you can understand that customerId and loanId together forms the primary key in CUSTOMERLOAN table. Below is the requirement given to you to solve.
Composite Primary Key Using @IdClass
R.4.1  Create an entity class for CUSTOMERLOAN table with composite primary key in it. 
create Customer and loan table entity class 
Implement your entity classes as shown below to solve the requirement R.4.1, implementing composite primary key.
CustomerLoan.java
@Entity(name="customerLoan")
@IdClass(CustomerLoanPK.class)
public class CustomerLoanEntity {
 @Id
 private Integer customerId;
 @Id
 private String loanId;
 private Double eligibleAmount;
 private Double availedAmount;
 private Float tenureInYears;
        //getters and setters
}
CustomerLoanPK.java
public class CustomerLoanPK implements Serializable {
 private Integer customerId;
 private String loanId;
        //getters and setters()
}
It follows the below steps.
  1. Add @Id for all the attributes (customerId and loanId) that are part of composite primary key in your entity class.
  2. Create a separate Serializable class CustomerLoanPK.java  which defines all the @Id attributes of entity class once again in it. The name and type of these attributes must be same as the entity class attributes.
  3. Use @IdClass annotation in your entity class to declare the serializable class.

Persisting the CustomerLoanEntity object

While persisting an object of CustomerLoan, you need to set all the attributes including customerId and loanId using setters method. If you use save() method to persist,  it returns the CustomerLoanPK object (serializable class).
CustomerLoanEntity cle=new CustomerLoanEntity();
cle.setCustomerId(5003);
cle.setLoanId(103);
cle.setEligibleAmount(1000000);
cle.setAvailedAmount(500000);
cle.setTenureInYears(5);
pk=(CustomerLoanPK) session.save(cle);

Retrieving CustomerLoanEntity object

While retrieving the object using get() or load() methods, you need to pass the object of CustomerLoanPK as your identifier value.
CustomerLoanPK pk=new CustomerLoanPK();
pk.setCustomerId(5003);
pk.setLoanId(103);
CustomerLoanEntity cle=(CustomerLoanEntity) session.get(CustomerLoanEntity.class, pk);
Composite Primary key Using @EmbeddedId
An alternative solution for R.4.1, implementing composite primary key, is given below.
CustomerLoanEntity.java
@Entity(name="customerLoan")
public class CustomerLoanEntity {
 @EmbeddedId
 private CustomerLoanPK pk;
 private Double eligibleAmount;
 private Double availedAmount;
 private Float tenureInYears;
        //getters and setters
}
CustomerLoanPK.java
@Embeddable
public class CustomerLoanPK implements Serializable {
 
 private Integer customerId;
 private String loanId;
        //getters and setters
}
It follows the below steps:
  1. The primary key attributes are defined only in an external Serializable class.
  2. The serializable class is annotated with @Embeddable and its reference is mapped with @EmbeddedId in entity class.
  3. The serializable class should be configured in hibernate.cfg.xml file using tag.

Persisting CustomerLoanEntity object

While persisting an object of CustomerLoanEntity, you need to create the object of CustomerLoanPK and use that to initialize pk property of CustomerLoanEntity. If you use save() method to persist,  it returns the CustomerLoanPK object (serializable class).
CustomerLoanPK key=new CustomerLoanPK();
key.setCustomerId(5003);
key.setLoanId(103);
CustomerLoanEntity cle=new CustomerLoanEntity();
cle.setPk(key);
cle.setEligibleAmount(1000000);
cle.setAvailedAmount(500000);
cle.setTenureInYears(5);
pk=(CustomerLoanPK) session.save(cle);

Retrieving CustomerLoanEntity object

While retrieving the object using get() or load() methods, you need to pass  the object of CustomerLoanPK as your identifier value.
CustomerLoanPK pk=new CustomerLoanPK();
pk.setCustomerId(5003);
pk.setLoanId(103);
CustomerLoanEntity cle=(CustomerLoanEntity) session.get(CustomerLoanEntity.class, pk);
Requirement 5: One to One Mapping





As per the business of Bank, the customers can avail locker service to only one locker and a locker cannot  be shared by more than one customers. That is, the cardinality of the relationship between CUSTOMER and the LOCKER table is defined as one to one in the database by imposing an unique constraint on foreign key column.
Implement the entity classes as shown below to solve the InfyBank requirement R.5.1, implementing one to one mapping.
CustomerEntity.java
@Entity
@Table(name="customer")
public class CustomerEntity {
 @Id
 private Integer customerId;
 private String customerName;
 @Temporal(TemporalType.DATE)
 private Calendar dateOfBirth;
 private String address;
 private Long contactNumber;
 @OneToOne(cascade=CascadeType.ALL)
 @JoinColumn(name="lockerId", unique=true)
 private LockerEntity locker;
 
 //Generate getters and setters
}
LockerEntity.java
@Entity
@Table(name="locker")
public class LockerEntity {
 @Id
 private String lockerId;
 private String lockerType;
 private Double rent;
 
 //generate getters and setters
}
OneToOne Mapping has following steps:

1. Identify the source and target entity among the given entities

As CUSTOMER table has the foreign key column LockerId which references the primary key of LOCKER table, you have to  consider the entity of CUSTOMER table as source and LOCKER as target. Source entity is associated with target entity, to map the foreign key column. Hence, the CustomerEntity class has an instance of LockerEntity.

2. Give appropriate annotations in the source entity to map with target entity

The annotations used in CustomerEntity for one to one mapping with LockerEntity are:
  • @OneToOne(cascade=CascadeType.ALL)  
This annotation is applied on the instance of target entity to indicate that the relationship has one to one cardinality. The cascade attribute of the annotation is mandatory. This attribute transfers operations (such as insert, update, delete) done on source object to its target object. 
  • @JoinColumn(name="lockerId", unique=true)
In Hibernate, the foreign key column is known as join column . In this annotation the name attribute specifies the name of the foreign key column in the source table. The unique attribute assures unique values in the join column to achieve one-to-one mapping
CRUD Operations
Some of the possible CRUD operations which you can perform on the associated entities (CustomerEntity.java and LockerEntity.java)
  • Persisting source and target entity instances together: Adding a new customer and a new locker and assigning the locker to the customer. if existing locker 
    if(customer.getLocker()!=null){
     LockerEntity le=(LockerEntity) session.get(LockerEntity.class, customer.getLocker().getLockerId());   
     ce.setLocker(le);
    }
  • Persisting only source entity instance: Adding a new customer without any locker details.
  • Associating an existing source entity instance with a new target entity instance: For an existing customer assigning a new locker.
  • Fetching source and target entity instances together: Retrieving the details of a customer along with the corresponding locker details if available.
  • Removing an existing source entity instance along with the associated target entity instance: Deleting the details of an existing customer and the associated locker details from the tables.
  • Removing only the source entity instance: Deleting the details of a customer while retaining the corresponding locker details.
One to One mapping[has-a] --> 'Object-Relational Impedance Mismatch' (sometimes called the 'paradigm mismatch')   : Granularity
Requirement 6: Many to One Mapping




As per the business of InfyBank, many accounts can be held by a customer. That is, the cardinality of the relationship between ACCOUNT and the CUSTOMER table is defined as many to one in the database.
Implement the entity classes as shown below to solve the InfyBank requirement R.6.1, implementing many to one mapping.
AccountEntity.java
@Entity
@Table(name="account")
public class AccountEntity {

 @Id
 private Long accountNo;
 private String accountType;
 @Temporal(TemporalType.DATE)
 private Calendar openingDate;
 private Double balance;
 private String accountStatus;
 @ManyToOne(cascade=CascadeType.ALL)
 @JoinColumn(name="customerId")
 private CustomerEntity customer;

 //Generate Getters and Setters
}
CustomerEntity.java
@Entity
@Table(name="customer")
public class CustomerEntity {
 @Id
 private Integer customerId;
 private String customerName;
 @Temporal(TemporalType.DATE)
 private Calendar dateOfBirth;
 private String address;
 private Long contactNumber;

 //generate Getters and Setters
}
Many to one mapping has following steps:

1. Identify the source and target entity among the given entities

As ACCOUNT table has the foreign key column CustomerId which references the primary key of CUSTOMER table, you have to  consider the entity of ACCOUNT table as source and CUSTOMER as target. Source entity is associated with target entity, to map the foreign key column. Hence the AccountEntity class declares the customer property which is an instance of CustomerEntity.

2. Give appropriate annotations in the source entity to map with target entity

The annotations used in AccountEntity for many to one mapping with CustomerEntity are:
  • @ManyToOne(cascade=CascadeType.ALL)  
This annotation is applied on the instance of target entity to indicate that the relationship has many to one cardinality. The cascade attribute of the annotation is mandatory. This attribute transfers operations (such as insert, update, delete) done on source object to its target object. 
  • @JoinColumn(name="customerId")
This annotation is used to specify a column for joining the associated entities. The name attribute specifies the name of the foreign key column in the source table.
CRUD Operations
Some of the possible CRUD operations which you can perform on the associated entities (AccountEntity.java and CustomerEntity.java) 
  • Persisting source and target entity instances together: Adding new account and a new account holder(Customer).
  • Persisting only source entity instance: Adding a new account without any customer associated with it.
  • Associating an existing source entity instance with a new target entity instance: For an existing account, assigning a new customer.
  • Fetching source and target entity instances together: Retrieving the account details along with the customer details.
  • Removing only the source entity instance: Deleting the account details while retaining the corresponding customer details.

Note: Removing an existing source entity instance along with the target entity may lead to foreign key violation as the target entity may be associated with the another source entity instance.
Requirement 7: Many to Many Mapping
As per the business of InfyBank, each customer can avail many loans and a loan can be shared by many customers. That is, the cardinality of the relationship between CUSTOMER and the LOAN table is defined as many to many in the database.  






Many to many is represented by creating a third table called lookup table (CUSTOMERLOAN) which contains a composite primary key which are copies of primary key columns from CUSTOMER and LOAN tables.
Implement the entity classes as shown below to solve the InfyBank requirement R.7.1, implementing many to many mapping.
CustomerEntity.java
@Entity
@Table(name="customer")
public class CustomerEntity {
 @Id
 private Integer customerId;
 private String customerName;
 @Temporal(TemporalType.DATE)
 private Calendar dateOfBirth;
 private String address;
 private Long contactNumber;
 @ManyToMany(cascade=CascadeType.ALL)
 @JoinTable(name="customerLoan",
 joinColumns=@JoinColumn(name="customerId", referencedColumnName="customerId"),
 inverseJoinColumns=@JoinColumn(name="loanId", referencedColumnName="loanId"))
 private List loans;
 
 //getters and setters

}
LoanEntity.java
@Entity(name="loan")
public class LoanEntity {
 @Id
 private String loanId;
 private String loanName;
 private Float rateOfInterest;
 
 //getters and setters
}
Observer that only two entity classes are defined here though the database contains three tables for many to many relationship.  The lookup table will not have its own entity class.
Many to many mapping has following steps:

1. Identify the Source entity and the Target entity among the given Entities

In the ManyToMany mapping you can use either of the entities as source entity and the remaining as target entity. In the given scenario we are using CustomerEntity as source and LoanEntity as target.

2. Give appropriate annotations in the source entity to map with target entity

The annotations used in CustomerEntity for many to many mapping with LoanEntity are:
  • @ManyToMany(cascade=CascadeType.ALL)  
This annotation is applied to the property which is a collection of instances of target entity to indicate that the relationship has many to many cardinality. The cascade attribute of the annotation is mandatory. This attribute transfers operations (such as insert,update,delete) done on source object to its target object.
  • @JoinTable
  • @JoinTable(name="customerLoan",
         joinColumns=@JoinColumn(name="customerId", referencedColumnName="customerId"),
         inverseJoinColumns=@JoinColumn(name="loanId", referencedColumnName="loanId"))
    

As many to many mapping does not define a separate entity for lookup table, this annotation is used to map with lookup table. 
The name attribute specifies the lookup table name (CUSTOMERLOAN) which contains the primary keys of both source and target table.
The joinColumns attribute is used to indicate the joining column names for source table. 
The inverseJoinColumns attribute is used to indicate the joining column names for target table.
In @JoinColumn annotation which is used for both joinColumns and inverseJoinColumns, the name attribute specifies the lookup table column name and referencedColumnName specifies the source/target table column name.
CRUD Operations
Some of the possible CRUD operations which you can perform on the associated entities (CustomerEntity.java and LoanEntity.java) 
  • Persisting a new source entity instance along with a new  list of instances of target entity: Adding a new customer and assigning new list of loans to him/her.
  • Associating an existing source entity with new set of target entity instances along with existing target instances: Adding new set of loans to the existing list of loans of a customer.
  • Fetching source and list of target entity instances together: Retrieving the details of a customer as well as the details of all the loans he/she has taken.
  • Removing specific target entity instances from the source entity instance: Removing some of the loans from the list of existing loans of a customer.
  • Removing all the target entity instances from the list in source entity instance: Removing all the loans of a customer.
Requirement 8: Inheritance Mapping



By observing the table you can get that InfyBank maintains two types of accounts i.e. savings and current.
In object oriented paradigm different types of account will be represented with hierarchical structure
In table per class hierarchy method the entire class hierarchy is mapped to a single table. Implement the entity classes as given below to solve the requirement R.8.1, inheritance mapping.
Inheritance Mapping Using Table Per Class
AccountEntity.java
@Entity
@Table(name="Account")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="accountType")
public class AccountEntity {
 @Id
 private Long accountNo;
 @Temporal(TemporalType.DATE)
 private Calendar openingDate;
 private Double balance;
 private String accountStatus;
        //getters and setters()
}
SavingsAccountEntity.java
@Entity
@DiscriminatorValue("SavingsAccount")
public class SavingsAccountEntity extends AccountEntity {
 private Double minimumBalance;
        //getter and setters()
}
CurrentAccountEntity.java
@Entity
@DiscriminatorValue("CurrentAccount")
public class CurrentAccountEntity extends AccountEntity{
 private Double overDraftLimit;
        //getters and setters()
}
AccountEntity.java
The steps involved in solving inheritance mismatch are:


Step 1: Identify the discriminator column

To determine the type of account that each row represents, the ACCOUNT table uses accountType column.
You already know that you have three entity classes which will be mapping to single ACCOUNT table. So, the  framework needs a way to differentiate the row representing an object of one class (let us say an object of SavingsAccountEntity) from the row representing an object of another class (let us say an object of CurrentAccountEntity). The persistence framework uses the accountType column for the same and it refers the column as discriminator column.
Discriminator column holds different values for different types of rows.  These values are called as discriminator values.  

Step 2: Annotate the entity classes to map with the single table

The annotations used in entity classes are:
  • @Inheritance(strategy=InheritanceType.SINGLE_TABLE)
  • @DiscriminatorColumn(name="discriminatorColumnName")
  • @DiscriminatorColumnValue("discriminatorColumnValue")
@Inheritance and @DiscriminatorColumn annotations should be applied on the base entity whereas @DiscriminatorColumnValue can be applied on all the classes.
@Inheritance:
This is the annotation which signifies the strategy type used for inheritance relationship mapping. The strategy attribute specifies the type of the strategy.  For table per class hierarchy, the type should be InheritanceType.SINGLE_TABLE.
@DiscriminatorColumn:
This annotation is used to map the extra column (accountType) that is available in table for which there is no corresponding property declared in any of the entity classes in hierarchy. The name attribute specifies the name of the discriminator column.
@DiscriminatorValue:
The string value in this annotation indicates the discriminator value that the instance of the class will have when it is getting persisted into the database. In the absence of this annotation, the class name itself will act as the discriminator value during persist.

Note: @DiscriminatorValue annotation can be applied in base class entity as well, so that persisting a base class object can have a value for discriminator column

Persist with table per class  hierarchy 

According to which subclass object you are going to persist, the hibernate will decide the discriminator value.  Columns respective to that subclass object will only be populated. For example, consider persisting an instance of SavingsAccountEnity as shown below:
savingsAccountEntity.set();
session.persist(savingsAccountEntity);
Ui.java
CurrentAccount ca=new CurrentAccount();
ca.setAccountNo(559412123438L);
ca.setOpeningDate(Calendar.getInstance());
ca.setBalance(4300000.00);
ca.setAccountStatus("Active");
ca.setOverDraftLimit(200000.00);
The discriminator column will have the value 'SavingsAccount' and overDraftLimit will be NULL.

Retrieve with table per class hierarchy

When you retrieve an object, the retrieved object will always be an instance of base class. You need to type cast it appropriately by checking its type with the help of instance of operator as shown below:
AccountEntity ae=(AccountEntity) session.get(AccountEntity.class, accountNo);
if(ae instanceof SavingsAccountEntity){
        //......
}else if(ae instanceof CurrentAccountEntity){
        //....
}

AccountEntityDAO.java
to retrive :  AccountEntity ae=(AccountEntity) session.get(AccountEntity.class, accountNo);
if(account instanceof SavingsAccount){
  SavingsAccount sa=(SavingsAccount) account;
  SavingsAccountEntity sae=new SavingsAccountEntity();
  sae.setAccountNo(sa.getAccountNo());
  sae.setOpeningDate(sa.getOpeningDate());
  sae.setBalance(sa.getBalance());
  sae.setAccountStatus(sa.getAccountStatus());
  sae.setMinimumBalance(sa.getMinimumBalance());
  accountNo=(Long) session.save(sae);
    
}
else if(account instanceof CurrentAccount){
  CurrentAccount ca=(CurrentAccount) account;
  CurrentAccountEntity cae=new CurrentAccountEntity();
  cae.setAccountNo(ca.getAccountNo());
  cae.setOpeningDate(ca.getOpeningDate());
  cae.setBalance(ca.getBalance());
  cae.setAccountStatus(ca.getAccountStatus());
  cae.setOverDraftLimit(ca.getOverDraftLimit());
  accountNo=(Long) session.save(cae);
}

Inheritance Mapping: Table Per Subclass
Besides table per class hierarchy, Hibernate provides one more strategy for inheritance mapping known as table per subclass
Let us discuss how to apply table per subclass strategy to the AccountEntity class hierarchy which we used previously. Below is the class diagram representing the relationship between entities. 
Below is the table structure InfyBank should maintain if your application uses the table per subclass strategy







Below is the implementation of entity classes with required annotations for table per subclass strategy.
AccountEntity.java
@Entity
@Table(name="Account")
@Inheritance(strategy=InheritanceType.JOINED)
public class AccountEntity {
 @Id
 private Long accountNo;
 @Temporal(TemporalType.DATE)
 private Calendar openingDate;
 private Double balance;
 private String accountStatus;
        //getters and setters
}
SavingsAccountEntity.java
@Entity
@Table(name="SavingsAccount")
public class SavingsAccountEntity extends AccountEntity {
 
 private Double minimumBalance;
        //getters and setters
}
CurrentAccountEntity.java
@Entity
@Table(name="CurrentAccount")
public class CurrentAccountEntity extends AccountEntity{

 private Double overDraftLimit;
        //getters and setters
}
Observe the strategy type in @Inheritance annotation. For table per subclass, the type should be InheritanceType.JOINED.  No other annotations are required for table per subclass strategy.
Each class in the hierarchy is mapped to its own table in the underlying database.

Requirement 9: HQL CRUD
InfyBank has come up with few more requirements as below which you need to solve.
R.9.1 Retrieve the details of customers based on name and dateOfBirth. 
R.9.2 Update the address and contact number of customers based on name and dateOfBirth. 
R.9.3 Delete the customer based on address.
R.9.4 Insert the customer details from CUSTOMER table into SENIORCITIZEN table depending on their age.
Below is the CUSTOMER and SENIORCITIZEN table structure used by InfyBank.
Requirement 9.1: Retrieval Using HQL
In order to solve Requirement R.9.1think whether you can solve this requirement using the concepts you learnt until now in hibernate..
As you need to retrieve data using non-primary key value, you cannot use either get() or load() methods to solve the requirement. So, you need to use Hibernate Query language (HQL).
HQL is same as SQL in its appearance, but instead of operating on tables and columns, HQL works with entity classes and its properties. Using HQL you can perform any DML operations that you can do with SQL. 
CustomerDAO.java
public class CustomerDAOImpl implements CustomerDAO {
    public List getCustomerDetails(String customerName, Calendar dateOfBirth) throws Exception {
 
        SessionFactory sessionFactory = HibernateUtility.createSessionFactory();
        Session session = null;

        String hql = "from CustomerEntity ce where ce.customerName='"+customerName +"' and ce.dateOfBirth='"+dateOfBirth; // Creating the query
        List returnList=new ArrayList();
        try {
            session = sessionFactory.openSession();
            Query query = session.createQuery(hql);                                  // creating the query object  
            List cList=query.list();                                 //executing the Query object
            for(CustomerEntity ce:cList){
                Customer customer=new Customer();                
                customer.setAddress(ce.getAddress());
                customer.setContactNumber(ce.getContactNumber());
                customer.setCustomerId(ce.getCustomerId());
                customer.setCustomerName(ce.getCustomerName());
                customer.setDateOfBirth(ce.getDateOfBirth());
                returnList.add(customer);                
            }           
        } catch (HibernateException exception) {
            throw exception;    
        } catch (Exception exception) {            
            throw exception;
        }finally {
            if(session.isOpen()|| session!=null){
                session.close();
            }
        }
        return returnList;
    }
}
public class CustomerDAOImpl implements CustomerDAO {
    public List getCustomerIdAndName() throws Exception {
  // TODO Auto-generated method stub
  SessionFactory sessionFactory = HibernateUtility.createSessionFactory();
  Session session = null;
  List objects = new LinkedList();
  List customers = new LinkedList();
  Customer customer = null;
  
  //Creating the query to fetch customer id and names of the customers
  String hql= "select CE.customerId,CE.customerName from CustomerEntity CE ";
  
  /* We can also create the query without using the alias name
   * String hql= "select customerId,customerName from CustomerEntity";
   */
  try {
   session = sessionFactory.openSession();
   
   //Creating query object
   Query query = session.createQuery(hql);
   
   //Executing the query
   objects = query.list();
   
   for (Object[] object : objects) {
    customer = new Customer();
    customer.setCustomerId((Integer)object[0]);
    customer.setCustomerName((String)object[1]);
    customers.add(customer);
    
   }
   
  } catch (HibernateException exception) {
   exception.printStackTrace();
   DOMConfigurator.configure("src/resources/log4j.xml");
   Logger logger = Logger.getLogger(this.getClass());
   logger.error(exception.getMessage(), exception); 
   throw new Exception("DAO.TECHNICAL_ERROR");
  } 
  catch (Exception exception) {
   exception.printStackTrace();
   DOMConfigurator.configure("src/resources/log4j.xml");
   Logger logger = Logger.getLogger(this.getClass());
   logger.error(exception.getMessage(), exception);
   throw exception;
  }
  finally {
   if(session.isOpen()|| session!=null){
    session.close();
   }
  
  
 }
  return customers;
 }}
When you invoke getCustomerDetails() of CustomerDAO.java from main() method, you get the below output if you set 'Joey' as customerName and '12-Jun-1993' as dateOfBirth
Below are the steps used in the code to perform retrieval using HQL:
Below are the steps in detail to perform retrieval operations using HQL.

1.Create SessionFactory and Session 

Creating SessionFactory and Session is already discussed in Hibernate CRUD.

2. Create Query

The object of Query interface in HQL represents your query in Hibernate.  The below code in CustomerDao.java ensures the creation of Query object.
String hql = "from CustomerEntity ce where ce.customerName='"+customerName+"' and ce.dateOfBirth='"+dateOfBirth;

Query query = session.createQuery(hql);
Query object is obtained by invoking createQuery() method of Session, passing your query String as argument.  You can see that the query appears exactly as SQL except that it uses entity class(CustomerEntity) and its property(customerName) name instead of table and column name. ce is instance of CustomerEntity which is an alias name of CustomerEntity. The alias name can be given with explicit AS clause as well.  
String hql = "from CustomerEntity AS ce where ce.customerName='"+customerName+"' and ce.dateOfBirth='"+dateOfBirth+"'";
Query query = session.createQuery(hql)
Have you observed that the query starts with FROM clause without a SELECT clause?  The use of SELECT clause is optional when you want to retrieve all the columns as given above. With SELECT clause the query will be,
String hql = "Select ce from CustomerEntity ce where ce.customerName='"+customerName+"' and ce.dateOfBirth='"+dateOfBirth+"'";
Query query = session.createQuery(hql)
Suppose, let us say you want to retrieve only the customer names of all the customers, then the SELECT clause is must for your HQL query as shown below,
String hql = "Select ce.customerName from CustomerEntity ce;
Query query = session.createQuery(hql);

3.  Execute the Query

Once the Query object is created, it needs to be executed using its list() method.
List cList=query.list();
The list() method returns java.util.List object as a result. The return value can be stored in List as your query retrieves all the columns/properties of a customer.
Consider that your query retrieves only a single column, let us say customerName, then the return value of list() can be stored in a list of String as customerName is of type String. Sample code and the output is given below:
String hql = "Select ce.customerName from CustomerEntity ce;
Query query = session.createQuery(hql);
List nameList = query.list();
Output:

Suppose your query retrieves more than one column, let us say customerId and customerName, then the return type of list() will be List
String hql = "Select ce.customerId,ce.customerName from CustomerEntity ce;
Query query = session.createQuery(hql);
Output:

In addition to the list() method, Query provides uniqueResult() method as well to execute the SELECT query. The uniqueResult() method returns an Object that matches your query, or null if none of the rows matches with your query. 
String hql = "from CustomerEntity AS ce where ce.customerName='"+customerName+"';
Query query = session.createQuery(hql);
CustomerEntity ce = (CustomerEntity)query.uniqueResult();
You have to be careful in using this method as this method throws NonUniqueResultException if your query returns more than one matching results. If you use it only for those queries which returns zero or one result, you are saved from the trouble of processing a list and extracting the first element.

4.Close Session and SessionFactory

Closing a Session and SessionFactory is already discussed in Hibernate CRUD operations.

Thus, you have learned how to use HQL to perform retrieval operation.  But, can you say that your query is free from injection attack?..
Parameter Setting
As we already discussed in JDBC, if your query uses String concatenation to bind the dynamic values, it may lead to "SQL Injection".
String hql = "from CustomerEntity ce where ce.customerName='"+customerName+"' and ce.dateOfBirth='"+dateOfBirth;
To overcome this vulnerability, you can use either of the below HQL parameter setting techniques.
1.Positional Parameters
When you use positional parameters, your query will be,
String hql = "from CustomerEntity ce where ce.customerName=? and ce.dateOfBirth=?";
Here, '?' symbols specifies that you will provide a parameter value for that position during query execution. To set the parameter value you can use either setParameter() or setXXX() methods of Query.  XXX in setXXXX() method represents the Java data type of the parameter that you wish to set.
query.setString(0,customerName);   //or query.setParameter(0, customerName)
query.setCalendar(1,dateOfBirth);  //or query.setParameter(1,dateOfBirth)
Each ? is substituted with position value starting from 0 using appropriate setXXX() methods.
2. Named Parameters
Instead of using ? symbol, you can also use user defined names to the parameters to enhance readability of your query. So, your query becomes as below using named parameters.
String hql = "from CustomerEntity ce where ce.customerName=:customerName and ce.dateOfBirth=:dateOfBirth";
The parameter name should be prefixed with : (colon) and the name should be unique in a Query. 
All the parameters should be set with values before executing the query. To provide the values for named parameters, you can use either setParameter() or setXXX() methods of Query.
query.setParameter("customerName", customerName);
query.setParameter("dateOfBirth", dateOfBirth);
3.Bean Object
Bean object with  properties corresponding to the parameter names can also be used to set the parameter values. The below code illustrates how to use bean object to set parameters.
String hql = "from CustomerEntity ce where ce.customerName=:customerName and ce.dateOfBirth=:dateOfBirth";

// Creating a new bean object which will be used to set parameters
Customer customerBean = new Customer(); 
customerBean.setCustomerName(customer.getCustomerName());
customerBean.setDateOfBirth(customer.getDateOfBirth());
   
// Setting the parameter values using the bean object
query.setProperties(customerBean);
Using either of these parameter setting techniques in your query will save your application from query injection attack.
Good coding practices in parameter setting
If more than one parameters are to be set in your query:
  • Defining a positional parameter after named parameter will cause an error?
SELECT c.customerName FROM CustomerEntity c WHERE c.customeId= :id AND c.address = ?
Image is not available    
  • It is allowed to define a positional parameter before a named parameter?

SELECT c.customerName FROM CustomerEntity c WHERE c.customeId= ? AND c.address = :ad
Image is not available
As a good coding practice, it is advisable to use any one of the parameter setting technique at a time.
At the same time, named parameters are preferred as,
  • It is easier to use and maintain
  • It increases the readability of the code
  • It makes the code look cleaner
Requirement 9.2: Update Using HQL

In order to solve requirement R.9.2, your application is going to reuse the same CustomerEntity.java. 
Implement CustomerDAO.java as shown below to update the address and contact number for the given customer name and date of birth.
public class CustomerDAOImpl implements CustomerDAO{
public Integer updateCustomerDetails(String customerName,Calender dob,String newAddress,Long newNumber)throws Exception{
  SessionFactory sessionFactory= null;
  Session session=null;
  Integer noOfRowsUpdated = null;

  // Creating a query to update the address and contact number of the customer
  String hql = "Update CustomerEntity ce set ce.address=:address,ce.contactNumber=:contactNumber where 
                           ce.customerName=:customerName and ce.dateOfBirth=:dateOfBirth";
  try {
   sessionFactory=HibernateUtility.createSessionFactory();
   session=sessionFactory.openSession();

   Query query = session.createQuery(hql);                           // Creating the query object
   
   query.setParameter("address", newAddress);             //setting parameters
   query.setParameter("dateOfBirth", dob);
   query.setParameter("customerName", customerName);
   query.setParameter("contactNumber", newNumber);

                        session.beginTransaction()
   noOfRowsUpdated = query.executeUpdate();          // Executing the query, returns the number of rows updated
                        session.getTransaction().commit();
  }catch (HibernateException exception) {
   throw new Exception("DAO.TECHNICAL_ERROR");
  }catch (Exception exception) {
   throw exception;
  }finally {
   if(session.isOpen()|| session!=null){
    session.close();
   }
  } 
  return noOfRowsUpdated;
 }
}
The steps used in CustomerDAO.java to update the customer details are:
  1. Create instance of SessionFactory and Session
  2. Create a Query object to update the details
  3. Begin a transaction
  4. Execute the Query
  5. Commit the transaction
  6. Close SessionFactory and Session

As you already know how to create SessionFactory and Session instances (step 1), how to close them (step 6),  and how to define the transaction context (step 3 and 5), let us discuss writing a HQL query for performing the update operation (step 2 and 4).

2. Create a Query object to update the details

For performing update operation, you need to create a Query object with update statement as shown in the code below.
String hql = "Update CustomerEntity ce set ce.address=:address,ce.contactNumber=:contactNumber where 
                        ce.customerName=:customerName and ce.dateOfBirth=:dateOfBirth";

Query query = session.createQuery(hql);

4. Execute the Query object

To execute the Query you need to use executeUpdate() method of Query.
query.executeUpdate();
This method will execute the query and return the number of rows updated.


Note: Update requires a transaction context as it changes the database values.  SELECT query should be executed using list() method and other DML queries (like insert, update and delete) should be executed using executeUpdate() method.
Requirement 9.3: Delete Using HQL
In order to solve requirement R.9.3, your application is going to reuse the same CustomerEntity.java. 
Implement CustomerDAO.java as shown below to delete the customer details based on address.
public class CustomerDAOImpl implements CustomerDAO{
public Integer deleteCustomerDetails(String address)throws Exception{
        SessionFactory sessionFactory=null;
        Session session=null;
        Integer noOfRowsUpdated= null ;
        
        String hql = "delete from CustomerEntity ce where ce.address=:address";    // Creating the query 
        try {
            sessionFactory=HibernateUtility.createSessionFactory();
            session=sessionFactory.openSession();
            Query query = session.createQuery(hql);                                // Creating the query object 
            query.setParameter("address", address);
            
            session.beginTransaction();
            noOfRowsUpdated = query.executeUpdate();                               // Executing the query
            session.getTransaction().commit();
            
        }catch (HibernateException exception) {
            throw new Exception("DAO.TECHNICAL_ERROR");
        }catch (Exception exception) {
            throw exception;
        }finally {
            if(session.isOpen()|| session!=null){
                session.close();
            }
        }
        return noOfRowsUpdated;
    }
}
The steps used in CustomerDAO.java to perform delete operation are:
  1. Create instance of SessionFactory and Session
  2. Create a Query object to delete the details
  3. Begin a transaction
  4. Execute the Query object
  5. Commit the transaction
  6. Close SessionFactory and Session

As you already know how to create SessionFactory and Session instances (step 1), how to close them (step 6),  and how to define the transaction context (step 3 and 5), let us discuss writing a query to perform the delete operation (step 2 and 4).

2.Create a Query object to delete the details

For performing delete operation, you need to create a Query object with delete statement as shown in the code below.
String hql = "delete from CustomerEntity ce where ce.address=:address";

4. Execute the Query object

To execute the Query you need to invoke executeUpdate() method.
query.executeUpdate();
This method will execute the query and return the number of rows deleted.

 Requirement 9.4: Insert Using HQL
In order to solve requirement R.9.4, your application is going to reuse the same CustomerEntity.java. 
Implement CustomerDAO.java as shown below to insert the contents of CUSTOMER table to SENIORCITIZEN Table.
public class CustomerDAOImpl implements CustomerDAO{
public Integer filterCustomers(String address,Calendar dateOfBirth) throws Exception{
        SessionFactory sessionFactory= null;
        Session session=null;
        Integer noOfRowsAffected = null;
        // Creating the query to filter the customers
        String hql = "insert into SeniorCustomerEntity(customerId,customerName,dateOfBirth,address,contactNumber) "
                      +" select customerId,customerName,dateOfBirth,address,contactNumber "
                      + "from CustomerEntity ce where ce.address=:address and ce.dateOfBirth<:dateofbirth span="">
        
        try {
            sessionFactory = HibernateUtility.createSessionFactory();
            session = sessionFactory.openSession();
            Query query = session.createQuery(hql);                                    // Creating query object
            query.setParameter("address", address);                                    //parameter setting
            query.setParameter("dateOfBirth", dateOfBirth);
            
            session.beginTransaction();           
            noOfRowsAffected = query.executeUpdate();                                 // Executing the query
            session.getTransaction().commit();
            
        }catch (HibernateException exception) {
            throw new Exception("DAO.TECHNICAL_ERROR");
        }catch (Exception exception) {
            throw exception;
        }finally {
            if(session.isOpen()|| session!=null){
                session.close();
            }
     }
        return noOfRowsAffected;
    }
}
The steps to be performed in CustomerDAO.java to insert the customer details into another table are:
  1. Create instance of SessionFactory and Session
  2. Create a Query object to insert the details
  3. Begin a transaction
  4. Execute the Query object
  5. Commit the transaction
  6. Close SessionFactory and Session

As you already know how to create SessionFactory and Session instances (step 1), how to close them (step 6),  and how to define the transaction context (step 3 and 5), let us discuss writing a query to perform the insert operation (step 2 and 4).

2.Create a Query object to insert the details

For performing insert operation, you need to create a Query object with insert statement as shown in the code below.
String hql = "insert into SeniorCustomerEntity(customerId,customerName,dateOfBirth,"
    + "address,contactNumber) "+
  " select customerId,customerName,dateOfBirth,address,contactNumber "
  + "from CustomerEntity ce where ce.address=:address and ce.dateOfBirth<:dateofbirth pre="">

4. Execute the Query object

To execute the Query you need to invoke executeUpdate() method.
query.executeUpdate();
This method will execute the query and return the number of rows inserted.
NOTE: HQL supports the insertion of data from one table to another table. HQL cannot be used to directly insert values into a table as it has to be performed with Session methods persist()/save()
Requirement 10: HQL Joins
InfyBank use the following table structures to store the CUSTOMER and ACCOUNT details:

CUSTOMER table:
ACCOUNT table:

R.10.1 Retrieve all the active accountIds along with the customerName who is owning that account.
In order to solve requirement R.10.1, you need to join ACCOUNT and CUSTOMER table as account id is available in ACCOUNT and customerName is available in CUSTOMER.
You are going to reuse the same CustomerEntity.java to solve this requirement. 
Implement AccountEntity.java having many to one relationship with CustomerEntity as shown below.
@Entity
@Table(name="account")
public class AccountEntity {
 @Id
 private Long accountNo;
 private String accountType;
 @Temporal(TemporalType.DATE)
 private Calendar openingDate;
 private Double balance;
 private String accountStatus;
 @ManyToOne(cascade=CascadeType.ALL)
 @JoinColumn(name="customerId")
 private CustomerEntity customer;
 //generate getters and setters
}
Implement CustomerDAO.java as shown below to retrieve account details based on customerId.
public class CustomerDAOImpl implements CustomerDAO {
 public List innerJoin() throws Exception {
  SessionFactory sessionFactory = HibernateUtility.createSessionFactory();
  Session session = sessionFactory.openSession();
  List list=null;
  try {
   //Explicit form
   Query query = session.createQuery("select c.customerName,a.accountNo"
                                      +" from AccountEntity a Inner Join a.customer c");

                        //Implicit form 
                        /*Query query = session.createQuery("select ae.customer.customerName,ae.accountNo"
                                        +" from AccountEntity ae"); */   
   
   list=query.list();
     
  } catch (HibernateException exception) {
   throw new Exception("DAO.TECHNICAL_ERROR"); 
  } catch (Exception exception) {
   throw exception;
  } finally {
   session.close(); 
  }
  return list;
 }
}
Observe the JOIN clause used in HQL query.  In HQL, join query can be written in two different ways:
  1. Implicit join - JOIN keyword not used explicitly and the relationship is dereferenced using dot operator
  2. Explicit join - JOIN keyword is used explicitly which increases the readability of code
The syntax for both the join types is shown in the above code.  Apart from inner join which is used in the above code, HQL supports left outer join and right outer join as well.
The output of the above code is:


Note: HQL join can be used only on associated entities.  To join the entities, the ON condition is not required, as the relationship is already mentioned using annotations in entity classe
Requirement 11: HQL Functions, Group by, Having, Order by


InfyBank has provided the following requirements which you need to solve.
R.11.1 Find the minimum, maximum, sum, average and count of total balances among all the accounts.
R.11.2 Find the customer whose name starts with "G" and having length less than 6, then display their name in uppercase, accountnumber, first three letters of account type.

R.11.3 Display the accountType and average balance of the account where the number of accounts in each account type is greater than 1. Display the output in ascending order of average balance.
Requirement 11.1: Aggregate funtions

n order to solve the requirement R.11.1, you need to use aggregate functions in your query.
Implement AccountDAO.java as shown below to solve the requirement.
public class AccountDAOImpl implements AccountDAO {
      public result minAggregate() throws Exception {
  
  SessionFactory sessionFactory = HibernateUtility.createSessionFactory();
  Session session = null;
  Object result[]= null;

  String hql = "select min(ae.balance), max(ae.balance), sum(ae.balance), avg(ae.balance), count(ae.balance)"
                               +" from AccountEntity ae";              // Creating the query
  try{
  session = sessionFactory.openSession();
  Query query = session.createQuery(hql);        // Creating the query object
  result= query.uniqueResult();             // Executing the query
  }catch (HibernateException exception) {
   throw new Exception("DAO.TECHNICAL_ERROR");
  }catch (Exception exception) {
   throw exception;
  }finally {
   if(session.isOpen()|| session!=null){
       session.close();
   }
  }
  return result;
 }
}
All the min()max()sum()avg()count() methods work same as the methods of SQL.

The output of the code will be:

In order to solve requirement R.11.2,  you need to use String functions in our query.
Implement CustomerDAO.java as shown below which satisfies the requirement:
public class CustomerDAOImpl implements CustomerDAO {
 public List innerJoin() throws Exception {
  SessionFactory sessionFactory = HibernateUtility.createSessionFactory();
  Session session = sessionFactory.openSession();
  List list=null;
  try {  
   Query query = session.createQuery("select upper(ae.customer.customerName), "
      + "concat(substring(ae.accountType,1,3), ae.accountNo) "
      + " from AccountEntity ae where ae.customer.customerName like 'G%'"
      + " and length(ae.customer.customerName)>5");
      
   list=query.list();
     
  } catch (HibernateException exception) {
   throw new Exception("DAO.TECHNICAL_ERROR"); 
  } catch (Exception exception) {
   throw exception;
  } finally {
   session.close();
   
  }
  return list;

 }
}

The output will be:


Requirement 11.3: Group by, Having and Order by
In order to solve the requirement R.11.3, the SQL query will be:
SELECT accountType, AVG(balance) FROM account GROUP BY accountType HAVING COUNT(accountNo)>=2 ORDER BY AVG(balance)
You can write the same query in HQL as shown below in the AccountDAO.java class. 
public class AccountDAOImpl implements AccountDAO{

 @SuppressWarnings("unchecked")
 @Override
 public void groupByHavingOrderByFunction() throws Exception {
  SessionFactory sessionFactory = HibernateUtility.createSessionFactory();
  Session session = sessionFactory.openSession();

  try { 
   Query query=session.createQuery("select accountType, avg(balance) from AccountEntity "
                                         + "group by accountType having count(accountNo)>=2 order by avg(balance)");
                        //Query using group by and having and order By 
   List values=query.list();

   for (Object[] objects : values) {
    System.out.println("Account Type :" +objects[0]);
    System.out.println("Average Balance :"+objects[1]);
   } 
  } catch (HibernateException exception) {
   throw new Exception("DAO.TECHNICAL_ERROR");
  } catch (Exception exception) {
   throw exception;
  } finally {
   session.close();
  }
        }
}
You can use group by, having, order by clauses as well in HQL as shown in the code.
The output of the code will be:

Named Query: @NamedQuery
By now you have learnt how to use HQL to perform your database operations.  But, if you look into your application, you can realize that the queries are scattered all over your application and you might have rewritten the same query in multiple places to achieve the same functionality.  Queries written in this way makes your application hard to maintain and your code looks ugly.
Consider that your CustomerDAO.java is reusing a retrieval query on CUSTOMER table very frequently at so many places.  
public class CustomerDAOImpl implements CustomerDAO {
        public void updateCustomer() throws Exception {

                // code to create SessionFactory and Session 
  
                Query query = session.createQeury("from CustomerEntity");
                List clist = query.list();

                //rest of your code    
        } 
        public void getCustomerDetails()throws Exception {

                // Code to create SessionFactory and Session
   
                Query query = session.createQeury("from CustomerEntity");
                List clist = query.list();

                // rest of your code   
        }
        public void DeleteCustomers()throws Exception {

                // Code to create SessionFactory and Session 

                Query query = session.createQeury("from CustomerEntity");
                List clist = query.list();
                
                // rest of your code 
        }
}
The same query is repeated in 3 different places and it will be compiled and executed every time when the code is executed.  
So, if your application reuses some queries of an entity in different DAO classes or methods as shown above, you can store the queries within the entity itself using Hibernate's named query concept, thus avoiding the need to rewrite the same query at different places.
In order to use named query, you have to write the query inside your entity class using @NamedQuery annotation as shown below:
@Entity
@Table(name="customer")
@NamedQuery(name ="CustomerEntity.retrieve", query ="from CustomerEntity")
public class CustomerEntity {
 @Id
 private Integer customerId;
 private String customerName;
 @Temporal(TemporalType.DATE)
 private Calendar dateOfBirth;
 private String address;
        @Column(name="phoneNumber")
 private Long ContactNamber;
 
 //generate getters and setters
}
The name attribute of @NamedQuery is used to specify a user defined name to the query. The query attribute is used to specify the query which need be stored in compilation form. The named query will be compiled and kept ready with execution plan along with the entity instance.
You can use this query in CustomerDAO.java as many times as you want by referring the query by its name in getNamedQuery() method of Session.
public class CustomerDAOImpl implements CustomerDAO {
        public void updateCustomer() throws Exception {

                // code to create SessionFactory and Session
   
                Query query = session.getNamedQuery("CustomerEntity.retrieve");
                List clist = query.list();
                
               //rest of your code    
        } 
        public void getCustomerDetails()throws Exception {

                // Code to create SessionFactory and Session 
  
                Query query = session.getNamedQuery("CustomerEntity.retrieve");
                List clist = query.list(); 

                // rest of your code   
        }
        public void DeleteCustomers()throws Exception {

                // Code to create SessionFactory and Session 

                Query query = session.getNamedQuery("CustomerEntity.retrieve");
                List clist = query.list(); 

                // rest of your code 
        }
}
Named Query: @NamedQueries
Besides the retrieval query that we discussed now, let us say your application is using an update query as well repeatedly at so many places. So, if you want to define more than one named query in your entity class, you must define them inside @NamedQueries annotation as shown below.
@Entity
@Table(name="customer")
@NamedQueries({
 @NamedQuery(name ="CustomerEntity.update", query ="update CustomerEntity CE set CE.address=?,CE.contactNumber=? where CE.customerName=? and CE.dateOfBirth=?")
 ,@NamedQuery(name="CustomerEntity.retreive", query="from CustomerEntity")
})
public class CustomerEntity {
 @Id
 private Integer customerId;
 private String customerName;
 @Temporal(TemporalType.DATE)
 private Calendar dateOfBirth;
 private String address;
 private Long contactNumber;
 
 //generate getters and setters
}

Difference between SQL and HQL


Query Interface

The methods used to execute the query are
The methods used to set parameters are:

Aggregate Functions

String Functions


Requirement 12: Criteria API
Consider the below requirements given by InfyBank to solve.
R.12.1 Retrieve customer name and address of all the customers whose contact number is not null and address is matching completely or partially to the given address.  Retrieve the details in alphabetical order of customer name.  Below is the CUSTOMER table maintained by InfyBank.

R.12.2 Retrieve the total number of accounts, total balance from all the accounts, average balance of accounts, total number of unique customers having account. Below is the ACCOUNT table maintained by InfyBank.

By seeing the requirements you are confident that these requirements can be solved by HQL.  But, imagine that you are week in HQL as it is influenced by SQL language syntax.  Being a Java expert, you want to solve it in object oriented way itself rather than using query syntax.
Requirement 12.1: Using Criteria
Implement CustomerDAO.java as given below in order to solve the requirement R.12.1, solving in object oriented way using CriteriaAPI. 
public class CustomerDao {
 
 @SuppressWarnings("unchecked")
 public void SearchCustomerByAddress() throws Exception{

  SessionFactory sessionFactory=HibernateUtility.createSessionFactory();
  Session session=null;
  try {
   String searchString="M";
   session=sessionFactory.openSession();
   Criteria criteria=session.createCriteria(CustomerEntity.class);
   
   Criterion condition1 = Restrictions.like("address", searchString+"%");
                        Criterion condition2 = Restrictions.isNotNull("contactNumber");
                       
                        criteria.add(Restrictions.and(condition1,condition2));
   
   ProjectionList requiredColumns = Projections.projectionList();
   
   requiredColumns.add(Projections.property("customerName"));
   requiredColumns.add(Projections.property("address"));
   
   criteria.setProjection(requiredColumns);
   
   criteria.addOrder(Order.asc("customerName"));
   
   List cusList=criteria.list();
   System.out.println( "CustomerName"+ "\t" + "Address");
   for (Object[] obj : cusList) {
    System.out.println(obj[0]  + "\t\t" + obj[1]);
   }
   
  }catch (HibernateException exception) {   
                        throw new Exception("DAO.TECHNICAL_ERROR");
                }catch (Exception exception) {   
                        throw exception;
                }finally {
                        if(session.isOpen()|| session!=null)
                                session.close();
                }
         }
}
You don't see any query written in the above code to solve the requirement. The whole code is written in object oriented way (Objects, dot operator and methods) using CriteriaAPI.
The SQL query to solve R.12.1 would be,
SELECT customerName, address FROM Customer WHERE address LIKE "?%" AND phoneNumber IS NOT NULL ORDER BY customerName DESC
Criteria: From clause
In solving Requirement R.12.1, let us see how each clauses of SQL is written in CriteriaAPI step by step.
Consider the below SQL query.
SELECT * FROM Customer;
The below highlighted code is equivalent to the FROM clause of above query.  The createCriteria() method of Session creates a Criteria instance which represents the query against the particular entity class given.  
public class CustomerDao {
 
 @SuppressWarnings("unchecked")
 public void SearchCustomerByAddress() throws Exception{

  SessionFactory sessionFactory=HibernateUtility.createSessionFactory();
  Session session=null;
  try {
   String searchString="M";
   session=sessionFactory.openSession();
   Criteria criteria=session.createCriteria(CustomerEntity.class);
 
   List cusList=criteria.list();   //executes the statement and returns the list of customer entity
 
  }catch (HibernateException exception) {   
                        throw new Exception("DAO.TECHNICAL_ERROR");
                }catch (Exception exception) {
                        throw exception;
                }finally {
                        if(session.isOpen()|| session!=null)
                                session.close();
                }
         }
}
The list() method of Criteria executes the query and gets the list of matched objects. The output of this program would be:

Criteria: Where clause--RestrictionClause
Now let us add WHERE clause to our query as given below.
SELECT * FROM Customer WHERE address LIKE "?%";
For adding WHERE clause in criteria, you have to use Restrictions class and their methods. These methods return a Criteiron object which needs to be added to the Criteria using add() method as shown below:
public class CustomerDao {
 
 @SuppressWarnings("unchecked")
 public void SearchCustomerByAddress() throws Exception{

  SessionFactory sessionFactory=HibernateUtility.createSessionFactory();
  Session session=null;
  try {
   String searchString="M";
   session=sessionFactory.openSession();
   Criteria criteria=session.createCriteria(CustomerEntity.class);
   
   criteria.add(Restrictions.like("address", searchString+"%"));
   
   List cusList=criteria.list();
   
  }catch (HibernateException exception) {   
                        throw new Exception("DAO.TECHNICAL_ERROR");
                }catch (Exception exception) {   
                        throw exception;
                }finally {
                        if(session.isOpen()|| session!=null)
                                session.close();
                }
         }
}
The like() method of Restrictions class is same as like operator in SQL. Here first argument is property name and the second argument is the String with which the property value should be matched.
The output will be:
Criteria: Where clause - Multiple Condition
Let us add the second condition in WHERE clause of our query.
SELECT * FROM Customer WHERE address LIKE "?%" AND phoneNumber IS NOT NULL;
For adding more than one conditions using AND/OR, you have to use and()/or() methods  of Restrictions class. These methods accpet Criterion objects as parameters and return us Criterion object which needs to be added to the Criteria as shown below:
public class CustomerDao {
 
 @SuppressWarnings("unchecked")
 public void SearchCustomerByAddress() throws Exception{

  SessionFactory sessionFactory=HibernateUtility.createSessionFactory();
  Session session=null;
  try {
   String searchString="M";
   session=sessionFactory.openSession();
   Criteria criteria=session.createCriteria(CustomerEntity.class);
   
   Criterion condition1 = Restrictions.like("address", searchString+"%");
                        Criterion condition2 = Restrictions.isNotNull("contactNumber");
                       
                        criteria.add(Restrictions.and(condition1,condition2));
     
   List Object[] cusList=criteria.list();
   
  }catch (HibernateException exception) {   
                        throw new Exception("DAO.TECHNICAL_ERROR");
                }catch (Exception exception) {   
                        throw exception;
                }finally {
                        if(session.isOpen()|| session!=null)
                                session.close();
                }
         }
}
In the same way how and() is used in the above code,  you can use or() method as well. There is an alternative way for AND  i.e. you can just add the Restrictions to Criteria as shown below:
criteria.add(Restrictions.like("address", searchString+"%")).add(Restrictions.isNotNull("contactNumber"));
The output of the code will be:

Criteria: Select clause-ProjectionClause
Let us add SELECT clause elements in our query as shown below:
SELECT customerName FROM Customer WHERE address LIKE "?%" AND phoneNumber IS NOT NULL;
To represent the column list in SELECT clause, you have to use Projections class. The property() method of Projections is used to indicate the property name that we want to fetch and it returns the Projection object which needs to be set to Criteria using setProjection() method. 
public class CustomerDao {
 
 @SuppressWarnings("unchecked")
 public void SearchCustomerByAddress() throws Exception{

  SessionFactory sessionFactory=HibernateUtility.createSessionFactory();
  Session session=null;
  try {
   String searchString="M";
   session=sessionFactory.openSession();
   Criteria criteria=session.createCriteria(CustomerEntity.class);
   
   Criterion condition1 = Restrictions.like("address", searchString+"%");
                        Criterion condition2 = Restrictions.isNotNull("contactNumber");
                       
                        criteria.add(Restrictions.and(condition1,condition2));
   
   criteria.setProjection(Projections.property("customerName"));
   
   List String cusList=criteria.list();
   
  }catch (HibernateException exception) {   
                        throw new Exception("DAO.TECHNICAL_ERROR");
                }catch (Exception exception) {   
                        throw exception;
                }finally {
                        if(session.isOpen()|| session!=null)
                                session.close();
                }
         }
}
The output would be:

Since we are not retrieving the whole row, the list() method does not return list of customer entity objects. It will be list of strings as we only retrieve names. i.e. the list type will be based on type of attribute retrieved.
Criteria: Select clause - multiple selection
As per the requirement R.12.1, we need to retrieve customerName and address. So, let us add one more column in SELECT clause column list.
SELECT customerName, address FROM Customer WHERE address LIKE "?%" AND phoneNumber IS NOT NULL;
If you want to select multiple columns, you have to create an instance of ProjectionsList, add all the properties to it and set the ProjectionsList instance to Criteria as shown below:
public class CustomerDao {
 
 @SuppressWarnings("unchecked")
 public void SearchCustomerByAddress() throws Exception{

  SessionFactory sessionFactory=HibernateUtility.createSessionFactory();
  Session session=null;
  try {
   String searchString="M";
   session=sessionFactory.openSession();
   Criteria criteria=session.createCriteria(CustomerEntity.class);
   
   Criterion condition1 = Restrictions.like("address", searchString+"%");
                        Criterion condition2 = Restrictions.isNotNull("contactNumber");
                       
                        criteria.add(Restrictions.and(condition1,condition2));
   
   ProjectionList requiredColumns = Projections.projectionList();
   
   requiredColumns.add(Projections.property("customerName"));
   requiredColumns.add(Projections.property("address"));
   
   criteria.setProjection(requiredColumns);
   
   List Object[] cusList=criteria.list();
   
  }catch (HibernateException exception) {   
                        throw new Exception("DAO.TECHNICAL_ERROR");
                }catch (Exception exception) {   
                        throw exception;
                }finally {
                        if(session.isOpen()|| session!=null)
                                session.close();
                }
         }
}
The projectionList() method of Projections creates the the instance of ProjectionList, which should be populated with Projections using add() method. Then ProjectionList instance should be added to Criteria using setProjection() method.
The output of the code will be:
Since data types of retrieving columns are of different types, list() returns list of Object array
Criteria: Order By clause
Now let us add ORDERBY clause to the previous SQL query (Final Query for R.12.1)
SELECT customerName, address FROM Customer WHERE address LIKE "?%" AND phoneNumber IS NOT NULL ORDER BY customerName;
For ordering the rows, you have to use Order class. Use asc() or desc() methods of Order as per the requirement which returns an Order object.  The Order object should be added to Criteria using addOrder() method. 
public class CustomerDao {
 
 @SuppressWarnings("unchecked")
 public void SearchCustomerByAddress() throws Exception{

  SessionFactory sessionFactory=HibernateUtility.createSessionFactory();
  Session session=null;
  try {
   String searchString="M";
   session=sessionFactory.openSession();
   Criteria criteria=session.createCriteria(CustomerEntity.class);
   
   Criterion condition1 = Restrictions.like("address", searchString+"%");
                        Criterion condition2 = Restrictions.isNotNull("contactNumber");
                       
                        criteria.add(Restrictions.and(condition1,condition2));
   
   ProjectionList requiredColumns = Projections.projectionList();
   
   requiredColumns.add(Projections.property("customerName"));
   requiredColumns.add(Projections.property("address"));
   
   criteria.setProjection(requiredColumns);
   
   criteria.addOrder(Order.asc("customerName"));
   
   List Object[] cusList=criteria.list();
   
    System.out.println( "CustomerName"+ "\t" + "Address");
   for (Object[] obj : cusList) {
    System.out.println(obj[0]  + "\t\t" + obj[1]);
   }
   
  }catch (HibernateException exception) {   
                        throw new Exception("DAO.TECHNICAL_ERROR");
                }catch (Exception exception) {   
                        throw exception;
                }finally {
                        if(session.isOpen()|| session!=null)
                                session.close();
                }
         }
}
Here asc() method takes propertyName as parameter and orders the rows in ascending order of property value. For descending order you can use desc() method.
The final output of the implementation of requirement R.12.1 will be
Requirement 12.2: Criteria Aggregate Funtions
he SQL statement for solving the requirement R.12.2 is,
SELECT count(*), sum(balance), avg(balance), count(distinct customerId) FROM Account;
Since the SELECT clause has to retrieve value of multiple aggregate functions,  you have to create a ProjectionList which contains Projections
public class CustomerDao {
 
 @SuppressWarnings("unchecked")
 public void SearchCustomerByAddress() throws Exception{

  SessionFactory sessionFactory=HibernateUtility.createSessionFactory();
  Session session=null;
  try {
   Criteria criteria = session.createCriteria(AccountEntity.class);
   ProjectionList projList = Projections.projectionList();
 
   projList.add(Projections.rowCount());
   projList.add(Projections.sum("balance"));
   projList.add(Projections.avg("balance"));
   projList.add(Projections.countDistinct("customerId"));
   criteria.setProjection(projList);
   list = criteria.list();
   
   System.out.println("Number of accounts: "+list.get(0)[0]);
   System.out.println("total balance of all accounts: "+list.get(0)[1]);
   System.out.println("average balance for each accounts: "+list.get(0)[2]);
   System.out.println("Number of unique customers: "+list.get(0)[3]);
   
  }catch (HibernateException exception) {   
                        throw new Exception("DAO.TECHNICAL_ERROR");
                }catch (Exception exception) {   
                        throw exception;
                }finally {
                        if(session.isOpen()|| session!=null)
                                session.close();
                }
         }
}
The output of the above code will be:
Criteria API: An Overview

Difference between HQL and Criteria


Note that Criteria API can be used only for retrieval operation.

Criteria API

By this time now you should be able to use below classes and interfaces of criteria API to perform the database operations.

Restrictions

Few methods of Restrictions class for basic relational constraints:
Few methods of Restrictions class for String matching constraints:18h,19h

Projections

Few aggregate functions are:
Requirement 13: Hibernate caching
The customer base of InfyBank has increased at exponential rate and all of them have started using the InfyBank application for their day-to-day banking queries like viewing their account details, loan details, locker details, updating their personal details, transferring money, etc.  Because of its huge number of user base (approximately 13Mn), InfyBank realized that the performance of their application has gone down due to heavy traffic between their application and database server.  
For example, if a customer wants to view the loan details, the application has to send a SELECT query to the database.  If the same request is raised by all the 13Mn customers, then the database will be accessed by 13Mn times for the same data.  
So, InfyBank has come up with the below requirement which you need to solve.
R.13.1. Improve the performance of the application by reducing the traffic between application and database server.
In order to solve the requirement R.13.1, you need to use Hibernate's caching mechanism. 
What is Caching?
To understand the term 'Caching', let us do below activity:
  • Open the chrome browser and enter http://ilp02/stream
  • Select any course like Fundamentals of Java Programming
  • Click on View TOC/Start learning and open TOC
  • Select first topic and go to the page
  • Go 4-5 pages forward
  • Remove the LAN connection
  • Now click on browser's back and forward button at top left corner
Did you observe the pages are still appearing. How is it possible?
This is because of browser cache. Your browser is caching the copy of visited pages and uses that copy to render when you revisit that page. The copied data will be available until you close the browser or clear the browser cache by refreshing it.
Cache are relatively small blocks of memory which stores the data temporarily. This data is either directly retrieved from database or some transient data computed from database records. The process of storing the data in cache memory is called caching. 
Don't you think it would be helpful if you have such an arrangement in Hibernate, so that the database hits are reduced by caching the first retrieved data for further access?
Caching in Hibernate
In Hibernate, cache sits between your application and database and stores local copy of data in it. So, when your application wants to access that data again, it directly gets the data from cache. Thus, the traffic between your application and database is reduced.
Consider the below code snippet:
Session session = sessionFactory.openSession();
CustomerEntity customer1 = (CustomerEntity)session.load(CustomerEntity.class, 5001);          
System.out.println("Customer Name 1: " + customer1.getCustomerName());
CustomerEntity customer2 = (CustomerEntity)session.load(CustomerEntity.class, 5001);          
System.out.println("Customer Name 2: " + customer2.getCustomerName());
By seeing the code you may think that the code will hit the database twice as the load() is invoked twice. But actually the database is hit only once. The reason is hibernate uses a default cache here. 
Note: Observe your console to know the number of times the 'select' query is generated by hibernate, by enabling ‘show_sql’ property in hibernate.cfg.xml.
In hibernate the default cache is associated with specific Session instance. That is, in your previous code the session instance itself is the cache. This cache is known as either first-level cache or session cache.
This is enabled by default, mandatory in nature and cannot be turned off.
In session cache, when the application requests the same persistent object more than once in a specific Session, it ensures that the application gets back the same instance that was cached during the initial request.

The next page demonstrates how session cache works in your previous code, so that the database is hit only once instead of twice. To understand the demonstration you need to know about cache hit and cache miss.
  
Cache Hit: When the requested data is found in cache.
  
Cache Miss: When the requested data is not found in cache and has to be fetched from database.



public void getCustomer() {
  SessionFactory sessionFactory = HibernateUtility.createSessionFactory();
  try {
   Session session = sessionFactory.openSession();
   CustomerEntity customer1 = (CustomerEntity) session.load(CustomerEntity.class, 5007);
   System.out.println("Customer Name 1: "+ customer1.getCustomerName());

   session.evict(customer1);
   //session.clear();
   //System.out.println("Customer cleared from cache");

   CustomerEntity customer2 = (CustomerEntity) session.load(CustomerEntity.class, 5007);
   System.out.println("Customer Name 2: "+ customer2.getCustomerName());

  } catch (Exception e) {
   System.err.println(e.getMessage());
  } finally {
   HibernateUtility.closeSessionFactory();
  }
 }
Managing Cache Memory
Consider that you have to load 100,000 customer objects into your session cache. When you load huge number of objects into cache memory, you may get OutOfMemoryException. So, how do you manage the memory efficiently?
You can manage the memory by removing and adding the objects as per the need by using the below methods:
  • void Session.evict(entityObject) : Removes the entity object from session cache.
  • boolean Session.contains(entityObject) : Checks whether the entity object is present in the session cache.
  • void Session.clear() : Clears all the data present in session cache.
Consider the below code snippet:
Session session = sessionFactory.openSession();
CustomerEntity customer1 = (CustomerEntity)session.load(CustomerEntity.class, 5001);          //cache miss
System.out.println("Customer Name 1: " + customer1.getCustomerName());
session.evict(customer1);
/*session.clear();*/
CustomerEntity customer2 = (CustomerEntity)session.load(CustomerEntity.class, 5001);          //cache miss
System.out.println("Customer Name 2: " + customer2.getCustomerName());
As per this code, when the application tries to retrieve the record for the very first time, it will be a cache miss, the database will be hit, the retrieved object will be stored in session cache. 
If you observe the code, we are invoking the evict() method after the first retrieval, which will remove the given object from the cache. The same can be done with clear() as well, but the difference is, evict will remove only the given object, whereas clear() will remove all the objects available in cache.
The next retrieval will also be a cache miss as the object is already removed from cache.
In first-level caching the cached objects are available only to that specific session. You cannot share the objects across sessions.
Why Second-level Caching?
Consider the below code snippet where two session objects session1 and session2 are working on the same customer object to perform two different transactions. Though the customer objects are same, the database will be hit twice as the session cache is associated with specific session.  
Session session1= sessionFactory.openSession();
session1.beginTransaction();
CustomerEntity customer1 = (CustomerEntity)session1.load(CustomerEntity.class, 5001);          //cache miss
System.out.println("Customer Name 1: " + customer1.getCustomerName());
//code for business transaction
session1.getTransaction.commit();
Session session2 = sessionFactory.openSession();
session2.beginTransaction();
CustomerEntity customer2 = (CustomerEntity)session2.load(CustomerEntity.class, 5001);          //cache miss
System.out.println("Customer Name 2: " + customer2.getCustomerName());
//code for business transaction
session2.getTransaction.commit();
In such situations, where you want to share the objects across sessions, may be across entire application, you can use the second-level caching mechanism.
Second-level Caching
The second-level cache is the SessionFactory itself. Hence this cache can be used by all the sessions under the same session factory.
Consider the same scenario of two transactions sharing the same entity object. Assume that your application is configured for second-level caching.
Session session1= sessionFactory.openSession();
session1.beginTransaction();
CustomerEntity customer1 = (CustomerEntity)session1.load(CustomerEntity.class, 5001);          //cache miss
System.out.println("Customer Name 1: " + customer1.getCustomerName());
//code for business transaction
session1.getTransaction.commit();
session1.evict(customer1);
Session session2 = sessionFactory.openSession();
session2.beginTransaction();
CustomerEntity customer2 = (CustomerEntity)session2.load(CustomerEntity.class, 5001);          //cache hit
System.out.println("Customer Name 2: " + customer2.getCustomerName());
//code for business transaction
session2.getTransaction.commit();
Because of second-level caching, this code will hit the database only once even when the entity object customer1 is evicted from session instance
Second level cache won't be available by default.  It is optional and is pluggable.
To plug in the second level cache into your Hibernate application, you have to choose a cache provider.
Cache providers are those who implements the Hibernate cache specification provided in the interface org.hibernate.cache.CacheProvider.
List of cache providers:
  • EHCache or Easy Hibernate Cache
  • OSCache
  • SwarmCache
  • JBossCache
We will be using the jar file provided by EHCache provider.
Second-level caching should be configured in hibernate.cfg.xml  as shown below, if your application needs this feature 
Your entity class should be annotated as shown below if you want your entity instances to be cached with SessionFactory.
@Entity
@Table(name="Customer")
@Cacheable
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
public class CustomerEntity {
 @Id
 private Integer customerId; 
 //properties, getters and setters 
}
Concurrency strategy is an intermediary mechanism which determines how the data should be stored in cache, and should be retrieved from cache.  
Different cache concurrency strategies are:
  • TRANSACTIONAL
  • READ_WRITE
  • NONSTRICT_READ_WRITE
  • READ_ONLY
We will be using READ_ONLY strategy in our course. This strategy is suitable when your application needs to read, but not modify, the entity objects.
Very important thing that you should know is, always the first level Cache will be accessed before tracing an object in the second level caching.
Managing Cache Memory
Methods involved in second level caching to manage the memory as per the need are:
  • void SessionFactory.getCache.evictEntity(EntityClass.class, primarykey) : Removes the entity object from session factory cache.
  • boolean SessionFactory.getCache.contains(EntityClass.class, primarykey) : Checks whether the entity object is present in the session factory cache.
  • void SessionFactory.getCache.evictAllRegions() : Clears all the data present in session factory cache.
Consider the below code snippet:
Session session = sessionFactory.openSession();
CustomerEntity customer1 = (CustomerEntity)session.load(CustomerEntity.class, 5001);          //cache miss
System.out.println("Customer Name 1: " + customer1.getCustomerName());
sessionFactory.getCache().evictEntity(CustomerEntity.class, 5001);
//sessionFactory.getCache().evictAllRegions();
CustomerEntity customer2 = (CustomerEntity)session.load(CustomerEntity.class, 5001);          //cache hit
System.out.println("Customer Name 2: " + customer2.getCustomerName());
When the above code tries to retrieve the record for the very first time, it will be a first-level and second-level cache miss, the database will be hit, the retrieved object will be stored in both the cache levels.
If you observe the code, we are invoking the evictEntity() method after the first retrival, which will remove the given object from the second level cache. The same can be done with evictAllRegions() as well, but the difference is, evictEntity() will remove only the given object, whereas evictAllRegions() will remove all the objects available in second level cache.
The next retrieval will be a cache hit as the object can be retrieved from first-level cache.

Featured Post

HTML cheetsheet

List: Link tgs Dropdown

Popular Post

(C) Copyright 2018, All rights resrved InShortView. Template by colorlib