MyBatis или Hibernate?
Рано или поздно Java-разработчик задаёт себе вопрос: Hibernate или MyBatis? От ответа зависит архитектура проекта, а ответ, в свою очередь, зависит от задачи. Давайте разберёмся, что они такое и когда какой лучше.
Итак, Hibernate это ORM framework. Его полное название Hibernate ORM. Он полностью поддерживает JPA и, фактически, является имплементацией JPA. Его основная задача – понять, как размечены ваши классы аннотациями ORM, построить по ним схему и собирать объекты по строкам из базы. Hibernate сам пишет за вас запросы к базе. Для вас работа с базой выглядит как работа с объектами. Сохранить объект, загрузить объект, обновить объект. Как, собственно, и должно быть. Это же Object Relational Mapping!
MyBatis это Java persistence framework. Не ORM. MyBatis связывает методы интерфейса маппера с SQL-запросом. MyBatis не умеет создавать схему. И, честно говоря, вообще ничего о схеме не знает. Он превращает вызов метода в запрос к базе, и как результат вызова метода возвращает результат запроса.
Как работает MyBatis
Приложения Java концептуально не могут напрямую взаимодействовать с базами данных, которые управляются с помощью специализированных SQL-запросов. Для того чтобы программа смогла обратиться к БД, используются различные инструменты, например:
- ORM-фреймворки — преобразующие информацию, хранящуюся в базе данных, в объекты с помощью объектно-ориентированных языков программирования;
- СУБД — системы управления базами данных, представляющие собой специализированное программное обеспечение, работа которого зависит от типа БД.
Эти инструменты представляют собой промежуточный уровень абстракции. Они являются посредниками, которые преобразуют информацию из баз данных в формат, используемый приложениями в соответствии с конкретным запросом. Однако они реализуют это различными методами, зачастую не самыми удобными для конкретного приложения, из-за чего разработчику приходится использовать готовые дополнительные инструменты или создавать их самому.
MyBatis также является средой-посредником между приложением и базой данных. Однако работа платформы основана на персистентных слоях (уровнях доступа к данным). Они обеспечивают выполнение таких операций с базой данных, как выбор, вставка, обновление, удаление и т.д. В MyBatis объекты Java (данные приложения и способы их обработки) связываются с операторами SQL и хранимыми процедурами баз данных с помощью аннотаций (описаний) и дескриптора, преобразующего XML-запросы. При этом платформа предоставляет пользователю полный набор функций для работы с БД, а многие процессы автоматизирует, упрощая общую архитектуру Java-приложения.
Таким образом, эта платформа представляет альтернативный вариант взаимодействия с базами данных в сравнении с Java Database Connectivity (стандартом или интерфейсом работы с данными) и ORM-фреймворками, такими как Hibernate.
Преимущества MyBatis
- Открытый исходный код — платформа распространяется по бесплатной лицензии, ее можно дорабатывать под конкретные задачи, что делает ее более гибкой в сравнении с конкурентами.
- Динамический SQL — функция, которая позволяет точно структурировать сложный SQL-оператор, тем самым избежав ошибок в его синтаксисе, которые часто возникают при ручном написании.
- Кэширование данных — в MyBatis оно декларативное, то есть если оператор пометить как кэшированный, то все извлеченные с его помощью данные из БД тоже будут храниться в кэше, причем платформа поддерживает различные форматы кэша.
- Простота — за счет автоматизации многих процессов у MyBatis ниже порог вхождения по сравнению с другими инструментами-посредниками (в частности Hibernate), что делает его более доступным для разработчиков.
- Оптимизация SQL — операторы SQL в MyBatis записываются вручную, поэтому поля запросов можно указывать по мере необходимости, что снижает нагрузку на систему разработчика.
- Совместимость — платформа совместима с различными базами данных, поддерживающими JDDS, а также с некоторыми Java-фреймворками (например Spring).
Недостатки MyBatis
- Нагрузка на разработчиков — операторы SQL в MyBatis нужно писать вручную, что довольно сложно, особенно если есть много полей и связанных таблиц.
- Плохая переносимость БД — операторы в различных базах данных реализованы по-разному, поэтому перенести БД просто так не получится.
MyBatis — хорошая альтернатива другим инструментам, обеспечивающим взаимодействие приложений с базами данных. Особенно эта платформа эффективна при работе со сложными запросами, агрегирующими информацию в базах данных. Однако, так как это проект с открытым исходным кодом, многие функции в нем еще не оптимизированы, что несколько ограничивает возможности MyBatis.
Что такое Hibernate?
Это — одна из наиболее популярных реализаций ORM-модели. Объектно-реляционная модель описывает отношения между программными объектами и записями в БД. Конечно, функционал Hibernate очень широк, но мы остановимся на самых простых функциях. Наша цель: создать CRUD-приложение (Create,Read,Update,Delete), которое будет уметь:
- Создавать пользователей (User), а также искать их в базе данных по ID, обновлять их данные в базе, а также удалять из базы.
- Присваивать пользователям объекты автомобилей (Auto). Создавать, редактировать, находить и удалять автомобили из базы данных.
- Кроме того, приложение должно автоматически удалять “бесхозные” автомобили из БД. Т.е. при удалении пользователя, все принадлежащии ему автомобили также должны быть удалены из БД.
Структура нашего проекта будет следующей:
Как видите, ничего сложного. 6 классов + 1 файл с конфигами. Для начала, создадим новый maven-проект в Intellij Idea. File -> New Project. Из предлагаемых типов проектов выбирайте Maven и переходите дальше.
Apache Maven — фреймворк для автоматизации сборки проектов на основе описания их структуры в файлах на языке POM. Вся структура вашего проекта будет описана в файле pom.xml, который IDEA сама создаст в корне вашего проекта. В настройках проекта вам нужно будет указать параметры Maven — groupId и artifactId. Обычно в проектах groupId наименование организации или подразделения, туда записывают доменное имя организации или сайта проекта. В свою очередь artifactId — название проекта. Для groupdId можете указать com.вашНикнейм.javarush
, на работу приложения это никак не повлияет. Для 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:
Для 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.
https://secure.esputnik.com.ua/4Q0Ef6d2SOs Теперь перейдем к 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
во многом похож на таблицу users. Есть поля 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-разработки.
Что лучше подходит для вашего проекта? Смотрите сами:
– Если у вас много простых объектов без сложных связей друг с другом – Hibernate. – Если у вас сложные запросы к базе на агрегацию данных – MyBatis. – Если вы готовы поручить фреймворку создавать и обновлять для вас схему – Hibernate. – Если у вас работа с хранимыми процедурами – MyBatis. – Если вы не хотите писать запросы сами, и работать с JPQL – HIbernate. – Если вы хотите писать свой ORM (а его так или иначе придётся писать) – MyBatis.
Можно попробовать использовать оба фреймворка в одном проекте. Но мы такого на практике не встречали. Поэтому рано или поздно Java-разработчик встаёт перед вопросом: Hibernate или MyBatis?
А что думаете вы? Напишите в комментариях!