CRUD Operations with Hibernate (Create, Read, Update, Delete)

This is a hands-on lesson demonstrating how to perform basic CRUD operations using Hibernate. You'll learn how to save new objects to the database (Create), retrieve objects (Read), modify existing objects (Update), and delete objects (Delete) using Hibernate's API.


Hibernate SessionFactory and Session

Hibernate SessionFactory and Session Explained

Hibernate is an Object-Relational Mapping (ORM) framework for Java. It simplifies the interaction between Java applications and relational databases by mapping Java objects to database tables. Two key interfaces in Hibernate are SessionFactory and Session. Understanding their roles and lifecycles is crucial for using Hibernate effectively.

SessionFactory

The SessionFactory is a heavyweight, immutable object that serves as a factory for creating Session objects. It is typically created once per application and shared across multiple threads. Think of it as the "blueprint" or "recipe" for creating database connections and configuring Hibernate.

Key responsibilities of the SessionFactory:

  • Configuration Loading: It reads the Hibernate configuration file (typically hibernate.cfg.xml or configured programmatically) and loads all the necessary mappings and settings.
  • Connection Pooling: It manages a pool of database connections. This is crucial for performance, as establishing a new database connection is an expensive operation.
  • Caching: It manages the second-level cache (optional), which can significantly improve performance by caching frequently accessed data.
  • Metadata Management: It holds metadata about the mapping between Java classes and database tables.

Session

The Session is a lightweight, single-threaded object that represents a unit of work with the database. It is short-lived and should be created and destroyed within a specific transaction or operation. Think of it as the "connection" or "conversation" with the database.

Key responsibilities of the Session:

  • Managing Persistent Objects: It manages the lifecycle of persistent objects (objects mapped to database tables). This includes creating, reading, updating, and deleting objects.
  • Transaction Management: It provides methods for starting, committing, and rolling back transactions.
  • Querying the Database: It provides methods for querying the database using HQL (Hibernate Query Language) or Criteria API.
  • Caching: It manages the first-level cache (mandatory), which caches objects within the current session.

Understanding the Roles of SessionFactory and Session in Hibernate

The SessionFactory is the factory for creating Session objects. You typically create a single SessionFactory instance when your application starts up and reuse it throughout the application's lifetime. Each request to the database (each "unit of work") requires a new Session. The Session is then used to interact with the database to persist, retrieve, update or delete entities.

Imagine the SessionFactory as a power plant providing electricity. The electricity is the database connection. The Session is the appliance using that electricity to perform a specific task (like saving data or retrieving data). You wouldn't have multiple power plants for each appliance; you have one power plant providing the electricity for all appliances. Similarly, you have one SessionFactory providing Sessions for all database operations.

Creating a SessionFactory and Obtaining Session Instances

Here's an example of how to create a SessionFactory and obtain Session instances using Hibernate's programmatic configuration:

 import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.Session;

public class HibernateUtil {

    private static final SessionFactory sessionFactory = buildSessionFactory();

    private static SessionFactory buildSessionFactory() {
        try {
            // Create the SessionFactory from hibernate.cfg.xml
            Configuration configuration = new Configuration().configure("hibernate.cfg.xml"); // Or specify a different configuration file

            // You can also configure Hibernate programmatically
            // configuration.setProperty("hibernate.connection.driver_class", "com.mysql.cj.jdbc.Driver");
            // configuration.setProperty("hibernate.connection.url", "jdbc:mysql://localhost:3306/mydatabase");
            // configuration.setProperty("hibernate.connection.username", "myuser");
            // configuration.setProperty("hibernate.connection.password", "mypassword");
            // configuration.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");

            return configuration.buildSessionFactory();

        } catch (Throwable ex) {
            // Make sure you log the exception, as it might be swallowed
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }

    public static Session getSession() {
        return sessionFactory.openSession();
    }

    public static void shutdown() {
        // Close caches and connection pools
        getSessionFactory().close();
    }

    public static void main(String[] args) {
        // Example usage
        Session session = null;
        try {
            session = HibernateUtil.getSession();
            session.beginTransaction();

            // Perform database operations here (e.g., save an object)
            // Example:
            // User user = new User("John Doe", "john.doe@example.com");
            // session.save(user);

            session.getTransaction().commit();
        } catch (Exception e) {
            if (session != null && session.getTransaction() != null) {
                session.getTransaction().rollback();
            }
            e.printStackTrace();
        } finally {
            if (session != null) {
                session.close();
            }
        }
        HibernateUtil.shutdown();
    }
} 

Explanation:

  1. HibernateUtil Class: This class encapsulates the SessionFactory creation and provides utility methods to access the SessionFactory and Session. This is a common practice for managing Hibernate resources.
  2. buildSessionFactory() Method: This method creates the SessionFactory.
    • It creates a Configuration object.
    • It calls configure("hibernate.cfg.xml") to load the configuration from the hibernate.cfg.xml file. Alternatively, you can configure Hibernate properties programmatically using configuration.setProperty().
    • It calls buildSessionFactory() on the Configuration object to create the SessionFactory.
  3. getSessionFactory() Method: This method returns the singleton SessionFactory instance.
  4. getSession() Method: This method opens a new Session from the SessionFactory. Each call will create a new session.
  5. shutdown() Method: This method closes the SessionFactory and releases all resources (connection pool, caches). It's important to call this when your application shuts down.
  6. main() Method (Example Usage): Demonstrates how to obtain a Session, begin a transaction, perform database operations, commit the transaction, and close the Session. Error handling with rollback is included.

Important Notes:

  • You'll need to replace the placeholder comments in the main method and the programatic configuration section with your actual Hibernate entity class and database details.
  • Ensure you have a valid `hibernate.cfg.xml` file in your classpath, or configure programmatically.
  • Remember to add Hibernate dependencies to your project.

Managing Session Lifecycle

The Session is a short-lived object, and its lifecycle must be carefully managed. Here are the key aspects of managing the Session lifecycle:

  1. Opening a Session: Use SessionFactory.openSession() to open a new Session. This creates a new database connection (or obtains one from the connection pool).
  2. Transactions: All database operations should be performed within a transaction. Use Session.beginTransaction() to start a transaction.
  3. Database Operations: Perform your CRUD (Create, Read, Update, Delete) operations using the Session object (e.g., session.save(), session.get(), session.update(), session.delete()).
  4. Committing or Rolling Back:
    • If the database operations are successful, call Session.getTransaction().commit() to commit the transaction. This persists the changes to the database.
    • If an error occurs, call Session.getTransaction().rollback() to roll back the transaction. This discards any changes made during the transaction and returns the database to its previous state.
  5. Closing the Session: Always close the Session after you're finished using it, regardless of whether the transaction was committed or rolled back. Use Session.close() to close the Session and release the database connection back to the connection pool. This is *crucial* to prevent resource leaks.

Best Practices:

  • Use a try-catch-finally block: Wrap your Hibernate code within a try-catch-finally block to ensure that the Session is always closed, even if an exception occurs. The finally block should always contain the session.close() call.
  • Short-lived Sessions: Keep the Session open for the shortest possible time. Avoid keeping a Session open for the entire duration of a user session or request.
  • Thread Safety:Session objects are *not* thread-safe. Each thread should have its own Session instance.

By following these guidelines, you can effectively manage the Session lifecycle and avoid common Hibernate pitfalls.