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