JavaRush /Java блог /Random UA /Ваш перший додаток на Hibernate
Джон Дориан
37 рівень

Ваш перший додаток на Hibernate

Стаття з групи Random UA
У цій статті ви познайомитеся з одним з найбільш популярних enterprise-фреймворків для Java і створіть свій перший додаток на Hibernate. Ніколи не чули про Hibernate? Може, чули про нього, але не користувалися? Чи намагалися розпочати, але не вийшло? У всіх трьох випадках - ласкаво просимо під кат:) Ваш перший додаток на Hibernate - 1Всім привіт! У цій статті я розповім про основні особливості фреймворку Hibernate і допоможу вам написати свій перший міні-додаток. Для цього нам знадобляться:
  1. Intellij Idea Ultimate Edition;
    Качаємо з офіційного сайту та активуємо 30-денну пробну версію.
  2. PostgeSQL - одна з найпопулярніших сучасних систем управління базами даних (СУБД);
  3. Maven (вже зашитий в IDEA);
  4. Трошки терпіння.
Стаття орієнтована насамперед тих, хто ніколи не працював з даною технологією, тому кількість коду була максимально зменшена. Почнемо!

Що таке Hibernate?

Це одна з найбільш популярних реалізацій ORM-моделі. Об'єктно-реляційна модель описує відносини між програмними об'єктами та записами у БД. Звичайно, функціонал Hibernate дуже широкий, але ми зупинимося на найпростіших функціях. Наша мета: створити CRUD-додаток (Create,Read,Update,Delete), який вмітиме:
  1. Створювати користувачів (User), а також шукати їх у базі даних за ID, оновлювати їх у базі, а також видаляти з бази.
  2. Надавати користувачам об'єкти автомобілів (Auto). Створювати, редагувати, знаходити та видаляти автомобілі з бази даних.
  3. Крім того, додаток повинен автоматично видаляти "безхазяйні" автомобілі з БД. Тобто. при видаленні користувача, всі його автомобілі також повинні бути видалені з БД.
Структура нашого проекту буде такою: Ваш перший додаток на Hibernate - 2Як бачите, нічого складного. 6 класів + 1 файл із конфігами. Для початку створимо новий maven-проект в Intellij Idea. File -> New Project. З пропонованих типів проектів вибирайте Maven і переходьте далі. Ваш перший додаток на Hibernate - 3Apache Maven — фреймворк для автоматизації складання проектів на основі опису їхньої структури у файлух мовою POM. Вся структура вашого проекту буде описана у файлі pom.xml, який IDEA сама створить у корені вашого проекту. У налаштуваннях проекту потрібно буде вказати параметри Maven - groupId і artifactId. Зазвичай у проектах групивизначається назва організації або підрозділу, туди записують доменне ім'я організації або сайту проекту. У свою чергу artifactId – назва проекту. Для groupdId можете вказатиcom.вашНикнейм.codegymНа роботу програми це ніяк не вплине. Для artifactId виберіть будь-яку назву проекту, що вам сподобалася. Version також можете залишити без змін. Ваш перший додаток на Hibernate - 4На останньому екрані просто підтвердьте введені дані. Ваш перший додаток на Hibernate - 5Отже, проект ми створабо, залишилося всього нічого - написати код і змусити його працювати :) Перш за все, якщо ми хочемо створити додаток, що працюють з базою даних - нам безумовно не обійтися без бази даних! Качаємо PostgreSQL звідси(Я використовую 9 версію). PostgreSQL має створений за замовчуванням користувач 'postgres', пароль для нього вам потрібно буде придумати при установці. Не забувайте пароль, він нам знадобиться згодом! (Взагалі, користуватися дефолтною БД у додатках - bad practice, але з метою скоротити обсяг геморою зі створенням своєї БД обійдемося їй). Якщо ви не товаришуєте з командним рядком та SQL-запитами, є хороша новина. Intellij IDEA надає цілком придатний інтерфейс користувача для роботи з БД. Виглядає він так:Ваш перший додаток на Hibernate - 6(Знаходиться на правій бічній панелі IDEA, вкладка Database) Для створення підключення натисніть "+", виберіть нашого провайдера (PostgeSQL). Заповніть поля з користувачем, іменем БД (і те й інше – postgres) та введіть пароль, який задали під час встановлення PostgreSQL. При необхідності – завантажте драйвер Postgres, це можна зробити на цій самій сторінці. Натисніть "Test Connection", щоб перевірити, чи з'єднання з БД встановлено. Якщо бачите напис "Successful" - їдемо далі. Тепер створимо потрібні таблиці. Усього їх буде дві - users і autos. Параметри для таблиці users: Ваш перший додаток на Hibernate - 7Зверніть увагу, що id є первинним ключем (Primary Key). Якщо не знаєте, що таке первинний ключ в SQL - гугл, це важливо. Налаштування для таблиці autos: Ваш перший додаток на Hibernate - 8Для автомобілів потрібно налаштувати Foreign Key — зовнішній ключ. Він буде пов'язувати наші таблиці. Раджу почитати про нього докладніше; якщо говорити дуже просто - він посилається на зовнішню таблицю, у разі на users. Якщо машина належить користувачеві з id=1, то полі user_id таблиці autos в неї буде 1. Так ми у нашому додатку пов'язуємо користувачів зі своїми автомобілями. У нашій таблиці autos роль зовнішнього ключа виконуватиме поле user_id. Воно буде посилатися на поле ID таблиці users. Ваш перший додаток на Hibernate - 9Таким чином, ми створабо базу даних із двома таблицями. Залишилося зрозуміти, як керувати з Java-коду. Почнемо ми з файлу pom.xml, в якому нам необхідно підключити необхідні бібліотеки (на мові Maven вони називаються dependencies - "залежності"). Усі бібліотеки зберігаються у центральному репозиторії Maven. Ті з них, які ви вкажете в pom.xml, ви можете використовувати у проекті. Ваш pom.xml повинен мати такий вигляд: Ваш перший додаток на Hibernate - 10Нічого складного, як бачите. Ми додали всього 2 залежності - для використання PostgreSQL та Hibernate. Тепер перейдемо до Java-коду. Створіть усі необхідні пакети та класи проекту. Для початку, нам знадобляться моделі даних – класи Userта Auto.
package models;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table (name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    @Column(name = "name")
    private String name;
    //Можна не вказувати Column name, якщо воно збігається з назвою стовпця в таблиці
    private int age;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Auto> autos;

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
        autos = new ArrayList<>();
    }

    public void addAuto(Auto auto) {
        auto.setUser(this);
        autos.add(auto);
    }

    public void removeAuto(Auto auto) {
        autos.remove(auto);
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public List<Auto> getAutos() {
        return autos;
    }

    public void setAutos(List<Auto> autos) {
        this.autos = autos;
    }

    @Override
    public String toString() {
        return "models.User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
package models;

import javax.persistence.*;

@Entity
@Table(name = "autos")
public class Auto {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @Column (name = "model")
    private String model;

    //Можна не вказувати Column name, якщо воно збігається з назвою стовпця в таблиці
    private String color;


    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    public Auto() {
    }

    public Auto(String model, String color) {
        this.model = model;
        this.color = color;
    }

    public int getId() {
        return id;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    @Override
    public String toString() {
        return color + " " + model;
    }
}
Як бачите, класи забезпечені купою поки що незрозумілих анотацій. Почнемо з ними розбиратися. Головна для нас інструкція - @Entity (сутність). Прочитайте в ній у Вікіпедії і запам'ятайте все, це - основа основ. Ця інструкція дозволяє Java-об'єктам вашого класу бути пов'язаними з БД. Щоб клас міг бути сутністю, щодо нього пред'являються такі вимоги:
  • Повинен мати порожній конструктор ( publicабо protected);
  • Не може бути вкладеним, інтерфейсом або enum;
  • Не може бути finalі не може містити finalполів/властивостей;
  • Має містити хоча б одне @Id-поле.
Перевірте ваші entity-класи, ці пункти – дуже популярне місце для "пострілу собі в ногу". Дуже легко щось забути. При цьому entity може:
  • утримувати непусті конструктори;
  • успадковуватися і бути успадкованим;
  • Утримувати інші методи та реалізовувати інтерфейси.
Як бачите, клас Userбагато в чому схожий на таблицю користувачів. Є поля id, name,age. Розташовані над ними анотації пояснення особливо не потребують: і так зрозуміло, що @Id - це вказівка, що поле є ідентифікатором об'єктів цього класу. Анотація @Table над класом вказує як називається таблиця, у якому записуються об'єкти. Зверніть увагу на коментар над полем age: якщо ім'я поля в класі та таблиці збігається - можна не додавати інструкцію @Column, буде працювати і так. Що стосується зазначеного у дужках "strategy = GenerationType.IDENTITY": є кілька стратегій генерації ID. Можете погуглити, але в рамках нашої програми можна не морочитися. Головне, що id для наших об'єктів генеруватимуться автоматично, тому для id відсутній сеттер, і в конструкторі ми його теж не задаємо. Проте, чимось класUserтаки виділяється. Має список машин! Над списком висить інструкція @OneToMany. Вона означає, що одному об'єкту класу user може відповідати кілька машин. Налаштування "mappedBY" вказує на поле user класу Auto. Таким чином машини та користувачі пов'язані між собою. Налаштування orphanRemoval цілком добре перекладається з англійської - "видаляти сиріт". Якщо ми видалимо користувача з БД - всі пов'язані з ним автомобілі також будуть видалені. У свою чергу у класіAutoви побачите поле user з анотацією @ManyToOne (багатьом Auto може відповідати один User) та анотацію @JoinColumn. Вона вказує, через який стовпець у таблиці autos відбувається зв'язок із таблицею users (той самий зовнішній ключ, про який ми говорабо раніше). Після створення моделі даних настав час навчити нашу програму виконувати з цими даними операції в БД. Почнемо з утилітного класу HibernateSessionFactoryUtil. Він має лише одне завдання — створювати для нашого додатку фабрику сесій для роботи з БД (привіт, патерн "Фабрика!"). Більше він нічого не вміє.
package utils;

import models.Auto;
import models.User;
import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;

public class HibernateSessionFactoryUtil {
    private static SessionFactory sessionFactory;

    private HibernateSessionFactoryUtil() {}

    public static SessionFactory getSessionFactory() {
        if (sessionFactory == null) {
            try {
                Configuration configuration = new Configuration().configure();
                configuration.addAnnotatedClass(User.class);
                configuration.addAnnotatedClass(Auto.class);
                StandardServiceRegistryBuilder builder = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties());
                sessionFactory = configuration.buildSessionFactory(builder.build());

            } catch (Exception e) {
                System.out.println("Виняток!" + e);
            }
        }
        return sessionFactory;
    }
}
У цьому вся класі ми створюємо новий об'єкт конфігурацій Configuration, і передаємо йому ті класи, які має сприймати як сутності — Userі Auto. Зверніть увагу на метод configuration.getProperties(). Які ще properties? Звідки? Properties — це параметри для роботи hibernate, зазначені у спеціальному файлі hibernate.cfg.xml. Ваш перший додаток на Hibernate - 11Hibernate.cfg.xml зачитується тут: new Configuration().configure(); У ньому, як бачите, немає нічого особливого - параметри з'єднання з базою даних, і спеціальний параметр show_sql. Він потрібен для того, щоб усі sql-запити, які hibernate буде виконувати до нашої БД, виводабося в консоль. Таким чином, ви будете бачити, що конкретно робить Hibernate в кожний момент часу і позбавитеся від ефекту "магії". Далі нам знадобиться класUserDAO. (По-доброму, програмувати потрібно через інтерфейси — створити інтерфейс UserDAOта окремо його реалізацію UserDAOImpl, але для скорочення обсягу коду я опущу це. Не робіть так у реальних проектах!). DAO (data access object) - один з найпоширеніших патернів проектування, "Доступ до даних". Його сенс простий — створити у додатку шар, який відповідає лише доступ до даних, і більше нізащо. Дістати дані з БД, оновити дані, видалити дані і все. Почитайте про DAO докладніше, у роботі користуватиметеся ними постійно. Що ж вміє наш клас UserDao? Власне, як і всі DAO, він уміє лише працювати з даними. Знайти користувача по id, оновити його дані, видалити його, витягнути з БД перелік всіх користувачів чи зберегти в БД нового користувача — ось його функционал.
package dao;

import models.Auto;
import models.User;
import org.hibernate.Session;
import org.hibernate.Transaction;
import utils.HibernateSessionFactoryUtil;
import java.util.List;

public class UserDao {

    public User findById(int id) {
        return HibernateSessionFactoryUtil.getSessionFactory().openSession().get(User.class, id);
    }

    public void save(User user) {
        Session session = HibernateSessionFactoryUtil.getSessionFactory().openSession();
        Transaction tx1 = session.beginTransaction();
        session.save(user);
        tx1.commit();
        session.close();
    }

    public void update(User user) {
        Session session = HibernateSessionFactoryUtil.getSessionFactory().openSession();
        Transaction tx1 = session.beginTransaction();
        session.update(user);
        tx1.commit();
        session.close();
    }

    public void delete(User user) {
        Session session = HibernateSessionFactoryUtil.getSessionFactory().openSession();
        Transaction tx1 = session.beginTransaction();
        session.delete(user);
        tx1.commit();
        session.close();
    }

    public Auto findAutoById(int id) {
        return HibernateSessionFactoryUtil.getSessionFactory().openSession().get(Auto.class, id);
    }

    public List<User> findAll() {
        List<User> users = (List<User>)  HibernateSessionFactoryUtil.getSessionFactory().openSession().createQuery("From User").list();
        return users;
    }
}
Методи UserDaoсхожі один на одного. У більшості з них ми отримуємо об'єкт Session (сесія з'єднання з нашою БД) за допомогою нашої Фабрики Сесій, створюємо в рамках цієї сесії одиночну транзакцію, виконуємо необхідні перетворення даних, зберігаємо результат транзакції в БД та закриваємо сесію. Самі методи також, як бачите, досить прості. Саме DAO - "серце" нашої програми. Однак ми не будемо створювати DAO безпосередньо і викликати його методи в нашому методі main(). Вся логіка буде переміщена до класу UserService.
package services;

import dao.UserDao;
import models.Auto;
import models.User;

import java.util.List;

public class UserService {

    private UserDao usersDao = new UserDao();

    public UserService() {
    }

    public User findUser(int id) {
        return usersDao.findById(id);
    }

    public void saveUser(User user) {
        usersDao.save(user);
    }

    public void deleteUser(User user) {
        usersDao.delete(user);
    }

    public void updateUser(User user) {
        usersDao.update(user);
    }

    public List<User> findAllUsers() {
        return usersDao.findAll();
    }

    public Auto findAutoById(int id) {
        return usersDao.findAutoById(id);
    }


}
Service - шар даних у додатку, що відповідає за виконання бізнес-логіки. Якщо ваша програма має виконати якусь бізнес-логіку, вона робить це через сервіси. Сервіс містить у собі UserDao, і у методах викликає методи DAO. Це може здатися вам дублюванням функцій (чому б просто не викликати методи з dao-об'єкта), але при великій кількості об'єктів і складній логіці розбиття програми на шари дає величезні переваги (це good practice, запам'ятайте цю інформацію на майбутнє і почитайте про "шари додатку "). У нас у сервісі логіка проста, а в реальних проектах методи сервісів будуть містити куди більше одного рядка коду:) Тепер у нас є все, що потрібно для роботи програми! Давайте створимо у методіmain()користувача, машини йому, зв'яжемо їх друг з одним і збережемо в БД.
import models.Auto;
import models.User;
import services.UserService;

import java.sql.SQLException;

public class Main {
    public static void main(String[] args) throws SQLException {

        UserService userService = new UserService();
        User user = new User("Masha",26);
        userService.saveUser(user);
        Auto ferrari = new Auto("Ferrari", "red");
        ferrari.setUser(user);
        user.addAuto(ferrari);
        Auto ford = new Auto("Ford", "black");
        ford.setUser(user);
        user.addAuto(ford);
        userService.updateUser(user);
    }
}
Як бачите, у таблиці users з'явився свій запис, а таблиці autos — свої. Ваш перший додаток на Hibernate - 13Ваш перший додаток на Hibernate - 14Спробуймо перейменувати нашого користувача. Очистимо таблицю users та виконаємо код
import models.Auto;
import models.User;
import services.UserService;

import java.sql.SQLException;

public class Main {
    public static void main(String[] args) throws SQLException {

        UserService userService = new UserService();
        User user = new User("Masha",26);
        userService.saveUser(user);
        Auto ferrari = new Auto("Ferrari", "red");
        user.addAuto(ferrari);
        Auto ford = new Auto("Ford", "black");
        ford.setUser(user);
        user.addAuto(ford);
        userService.updateUser(user);
        user.setName("Sasha");
        userService.updateUser(user);
    }
}
Працює! Ваш перший додаток на Hibernate - 15А якщо видалити користувача? Очистимо таблицю users (autos очиститься сама) та виконаємо код
import models.Auto;
import models.User;
import services.UserService;

import java.sql.SQLException;

public class Main {
    public static void main(String[] args) throws SQLException {

        UserService userService = new UserService();
        User user = new User("Masha",26);
        userService.saveUser(user);
        Auto ferrari = new Auto("Ferrari", "red");
        user.addAuto(ferrari);
        Auto ford = new Auto("Ford", "black");
        ford.setUser(user);
        user.addAuto(ford);
        userService.updateUser(user);
        user.setName("Sasha");
        userService.updateUser(user);
        userService.deleteUser(user);
    }
}
І наші таблиці абсолютно порожні (зверніть увагу на консоль, туди виведуть усі запити, які виконав Hibernate). Ви можете "грати" з додатком і спробувати всі його функції. Наприклад, створіть користувача з машинами, збережіть його в БД, подивіться який ID йому присвоєно, і спробуйте в методі main()"витягнути" користувача з БД з цього id і вивести в консоль список його машин. Звичайно, ми побачабо лише невелику частину функціональності Hibernate. Його можливості дуже широкі, і він давно є одним із промислових стандартів Java-розробки. Якщо ви хочете вивчити його у всіх подробицях – можу рекомендувати книгу "Java Persistence API та Hibernate", оглядна яку я робив в одній із минулих статей. Сподіваюся, ця стаття була корисною для читачів. Якщо у вас будуть питання - задавайте їх у коментарях, з радістю відповім :) Також не забувайте підтримати автора у конкурсі, поставивши "Подобається". А краще - "Дуже подобається" :) Успіхів у навчанні!
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ