Full Stack • Java • System Design • Cloud • AI Engineering

Hibernate2026-06-17

Hibernate Fundamentals

Master Hibernate ORM with visual diagrams covering architecture, SessionFactory, Session lifecycle, JPA vs Hibernate, and best practices

Hibernate is a powerful Object-Relational Mapping (ORM) framework that bridges the gap between object-oriented programming and relational databases. Understanding its architecture, components, and best practices is crucial for Java developers.

ORM vs Direct JDBC

graph TB
    subgraph "Direct JDBC"
        A1[Java Application] --> B1[JDBC Driver]
        B1 --> C1[Write SQL]
        C1 --> D1[ResultSet Processing]
        D1 --> E1[Manual Mapping]
        E1 --> F1[Database]
    end
    
    subgraph "ORM Hibernate"
        A2[Java Application] --> B2[Domain Objects]
        B2 --> C2[Hibernate]
        C2 --> D2[Auto SQL Generation]
        D2 --> E2[Auto Mapping]
        E2 --> F2[Database]
    end
    
    style C2 fill:#4CAF50
    style D2 fill:#4CAF50
    style E2 fill:#4CAF50

Key Points:

  • JDBC: SQL-centric, manual mapping, verbose code, database-specific
  • ORM: Object-centric, automatic mapping, less code, database-agnostic
  • Abstraction: ORM handles SQL generation, connection management, caching
  • Complexity: ORM adds complexity but provides powerful features
  • Use Cases: ORM best for CRUD operations, JDBC for complex queries/batch

Persistence Approaches Comparison

// 1. Direct JDBC - SQL Centric
public List<Customer> findCustomersJDBC() throws SQLException {
    List<Customer> customers = new ArrayList<>();
    Connection conn = dataSource.getConnection();
    
    // Manual SQL writing
    String sql = "SELECT id, name, email, created_at FROM customers WHERE active = ?";
    PreparedStatement stmt = conn.prepareStatement(sql);
    stmt.setBoolean(1, true);
    
    // Manual result set processing
    ResultSet rs = stmt.executeQuery();
    while (rs.next()) {
        Customer customer = new Customer();
        customer.setId(rs.getLong("id"));
        customer.setName(rs.getString("name"));
        customer.setEmail(rs.getString("email"));
        customer.setCreatedAt(rs.getTimestamp("created_at"));
        customers.add(customer);
    }
    
    // Manual resource cleanup
    rs.close();
    stmt.close();
    conn.close();
    
    return customers;
}

// 2. Spring JDBC Template - Less Verbose
public List<Customer> findCustomersSpringJDBC() {
    String sql = "SELECT id, name, email, created_at FROM customers WHERE active = ?";
    
    // Simplified with RowMapper
    return jdbcTemplate.query(sql, new Object[]{true}, 
        (rs, rowNum) -> {
            Customer customer = new Customer();
            customer.setId(rs.getLong("id"));
            customer.setName(rs.getString("name"));
            customer.setEmail(rs.getString("email"));
            customer.setCreatedAt(rs.getTimestamp("created_at"));
            return customer;
        });
}

// 3. Hibernate/JPA - Object Centric
public List<Customer> findCustomersHibernate() {
    // No SQL needed - uses JPQL (object-oriented query language)
    return entityManager.createQuery(
        "FROM Customer c WHERE c.active = :active", Customer.class)
        .setParameter("active", true)
        .getResultList();
    // Automatic mapping, caching, lazy loading handled by Hibernate
}

// 4. Spring Data JPA - Most Concise
public interface CustomerRepository extends JpaRepository<Customer, Long> {
    // Method name automatically generates query
    List<Customer> findByActiveTrue();
    
    // Or use @Query for custom queries
    @Query("FROM Customer c WHERE c.active = :active")
    List<Customer> findActiveCustomers(@Param("active") boolean active);
}

// Usage
@Service
public class CustomerService {
    @Autowired
    private CustomerRepository customerRepository;
    
    public List<Customer> getActiveCustomers() {
        return customerRepository.findByActiveTrue();
        // One line - Hibernate handles everything
    }
}

Hibernate Architecture

graph TB
    A[Application Layer] --> B[Hibernate API]
    B --> C[Configuration]
    B --> D[SessionFactory]
    D --> E[Session]
    E --> F[Transaction]
    E --> G[Query/Criteria]
    
    E --> H[First Level Cache]
    D --> I[Second Level Cache]
    
    E --> J[JDBC]
    J --> K[Database]
    
    C -.->|Creates| D
    D -.->|Creates| E
    E -.->|Uses| F
    
    style D fill:#FF9800
    style E fill:#4CAF50
    style H fill:#2196F3
    style I fill:#2196F3

Key Points:

  • Configuration: Loads hibernate.cfg.xml or properties, creates SessionFactory
  • SessionFactory: Thread-safe, expensive to create, one per database
  • Session: Not thread-safe, lightweight, one per request/transaction
  • Transaction: Manages ACID properties, commit/rollback operations
  • Cache: First-level (Session), Second-level (SessionFactory)

Hibernate Architecture Code

// 1. Configuration - Bootstrap Hibernate
Configuration configuration = new Configuration();
configuration.configure("hibernate.cfg.xml"); // Load config
configuration.addAnnotatedClass(Customer.class); // Add entities
configuration.addAnnotatedClass(Order.class);

// 2. SessionFactory - Created once, thread-safe
SessionFactory sessionFactory = configuration.buildSessionFactory();
// Expensive operation - cache and reuse
// Contains compiled mappings, second-level cache

// 3. Session - Per request/transaction, not thread-safe
Session session = sessionFactory.openSession();
// Lightweight, represents unit of work
// Contains first-level cache

// 4. Transaction - ACID operations
Transaction transaction = session.beginTransaction();

try {
    // Perform database operations
    Customer customer = new Customer("John Doe", "[email protected]");
    session.save(customer);
    
    // Query data
    List<Customer> customers = session.createQuery(
        "FROM Customer WHERE active = true", Customer.class)
        .list();
    
    // Commit transaction
    transaction.commit();
    
} catch (Exception e) {
    // Rollback on error
    if (transaction != null) {
        transaction.rollback();
    }
    throw e;
    
} finally {
    // Always close session
    session.close();
}

// 5. Shutdown - Application end
sessionFactory.close();

// Modern approach with try-with-resources
try (Session session = sessionFactory.openSession()) {
    Transaction tx = session.beginTransaction();
    
    // Database operations
    Customer customer = session.get(Customer.class, 1L);
    customer.setEmail("[email protected]");
    
    tx.commit();
} // Session auto-closed

SessionFactory vs Session

sequenceDiagram
    participant App as Application
    participant SF as SessionFactory
    participant S1 as Session 1
    participant S2 as Session 2
    participant DB as Database
    
    App->>SF: buildSessionFactory() [Once]
    Note over SF: Thread-safe<br/>Expensive<br/>Singleton
    
    App->>SF: openSession()
    SF->>S1: Create Session 1
    Note over S1: Not thread-safe<br/>Lightweight<br/>Per request
    
    App->>S1: save(customer)
    S1->>DB: INSERT
    App->>S1: close()
    
    App->>SF: openSession()
    SF->>S2: Create Session 2
    App->>S2: get(Customer, id)
    S2->>DB: SELECT
    App->>S2: close()
    
    App->>SF: close() [Shutdown]

Key Points:

  • SessionFactory: Created once, thread-safe, contains compiled mappings
  • Session: Created per request, not thread-safe, represents unit of work
  • Lifecycle: SessionFactory lives for application, Session for transaction
  • Cost: SessionFactory expensive to create, Session lightweight
  • Caching: SessionFactory has second-level cache, Session has first-level

SessionFactory and Session Code

// SessionFactory - Thread-safe Singleton
@Configuration
public class HibernateConfig {
    
    private static SessionFactory sessionFactory;
    
    @Bean
    public SessionFactory sessionFactory() {
        if (sessionFactory == null) {
            synchronized (HibernateConfig.class) {
                if (sessionFactory == null) {
                    // Create once, reuse everywhere
                    sessionFactory = new Configuration()
                        .configure()
                        .addAnnotatedClass(Customer.class)
                        .buildSessionFactory();
                }
            }
        }
        return sessionFactory;
    }
    
    @PreDestroy
    public void cleanup() {
        if (sessionFactory != null) {
            sessionFactory.close();
        }
    }
}

// Session - Not thread-safe, per request
@Service
public class CustomerService {
    
    @Autowired
    private SessionFactory sessionFactory;
    
    // Wrong - Don't share Session between threads
    // private Session session; // BAD!
    
    public Customer findCustomer(Long id) {
        // Create new Session per operation
        Session session = sessionFactory.openSession();
        try {
            return session.get(Customer.class, id);
        } finally {
            session.close();
        }
    }
    
    // Better - Use ThreadLocal for Session per thread
    private static ThreadLocal<Session> threadSession = 
        new ThreadLocal<>();
    
    public Session getCurrentSession() {
        Session session = threadSession.get();
        if (session == null || !session.isOpen()) {
            session = sessionFactory.openSession();
            threadSession.set(session);
        }
        return session;
    }
    
    public void closeCurrentSession() {
        Session session = threadSession.get();
        if (session != null) {
            session.close();
            threadSession.remove();
        }
    }
}

// Best - Use Spring's @Transactional
@Service
public class CustomerServiceTransactional {
    
    @Autowired
    private SessionFactory sessionFactory;
    
    @Transactional
    public Customer saveCustomer(Customer customer) {
        // Spring manages Session lifecycle
        Session session = sessionFactory.getCurrentSession();
        session.save(customer);
        return customer;
    }
    
    @Transactional(readOnly = true)
    public Customer findCustomer(Long id) {
        Session session = sessionFactory.getCurrentSession();
        return session.get(Customer.class, id);
    }
}

JPA vs Hibernate

graph TB
    A[JPA - Java Persistence API] --> B[Standard Specification]
    A --> C[javax.persistence.*]
    A --> D[EntityManagerFactory]
    A --> E[EntityManager]
    
    F[Hibernate] --> G[Implementation]
    F --> H[org.hibernate.*]
    F --> I[SessionFactory]
    F --> J[Session]
    
    D -.->|Wraps| I
    E -.->|Wraps| J
    
    style A fill:#4CAF50
    style F fill:#FF9800

Key Points:

  • JPA: Standard specification, portable, evolving
  • Hibernate: Implementation of JPA, proprietary features
  • Portability: JPA annotations work with other providers (EclipseLink, TopLink)
  • Features: Hibernate has additional features beyond JPA
  • Best Practice: Use JPA APIs, unwrap to Hibernate when needed

JPA vs Hibernate Code Examples

// JPA Approach - Standard and Portable
@Entity
@Table(name = "customers")
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String name;
    
    @Column(unique = true)
    private String email;
    
    // JPA lifecycle callbacks
    @PrePersist
    protected void onCreate() {
        createdAt = new Date();
    }
    
    @PreUpdate
    protected void onUpdate() {
        updatedAt = new Date();
    }
}

// Using JPA EntityManager
@Service
public class CustomerServiceJPA {
    
    @PersistenceContext
    private EntityManager entityManager;
    
    @Transactional
    public Customer save(Customer customer) {
        entityManager.persist(customer);
        return customer;
    }
    
    public Customer findById(Long id) {
        return entityManager.find(Customer.class, id);
    }
    
    public List<Customer> findAll() {
        return entityManager.createQuery(
            "SELECT c FROM Customer c", Customer.class)
            .getResultList();
    }
}

// Hibernate Approach - Proprietary Features
@Entity
@Table(name = "customers")
@org.hibernate.annotations.Cache(
    usage = CacheConcurrencyStrategy.READ_WRITE)
@org.hibernate.annotations.DynamicUpdate
public class CustomerHibernate {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    // Hibernate-specific formula
    @org.hibernate.annotations.Formula(
        "CONCAT(first_name, ' ', last_name)")
    private String fullName;
    
    // Hibernate-specific type
    @org.hibernate.annotations.Type(type = "yes_no")
    private Boolean active;
}

// Using Hibernate Session
@Service
public class CustomerServiceHibernate {
    
    @Autowired
    private SessionFactory sessionFactory;
    
    @Transactional
    public Customer save(Customer customer) {
        Session session = sessionFactory.getCurrentSession();
        session.save(customer);
        return customer;
    }
    
    public Customer findById(Long id) {
        Session session = sessionFactory.getCurrentSession();
        return session.get(Customer.class, id);
    }
}

// Unwrapping - Use JPA, access Hibernate when needed
@Service
public class CustomerServiceUnwrap {
    
    @PersistenceContext
    private EntityManager entityManager;
    
    @Transactional
    public void useHibernateFeature() {
        // Use JPA by default
        Customer customer = entityManager.find(Customer.class, 1L);
        
        // Unwrap to Hibernate Session when needed
        Session session = entityManager.unwrap(Session.class);
        
        // Use Hibernate-specific features
        session.enableFilter("activeCustomersFilter");
        
        // Or get SessionFactory
        SessionFactory sf = session.getSessionFactory();
        Statistics stats = sf.getStatistics();
        System.out.println("Cache hit ratio: " + 
            stats.getSecondLevelCacheHitCount());
    }
}

Best Practices

  1. Use JPA APIs: Prefer EntityManager over Session for portability
  2. SessionFactory Singleton: Create once, reuse throughout application
  3. Session Per Request: Never share Session between threads
  4. Transaction Management: Always use transactions for write operations
  5. Resource Cleanup: Close Session in finally block or use try-with-resources
  6. Lazy Loading: Keep Session open for lazy-loaded associations
  7. Batch Operations: Use batch size configuration for bulk operations
  8. Second-Level Cache: Enable for read-heavy entities