Hibernate – podstawy ORM

TL;DR: Hibernate to ORM (Object-Relational Mapping) framework który mapuje obiekty Java na tabele bazy danych. Używasz @Entity do oznaczenia klas, @Id dla klucza głównego, @Column dla kolumn. SessionFactory tworzy Sessions do operacji CRUD. Hibernate automatycznie generuje SQL i zarządza lazy loading.

Pisanie SQL queries dla każdej operacji CRUD to żmudna praca. Hibernate rozwiązuje ten problem przez automatic mapping między obiektami Java a tabelami bazy danych. To najpopularniejszy ORM w ekosystemie Java, używany przez miliony aplikacji na całym świecie.

Dlaczego Hibernate jest ważne

Hibernate to industry standard dla ORM w Javie od 2001 roku. W 2016 roku Hibernate 5.x wprowadza Java 8 support i lepszą performance. Framework radykalnie zmniejsza ilość boilerplate code i pozwala skupić się na logice biznesowej zamiast na SQL queries.

ORM znacznie przyspiesza development – developers nie muszą pisać repetitive SQL code. Hibernate provides advanced features jak caching, lazy loading i connection pooling, które improve performance i scalability enterprise applications.

Co się nauczysz:

  • Czym jest ORM i jakie problemy rozwiązuje
  • Podstawowe adnotacje Hibernate
  • Konfiguracja SessionFactory i Session management
  • CRUD operations z Hibernate API
  • Relacje między encjami (One-to-Many, Many-to-One)

Wymagania wstępne:

  • Dobra znajomość Java i programowania obiektowego
  • Podstawy SQL i baz danych relacyjnych
  • Znajomość JDBC (opcjonalna ale pomocna)
  • Experience z Maven lub Gradle

Problem który rozwiązuje ORM

Analogia: ORM to jak translator między dwoma kulturami. Obiektowy świat Java (objects, inheritance, polymorphism) i relacyjny świat SQL (tables, foreign keys, joins) mówią różnymi językami. Hibernate tłumaczy między nimi automatically.

Bez ORM musisz ręcznie mapować dane:

// Traditional JDBC approach - lots of boilerplate
public User findUserById(Long id) {
    String sql = "SELECT id, name, email, age FROM users WHERE id = ?";
    
    try (Connection conn = DriverManager.getConnection(url);
         PreparedStatement stmt = conn.prepareStatement(sql)) {
        
        stmt.setLong(1, id);
        ResultSet rs = stmt.executeQuery();
        
        if (rs.next()) {
            User user = new User();
            user.setId(rs.getLong("id"));
            user.setName(rs.getString("name"));
            user.setEmail(rs.getString("email"));
            user.setAge(rs.getInt("age"));
            return user;
        }
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }
    return null;
}

// With Hibernate - much simpler!
public User findUserById(Long id) {
    Session session = sessionFactory.getCurrentSession();
    return session.get(User.class, id);
}
Problem z JDBC: Repetitive code, manual mapping, error-prone, trudny maintenance. ORM eliminuje te problemy przez automatic object-relational mapping.

Setup Hibernate

Maven dependencies

<dependencies>
    <!-- Hibernate Core -->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>5.2.2.Final</version>
    </dependency>
    
    <!-- MySQL Driver -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.39</version>
    </dependency>
    
    <!-- Connection Pool -->
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
        <version>2.4.7</version>
    </dependency>
</dependencies>

Hibernate configuration

<!-- hibernate.cfg.xml -->
<?xml version='1.0' encoding='utf-8'?>
<hibernate-configuration>
    <session-factory>
        <!-- Database connection settings -->
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/myapp</property>
        <property name="connection.username">root</property>
        <property name="connection.password">password</property>
        
        <!-- SQL dialect -->
        <property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>
        
        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql">true</property>
        <property name="format_sql">true</property>
        
        <!-- Drop and re-create the database schema on startup -->
        <property name="hbm2ddl.auto">update</property>
        
        <!-- Names the annotated entity class -->
        <mapping class="com.example.model.User"/>
        <mapping class="com.example.model.Order"/>
    </session-factory>
</hibernate-configuration>

Podstawowe adnotacje Hibernate

Prosta encja User

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "user_name", nullable = false, length = 100)
    private String name;
    
    @Column(unique = true, nullable = false)
    private String email;
    
    private Integer age;
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    @Column(name = "is_active")
    private Boolean active = true;
    
    // Default constructor (required by Hibernate)
    public User() {}
    
    public User(String name, String email, Integer age) {
        this.name = name;
        this.email = email;
        this.age = age;
        this.createdAt = LocalDateTime.now();
    }
    
    // Getters and setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    
    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }
    
    public LocalDateTime getCreatedAt() { return createdAt; }
    public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
    
    public Boolean getActive() { return active; }
    public void setActive(Boolean active) { this.active = active; }
    
    @Override
    public String toString() {
        return "User{id=" + id + ", name='" + name + "', email='" + email + "'}";
    }
}
Pro tip: @Entity oznacza klasę jako Hibernate entity. @Table(name) mapuje na konkretną tabelę. Bez @Table, Hibernate używa nazwy klasy jako nazwa tabeli.

Kluczowe adnotacje

AdnotacjaPrzeznaczeniePrzykład
@EntityOznacza klasę jako encję@Entity public class User
@TableMapuje na tabelę w bazie@Table(name = „users”)
@IdKlucz główny@Id private Long id
@GeneratedValueAuto-generated values@GeneratedValue(strategy = IDENTITY)
@ColumnMapuje pole na kolumnę@Column(name = „user_name”)
@TemporalMapowanie dat@Temporal(TemporalType.TIMESTAMP)

SessionFactory i Session management

SessionFactory setup

import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;

public class HibernateUtil {
    
    private static SessionFactory sessionFactory;
    
    static {
        try {
            Configuration configuration = new Configuration();
            configuration.configure("hibernate.cfg.xml");
            
            StandardServiceRegistryBuilder builder = 
                new StandardServiceRegistryBuilder().applySettings(configuration.getProperties());
            
            sessionFactory = configuration.buildSessionFactory(builder.build());
            
        } catch (Exception e) {
            System.err.println("Initial SessionFactory creation failed." + e);
            throw new ExceptionInInitializerError(e);
        }
    }
    
    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }
    
    public static void shutdown() {
        getSessionFactory().close();
    }
}

CRUD operations

import org.hibernate.Session;
import org.hibernate.Transaction;
import java.util.List;

public class UserDAO {
    
    // Create
    public void saveUser(User user) {
        Transaction transaction = null;
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            transaction = session.beginTransaction();
            session.save(user);
            transaction.commit();
        } catch (Exception e) {
            if (transaction != null) {
                transaction.rollback();
            }
            throw e;
        }
    }
    
    // Read
    public User getUserById(Long id) {
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            return session.get(User.class, id);
        }
    }
    
    public List<User> getAllUsers() {
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            return session.createQuery("FROM User", User.class).list();
        }
    }
    
    // Update
    public void updateUser(User user) {
        Transaction transaction = null;
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            transaction = session.beginTransaction();
            session.update(user);
            transaction.commit();
        } catch (Exception e) {
            if (transaction != null) {
                transaction.rollback();
            }
            throw e;
        }
    }
    
    // Delete
    public void deleteUser(Long id) {
        Transaction transaction = null;
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            transaction = session.beginTransaction();
            User user = session.get(User.class, id);
            if (user != null) {
                session.delete(user);
            }
            transaction.commit();
        } catch (Exception e) {
            if (transaction != null) {
                transaction.rollback();
            }
            throw e;
        }
    }
}

HQL (Hibernate Query Language)

public class UserDAO {
    
    // Find users by name
    public List<User> findUsersByName(String name) {
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            String hql = "FROM User u WHERE u.name LIKE :name";
            return session.createQuery(hql, User.class)
                    .setParameter("name", "%" + name + "%")
                    .list();
        }
    }
    
    // Find active users older than specified age
    public List<User> findActiveUsersOlderThan(int age) {
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            String hql = "FROM User u WHERE u.active = true AND u.age > :age ORDER BY u.name";
            return session.createQuery(hql, User.class)
                    .setParameter("age", age)
                    .list();
        }
    }
    
    // Count users by criteria
    public Long countUsersByEmail(String emailDomain) {
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            String hql = "SELECT COUNT(u) FROM User u WHERE u.email LIKE :domain";
            return session.createQuery(hql, Long.class)
                    .setParameter("domain", "%" + emailDomain)
                    .uniqueResult();
        }
    }
    
    // Update multiple records
    public int deactivateOldUsers(int maxAge) {
        Transaction transaction = null;
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            transaction = session.beginTransaction();
            String hql = "UPDATE User SET active = false WHERE age > :maxAge";
            int updatedEntities = session.createQuery(hql)
                    .setParameter("maxAge", maxAge)
                    .executeUpdate();
            transaction.commit();
            return updatedEntities;
        } catch (Exception e) {
            if (transaction != null) {
                transaction.rollback();
            }
            throw e;
        }
    }
}

Relationships – One-to-Many example

// User entity with orders
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    private String email;
    
    // One user can have many orders
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<Order> orders = new ArrayList<>();
    
    // Helper method
    public void addOrder(Order order) {
        orders.add(order);
        order.setUser(this);
    }
    
    // getters, setters...
}

// Order entity
@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String orderNumber;
    private BigDecimal amount;
    
    @Temporal(TemporalType.TIMESTAMP)
    private Date orderDate;
    
    // Many orders belong to one user
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
    
    // constructors, getters, setters...
}

Working with relationships

public void createUserWithOrders() {
    Transaction transaction = null;
    try (Session session = HibernateUtil.getSessionFactory().openSession()) {
        transaction = session.beginTransaction();
        
        // Create user
        User user = new User("John Doe", "john@example.com", 30);
        
        // Create orders
        Order order1 = new Order("ORD-001", new BigDecimal("99.99"));
        Order order2 = new Order("ORD-002", new BigDecimal("149.50"));
        
        // Establish relationships
        user.addOrder(order1);
        user.addOrder(order2);
        
        // Save (cascade will save orders too)
        session.save(user);
        
        transaction.commit();
    } catch (Exception e) {
        if (transaction != null) {
            transaction.rollback();
        }
        throw e;
    }
}

// Fetch user with orders (lazy loading)
public User getUserWithOrders(Long userId) {
    try (Session session = HibernateUtil.getSessionFactory().openSession()) {
        User user = session.get(User.class, userId);
        
        // Trigger lazy loading
        user.getOrders().size();
        
        return user;
    }
}

Best practices

✅ Hibernate best practices:

  • Always use transactions – dla data consistency
  • Close sessions properly – try-with-resources recommended
  • Use lazy loading wisely – avoid N+1 queries problem
  • Configure connection pooling – HikariCP, C3P0
  • Enable SQL logging – w development dla debugging
  • Use appropriate fetch strategies – LAZY vs EAGER
Błąd #1: Zapominanie o transakcjach – może prowadzić do data corruption.
Błąd #2: Nie zamykanie Session objects – memory leaks i connection pool exhaustion.
Błąd #3: Nadużywanie EAGER loading – może prowadzić do loading całej bazy danych.
Błąd #4: N+1 queries problem – loading related entities w pętli zamiast w jednym query.

Hibernate vs JDBC porównanie

AspektHibernateJDBC
Development speedSzybsze (less boilerplate)Wolniejsze (more code)
Learning curveStroma (complex concepts)Łagodniejsza (simple API)
PerformanceDobra (z optymalizacjami)Bardzo dobra (direct SQL)
MaintenanceŁatwiejszeTrudniejsze
SQL controlOgraniczonaPełna kontrola
CachingWbudowane (L1, L2)Manual implementation
Kiedy używać Hibernate zamiast JDBC?

Hibernate dla complex business domains z relationships, rapid development, automatic schema generation. JDBC dla high-performance applications, simple queries, full SQL control.

Jak rozwiązać N+1 queries problem?

Use JOIN FETCH w HQL, @BatchSize annotation, lub configure appropriate fetch strategies. Enable SQL logging żeby detect N+1 problems.

Co to jest LazyInitializationException?

Occurs when accessing lazy-loaded properties outside of session context. Solutions: eager loading, keeping session open, using DTOs, lub initialize collections within session.

Czy Hibernate generuje efficient SQL?

Generally yes, ale depends on configuration. Use SQL logging, query plan analysis, i proper fetch strategies. For complex queries consider native SQL lub stored procedures.

Jak testować kod z Hibernate?

Use in-memory databases (H2) dla unit tests, transaction rollback w tests, separate test configuration. Consider using TestContainers dla integration tests z real database.

Co to jest Second Level Cache?

Shared cache across sessions dla frequently accessed data. Improves performance ale adds complexity. Configure carefully – inappropriate caching może cause stale data issues.

Przydatne zasoby:

🚀 Zadanie dla Ciebie

Stwórz complete Hibernate application dla library system:

  • Entities: Book, Author, Category, Member, BorrowRecord
  • Relationships: Book many-to-many Authors, Book many-to-one Category
  • CRUD operations dla all entities
  • HQL queries: find books by author, available books, overdue loans
  • Proper transaction management
  • Configuration dla MySQL database

Test scenarios:

  • Create authors i books z relationships
  • Search books by different criteria
  • Track book borrowing i returns
  • Generate reports using HQL aggregations

Masz pytania o Hibernate? Podziel się swoimi doświadczeniami w komentarzach – ORM znacznie ułatwia development, ale requires understanding underlying concepts!

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Przewijanie do góry