У цій статті ви познайомитеся з одним з найбільш популярних enterprise-фреймворків для Java і створіть свій перший додаток на Hibernate. Ніколи не чули про Hibernate? Може, чули про нього, але не користувалися? Чи намагалися розпочати, але не вийшло? У всіх трьох випадках - ласкаво просимо під кат:) Всім привіт! У цій статті я розповім про основні особливості фреймворку Hibernate і допоможу вам написати свій перший міні-додаток. Для цього нам знадобляться:
Тепер перейдемо до Java-коду. Створіть усі необхідні пакети та класи проекту. Для початку, нам знадобляться моделі даних – класи
- Intellij Idea Ultimate Edition;
Качаємо з офіційного сайту та активуємо 30-денну пробну версію. - PostgeSQL - одна з найпопулярніших сучасних систем управління базами даних (СУБД);
- Maven (вже зашитий в IDEA);
- Трошки терпіння.
Що таке Hibernate?
Це одна з найбільш популярних реалізацій ORM-моделі. Об'єктно-реляційна модель описує відносини між програмними об'єктами та записами у БД. Звичайно, функціонал Hibernate дуже широкий, але ми зупинимося на найпростіших функціях. Наша мета: створити CRUD-додаток (Create,Read,Update,Delete), який вмітиме:- Створювати користувачів (User), а також шукати їх у базі даних за ID, оновлювати їх у базі, а також видаляти з бази.
- Надавати користувачам об'єкти автомобілів (Auto). Створювати, редагувати, знаходити та видаляти автомобілі з бази даних.
- Крім того, додаток повинен автоматично видаляти "безхазяйні" автомобілі з БД. Тобто. при видаленні користувача, всі його автомобілі також повинні бути видалені з БД.
com.вашНикнейм.codegym
На роботу програми це ніяк не вплине. Для artifactId виберіть будь-яку назву проекту, що вам сподобалася. Version також можете залишити без змін. На останньому екрані просто підтвердьте введені дані. Отже, проект ми створабо, залишилося всього нічого - написати код і змусити його працювати :) Перш за все, якщо ми хочемо створити додаток, що працюють з базою даних - нам безумовно не обійтися без бази даних! Качаємо PostgreSQL звідси(Я використовую 9 версію). PostgreSQL має створений за замовчуванням користувач 'postgres', пароль для нього вам потрібно буде придумати при установці. Не забувайте пароль, він нам знадобиться згодом! (Взагалі, користуватися дефолтною БД у додатках - bad practice, але з метою скоротити обсяг геморою зі створенням своєї БД обійдемося їй). Якщо ви не товаришуєте з командним рядком та SQL-запитами, є хороша новина. Intellij IDEA надає цілком придатний інтерфейс користувача для роботи з БД. Виглядає він так:(Знаходиться на правій бічній панелі IDEA, вкладка Database) Для створення підключення натисніть "+", виберіть нашого провайдера (PostgeSQL). Заповніть поля з користувачем, іменем БД (і те й інше – postgres) та введіть пароль, який задали під час встановлення PostgreSQL. При необхідності – завантажте драйвер Postgres, це можна зробити на цій самій сторінці. Натисніть "Test Connection", щоб перевірити, чи з'єднання з БД встановлено. Якщо бачите напис "Successful" - їдемо далі. Тепер створимо потрібні таблиці. Усього їх буде дві - users і autos. Параметри для таблиці users: Зверніть увагу, що id є первинним ключем (Primary Key). Якщо не знаєте, що таке первинний ключ в SQL - гугл, це важливо. Налаштування для таблиці autos: Для автомобілів потрібно налаштувати Foreign Key — зовнішній ключ. Він буде пов'язувати наші таблиці. Раджу почитати про нього докладніше; якщо говорити дуже просто - він посилається на зовнішню таблицю, у разі на users. Якщо машина належить користувачеві з id=1, то полі user_id таблиці autos в неї буде 1. Так ми у нашому додатку пов'язуємо користувачів зі своїми автомобілями. У нашій таблиці autos роль зовнішнього ключа виконуватиме поле user_id. Воно буде посилатися на поле ID таблиці users. Таким чином, ми створабо базу даних із двома таблицями. Залишилося зрозуміти, як керувати з Java-коду. Почнемо ми з файлу pom.xml, в якому нам необхідно підключити необхідні бібліотеки (на мові Maven вони називаються dependencies - "залежності"). Усі бібліотеки зберігаються у центральному репозиторії Maven. Ті з них, які ви вкажете в pom.xml, ви можете використовувати у проекті. Ваш pom.xml повинен мати такий вигляд: Нічого складного, як бачите. Ми додали всього 2 залежності - для використання PostgreSQL та Hibernate.
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-поле.
- утримувати непусті конструктори;
- успадковуватися і бути успадкованим;
- Утримувати інші методи та реалізовувати інтерфейси.
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.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 — свої. Спробуймо перейменувати нашого користувача. Очистимо таблицю 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);
}
}
Працює! А якщо видалити користувача? Очистимо таблицю 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", оглядна яку я робив в одній із минулих статей. Сподіваюся, ця стаття була корисною для читачів. Якщо у вас будуть питання - задавайте їх у коментарях, з радістю відповім :) Також не забувайте підтримати автора у конкурсі, поставивши "Подобається". А краще - "Дуже подобається" :) Успіхів у навчанні!