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.
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
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); }
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 + "'}"; } }
Kluczowe adnotacje
Adnotacja | Przeznaczenie | Przykład |
---|---|---|
@Entity | Oznacza klasę jako encję | @Entity public class User |
@Table | Mapuje na tabelę w bazie | @Table(name = „users”) |
@Id | Klucz główny | @Id private Long id |
@GeneratedValue | Auto-generated values | @GeneratedValue(strategy = IDENTITY) |
@Column | Mapuje pole na kolumnę | @Column(name = „user_name”) |
@Temporal | Mapowanie 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
Hibernate vs JDBC porównanie
Aspekt | Hibernate | JDBC |
---|---|---|
Development speed | Szybsze (less boilerplate) | Wolniejsze (more code) |
Learning curve | Stroma (complex concepts) | Łagodniejsza (simple API) |
Performance | Dobra (z optymalizacjami) | Bardzo dobra (direct SQL) |
Maintenance | Łatwiejsze | Trudniejsze |
SQL control | Ograniczona | Pełna kontrola |
Caching | Wbudowane (L1, L2) | Manual implementation |
Hibernate dla complex business domains z relationships, rapid development, automatic schema generation. JDBC dla high-performance applications, simple queries, full SQL control.
Use JOIN FETCH w HQL, @BatchSize annotation, lub configure appropriate fetch strategies. Enable SQL logging żeby detect N+1 problems.
Occurs when accessing lazy-loaded properties outside of session context. Solutions: eager loading, keeping session open, using DTOs, lub initialize collections within session.
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.
Use in-memory databases (H2) dla unit tests, transaction rollback w tests, separate test configuration. Consider using TestContainers dla integration tests z real database.
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!