Hibernate Development Guide

Hibernate is one of the most popular Object-Relational Mapping (ORM) frameworks for Java applications. This guide demonstrates how to leverage Hibernate's powerful features with Tacnode's PostgreSQL compatibility to build robust, data-driven applications.

Prerequisites

Before starting, ensure you have:

  • Java 8+ (Java 11+ recommended)
  • Maven or Gradle for dependency management
  • A Tacnode database with appropriate access credentials

Create Database and Table

Set up your database structure in Tacnode:

-- Create the database
CREATE DATABASE example;
 
-- Connect to the database and create a table
\c example;
 
CREATE TABLE customer (
    id BIGINT NOT NULL,
    name TEXT NOT NULL,
    email TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (id)
);

Project Setup

Maven Dependencies

Add the required dependencies to your pom.xml:

<properties>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
    <hibernate.version>5.6.15.Final</hibernate.version>
    <postgresql.version>42.5.0</postgresql.version>
</properties>
 
<dependencies>
    <!-- Hibernate Core -->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>${hibernate.version}</version>
    </dependency>
    
    <!-- PostgreSQL JDBC Driver -->
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <version>${postgresql.version}</version>
    </dependency>
    
    <!-- JPA API -->
    <dependency>
        <groupId>javax.persistence</groupId>
        <artifactId>javax.persistence-api</artifactId>
        <version>2.2</version>
    </dependency>
    
    <!-- Lombok for cleaner code (optional) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.24</version>
        <scope>provided</scope>
    </dependency>
    
    <!-- SLF4J for logging -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.7.36</version>
    </dependency>
</dependencies>

Entity Class Definition

Create a Customer entity that maps to your database table:

package io.tacnode.model;
 
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
 
import javax.persistence.*;
import java.time.LocalDateTime;
 
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "customer")
public class Customer {
    
    @Id
    @Column(name = "id")
    private Long id;
    
    @Column(name = "name", nullable = false, length = 255)
    private String name;
    
    @Column(name = "email", nullable = false, length = 255)
    private String email;
    
    @Column(name = "created_at", updatable = false)
    private LocalDateTime createdAt;
    
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
    
    // Constructor for required fields
    public Customer(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.createdAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
    }
    
    @PreUpdate
    public void preUpdate() {
        this.updatedAt = LocalDateTime.now();
    }
}

Hibernate Configuration

Create hibernate.cfg.xml in your src/main/resources directory:

<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
 
<hibernate-configuration>
    <session-factory>
        <!-- Database connection settings -->
        <property name="hibernate.dialect">org.hibernate.dialect.PostgreSQL10Dialect</property>
        <property name="hibernate.connection.driver_class">org.postgresql.Driver</property>
        <property name="hibernate.connection.url">jdbc:postgresql://your-cluster.tacnode.io:5432/example</property>
        <property name="hibernate.connection.username">your_username</property>
        <property name="hibernate.connection.password">your_password</property>
        
        <!-- Connection pool settings -->
        <property name="hibernate.connection.pool_size">10</property>
        <property name="hibernate.connection.autocommit">false</property>
        
        <!-- Performance and debugging settings -->
        <property name="hibernate.show_sql">true</property>
        <property name="hibernate.format_sql">true</property>
        <property name="hibernate.use_sql_comments">true</property>
        
        <!-- Schema generation (for development only) -->
        <property name="hibernate.hbm2ddl.auto">validate</property>
        
        <!-- Entity mappings -->
        <mapping class="io.tacnode.model.Customer" />
    </session-factory>
</hibernate-configuration>

CRUD Operations Implementation

Hibernate Utility Class

Create a utility class to manage SessionFactory:

package io.tacnode.util;
 
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
 
public class HibernateUtil {
    private static SessionFactory sessionFactory;
    
    static {
        try {
            sessionFactory = new Configuration()
                    .configure("hibernate.cfg.xml")
                    .buildSessionFactory();
        } catch (Exception e) {
            System.err.println("SessionFactory creation failed: " + e);
            throw new ExceptionInInitializerError(e);
        }
    }
    
    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }
    
    public static void shutdown() {
        if (sessionFactory != null) {
            sessionFactory.close();
        }
    }
}

Data Access Object (DAO)

Create a DAO class for Customer operations:

package io.tacnode.dao;
 
import io.tacnode.model.Customer;
import io.tacnode.util.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.query.Query;
 
import java.util.List;
import java.util.Optional;
 
public class CustomerDAO {
    
    public void save(Customer customer) {
        Transaction transaction = null;
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            transaction = session.beginTransaction();
            session.save(customer);
            transaction.commit();
        } catch (Exception e) {
            if (transaction != null) {
                transaction.rollback();
            }
            throw new RuntimeException("Error saving customer", e);
        }
    }
    
    public Optional<Customer> findById(Long id) {
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            Customer customer = session.get(Customer.class, id);
            return Optional.ofNullable(customer);
        } catch (Exception e) {
            throw new RuntimeException("Error finding customer by id: " + id, e);
        }
    }
    
    public List<Customer> findAll() {
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            Query<Customer> query = session.createQuery("FROM Customer", Customer.class);
            return query.getResultList();
        } catch (Exception e) {
            throw new RuntimeException("Error finding all customers", e);
        }
    }
    
    public List<Customer> findByNamePattern(String namePattern) {
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            Query<Customer> query = session.createQuery(
                "FROM Customer c WHERE c.name LIKE :pattern", Customer.class);
            query.setParameter("pattern", "%" + namePattern + "%");
            return query.getResultList();
        } catch (Exception e) {
            throw new RuntimeException("Error finding customers by name pattern", e);
        }
    }
    
    public void update(Customer customer) {
        Transaction transaction = null;
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            transaction = session.beginTransaction();
            session.update(customer);
            transaction.commit();
        } catch (Exception e) {
            if (transaction != null) {
                transaction.rollback();
            }
            throw new RuntimeException("Error updating customer", e);
        }
    }
    
    public void delete(Long id) {
        Transaction transaction = null;
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            transaction = session.beginTransaction();
            Customer customer = session.get(Customer.class, id);
            if (customer != null) {
                session.delete(customer);
            }
            transaction.commit();
        } catch (Exception e) {
            if (transaction != null) {
                transaction.rollback();
            }
            throw new RuntimeException("Error deleting customer with id: " + id, e);
        }
    }
}

Main Application

Create a main class to demonstrate CRUD operations:

package io.tacnode;
 
import io.tacnode.dao.CustomerDAO;
import io.tacnode.model.Customer;
import io.tacnode.util.HibernateUtil;
 
import java.util.List;
import java.util.Optional;
 
public class CustomerApplication {
    private static final CustomerDAO customerDAO = new CustomerDAO();
    
    public static void main(String[] args) {
        try {
            // Create sample customers
            System.out.println("=== Creating Customers ===");
            createSampleCustomers();
            
            // Read all customers
            System.out.println("\n=== Reading All Customers ===");
            readAllCustomers();
            
            // Update a customer
            System.out.println("\n=== Updating Customer ===");
            updateCustomer();
            
            // Read updated customer
            System.out.println("\n=== Reading Updated Customer ===");
            readCustomerById(2L);
            
            // Delete a customer
            System.out.println("\n=== Deleting Customer ===");
            deleteCustomer(1L);
            
            // Read remaining customers
            System.out.println("\n=== Reading Remaining Customers ===");
            readAllCustomers();
            
            // Search customers by name
            System.out.println("\n=== Searching Customers by Name ===");
            searchCustomersByName("Emma");
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            HibernateUtil.shutdown();
        }
    }
    
    private static void createSampleCustomers() {
        Customer customer1 = new Customer(1L, "Jacob Emily", "jacob.emily@tacnode.io");
        Customer customer2 = new Customer(2L, "Michael Emma", "michael.emma@tacnode.io");
        
        customerDAO.save(customer1);
        customerDAO.save(customer2);
        
        System.out.println("Created customer: " + customer1.getName());
        System.out.println("Created customer: " + customer2.getName());
    }
    
    private static void readAllCustomers() {
        List<Customer> customers = customerDAO.findAll();
        customers.forEach(customer -> 
            System.out.println("Customer: " + customer.getName() + 
                             " | Email: " + customer.getEmail() + 
                             " | Created: " + customer.getCreatedAt()));
    }
    
    private static void readCustomerById(Long id) {
        Optional<Customer> customer = customerDAO.findById(id);
        if (customer.isPresent()) {
            Customer c = customer.get();
            System.out.println("Found customer: " + c.getName() + 
                             " | Email: " + c.getEmail() + 
                             " | Updated: " + c.getUpdatedAt());
        } else {
            System.out.println("Customer with ID " + id + " not found");
        }
    }
    
    private static void updateCustomer() {
        Optional<Customer> customerOpt = customerDAO.findById(2L);
        if (customerOpt.isPresent()) {
            Customer customer = customerOpt.get();
            customer.setEmail("michael.emma@gmail.com");
            customer.setName("Michael Emma Johnson");
            customerDAO.update(customer);
            System.out.println("Updated customer: " + customer.getName());
        }
    }
    
    private static void deleteCustomer(Long id) {
        customerDAO.delete(id);
        System.out.println("Deleted customer with ID: " + id);
    }
    
    private static void searchCustomersByName(String namePattern) {
        List<Customer> customers = customerDAO.findByNamePattern(namePattern);
        System.out.println("Found " + customers.size() + " customers matching '" + namePattern + "':");
        customers.forEach(customer -> 
            System.out.println("  - " + customer.getName() + " (" + customer.getEmail() + ")"));
    }
}

Sample Output

When you run the application, you should see output similar to:

=== Creating Customers ===
Created customer: Jacob Emily
Created customer: Michael Emma

=== Reading All Customers ===
Customer: Jacob Emily | Email: jacob.emily@tacnode.io | Created: 2024-01-15T10:30:45
Customer: Michael Emma | Email: michael.emma@tacnode.io | Created: 2024-01-15T10:30:45

=== Updating Customer ===
Updated customer: Michael Emma Johnson

=== Reading Updated Customer ===
Found customer: Michael Emma Johnson | Email: michael.emma@gmail.com | Updated: 2024-01-15T10:31:15

=== Deleting Customer ===
Deleted customer with ID: 1

=== Reading Remaining Customers ===
Customer: Michael Emma Johnson | Email: michael.emma@gmail.com | Created: 2024-01-15T10:30:45

=== Searching Customers by Name ===
Found 1 customers matching 'Emma':
  - Michael Emma Johnson (michael.emma@gmail.com)

Best Practices

Performance Optimization

  1. Use Connection Pooling:

    <property name="hibernate.connection.pool_size">20</property>
    <property name="hibernate.c3p0.min_size">5</property>
    <property name="hibernate.c3p0.max_size">20</property>
    <property name="hibernate.c3p0.timeout">300</property>
  2. Enable Second-Level Cache:

    <property name="hibernate.cache.use_second_level_cache">true</property>
    <property name="hibernate.cache.region.factory_class">
        org.hibernate.cache.ehcache.EhCacheRegionFactory
    </property>
  3. Batch Processing:

    <property name="hibernate.jdbc.batch_size">20</property>
    <property name="hibernate.order_inserts">true</property>
    <property name="hibernate.order_updates">true</property>

Error Handling

Always implement proper transaction management:

public void saveWithProperErrorHandling(Customer customer) {
    Transaction transaction = null;
    Session session = null;
    try {
        session = HibernateUtil.getSessionFactory().openSession();
        transaction = session.beginTransaction();
        
        session.save(customer);
        
        transaction.commit();
    } catch (Exception e) {
        if (transaction != null) {
            transaction.rollback();
        }
        throw new RuntimeException("Error saving customer", e);
    } finally {
        if (session != null) {
            session.close();
        }
    }
}

Security Considerations

  1. Use Parameterized Queries:

    Query query = session.createQuery("FROM Customer WHERE email = :email");
    query.setParameter("email", emailAddress);
  2. Validate Input Data:

    @Column(name = "email", nullable = false, length = 255)
    @Email
    @NotBlank
    private String email;

This comprehensive guide demonstrates how to build robust Java applications using Hibernate with Tacnode. The PostgreSQL compatibility ensures that all Hibernate features work seamlessly, providing you with a powerful and scalable database solution.