MyBatis як більш швидка альтернатива Hibernate

В Java-співтоваристві Hibernate framework де-факто вважається стандартом для зручної роботи з базою даних. Розробнику важко вибрати інший фреймфорк, тому що часом він не знає про існування альтернатив. У цій статті я проведу курс молодого бійця по роботі з MyBatis framework. Повністю охопити весь framework не вийде, але інформації буде достатньо, що б побачити переваги і слабкі сторони даного framework'а і почати працювати з MyBatis.

MyBatis не реалізує JPA спеки, а є альтернативою JPA. Основна відмінність MyBatis від Hibernate — це те як проводиться мапінг об'єктів. Hibernate мапит таблиці БД на сутності, даючи нам доступ до даних. Для отримання даних Hibernate генерує SQL запити, а генеруються запити хороші до пори — до часу, а потім вони з'їдають купу часу, стають громіздкими і не керованими. MyBatis мапится не на таблиці, а на SQL запити, за формування запитів відповідає розробник і тільки від нього буде залежати, як швидко буде працювати програма.

З преамбулою закінчили, тепер можна перейти безпосередньо до створення невеликого проекту з використанням MyBatis, що б познайомитися з ним ближче. Не буду оригінальним, зробимо кілька запитів до БД з використанням MyBatis. У прикладі буду використовувати СУБД MySQL, а ви можете використовувати будь-яку іншу СУБД, яка вам до душі.

Створимо БД mybatis:

CREATE DATABASE `mybatis`;

Створимо таблиці subscriber, tariff, payments:

subscriber:

CREATE TABLE `mybatis`.`subscriber` (
`id` INT( 10 ) NOT NULL ,
`name` VARCHAR( 255 ) NOT NULL ,
`ref_tariff` VARCHAR( 10 ) NOT NULL ,
PRIMARY KEY ( `id` ) 
) ENGINE = MYISAM

tariff:

CREATE TABLE `mybatis`.`tariff` (
`id` INT( 10 ) NOT NULL ,
`descr` VARCHAR( 255 ) NOT NULL ,
PRIMARY KEY ( `id` ) 
) ENGINE = MYISAM

payments:

CREATE TABLE `mybatis`.`payments` (
`id` INT( 10 ) NOT NULL ,
`ref_subscriber` INT( 10 ) NOT NULL ,
`сума` INT( 10 ) NOT NULL ,
PRIMARY KEY ( `id` ) 
) ENGINE = MYISAM

Найнудніше позаду — у нас є БД, з якої ми будемо отримувати дані, тепер приступимо безпосередньо до роботи з MyBatis. Для початку нам потрібна бібліотека MyBatis. Для отримання бібліотеки ми будемо використовувати maven, необхідно додати залежність в налаштування проекту(pom.xml):

<dependency> 
<groupId>org.mybatis</groupId> 
<artifactId>mybatis</artifactId> 
<version>3.2.8</version> 
</dependency>

На момент написання статті остання версія MyBatis 3.2.8

Після того як бібліотека успішно завантажилася, необхідно налаштувати підключення до БД. Налаштування здійснюються в конфігураційному файлі mybatis-config.xml.

Нижче наведено лістинг конфігураційного файлу:

<?xml version="1.0" encoding="UTF-8" ?> 
<!DOCTYPE configuration 
PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-config.dtd"> 
<configuration> 
<properties resource="config.properties"> <!--посилання на файл з властивостями(посилання на СУБД, логін, пароль і тп.)-->
</properties> 
<settings><!--в даному блоці можна налаштувати багато параметрів. Повний список параметрів http://mybatis.github.io/mybatis-3/configuration.html#settings-->
<setting name="logImpl" value="LOG4J"/> 
</settings> 
<environments default="development"><!--в даному блоці налаштовуються підключення до БД-->
<environment id="development"> 
<transactionManager type="JDBC"/> 
<dataSource type="POOLED"> 
<property name="driver" value="${db.driver}"/> 
<property name="url" value="${db.url}"/> 
<property name="username" value="${db.username}"/> 
<property name="password" value="${db.password}"/> 
</dataSource> 
</environment> 
</environments> 
<mappers><!--в даному блоці необхідно описати маперы, які використовуються в проекті-->
<mapper class="kz.jazzsoft.mapper.SubscriberMapper"/> 
<mapper class="kz.jazzsoft.mapper.TariffMapper"/>
<mapper class="kz.jazzsoft.mapper.PaymentMapper"/>
</mappers> 
</configuration>

У лістингу вище я вказав 3 мапера — всі взаємодія з БД буде здійснюватися через маперы і чим детальніше ви будете розуміти як працювати з маперами і формувати запити, тим більш потужніший будуть ваші програми.

Для коректної роботи з MyBatis необхідно створити інтерфейс мапера в якому будуть визначені методи, які будуть використовуватися і xml файл налаштувань в якому будуть описані sql запити, правила їх мапинга на об'єкти і тп.

Створимо інтерфейс kz.jazzsoft.mapper.SubscriberMapper:

package kz.jazzsoft.mapper;

import kz.jazzsoft.dal.Subscriber;

public interface SubscriberMapper {

Subscriber getSubscriberById(id Integer);

List getSubscriber();

}

В даному інтерфейсі ми визначили два методу:

1. getSubscriberById — поверне одного користувача з id;

2. getSubscriber — поверне список користувачів;

Але що б дані методи заробили необхідно створити xml маппер з sql запитами.

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="kz.jazzsoft.mapper.SubscriberMapper">
<select 
id="getSubscriberById" <!--назва методу-->
parameterType="java.lang.Integer" <!--тип вхідних параметрів, може бути воістину різноманітним, починаючи від Map і закінчуючи EntityBean.--> 
>
select * from subscriber where id = #{id} <!-- поле в фігурних дужках це параметр, який прилетів в метод. Якщо це Map - то {ім'я} це ключ до змінної. Якщо метод передаємо EntityBean то {ім'я} - це назва змінної даного bean.-->
</select>

<select id="getSubscriber">
select * from subscriber
</select>
</mapper>

Я втратив ще один момент, який необхідно було зробити — це створити класи beanEntity, на які ми будемо мапить результати виконання запитів.

Subscriber:

package kz.jazzsoft.dal;
import java.util.List;
public class Subscriber {
private Long id;
private String name;
private Tariff tariff;
private List<Payment> payments
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 Tariff getTariff() {
return tariff;
}
public void setTariff(Tariff tariff) {
this.tariff = tariff;
}
public List<Payment> getPayments() {
return paymentList;
}
public void setPayments(List<Payment> payments) {
this.payments = payments;
}
public List<Connection> getConnections() {
return connections;
}
}

Tariff:

package kz.jazzsoft.dal;
public class Tariff {
private Long id;
private String descr;

public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getDescr() {
return descr;
}
public void setDescr(String descr) {
this.descr = descr;
}
}

Payment:

package kz.jazzsoft.dal;
public class Payment {
private Long id;
private Integer summa;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getSumma() {
return discount;
}
public void getSumma(Integer summa) {
this.summa = summa;
}
}

Можна відразу подивитися, як працює код, для цього необхідно підключитися до БД і ініціалізувати потрібний мапер, а поки що він у нас один (SubscriberMapper). Створимо клас, в якому будемо працювати:

Work:

package kz.jazzsoft;
import kz.jazzsoft.mapper.SubscriberMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.Reader;
public class Work {
public static void main(String[] args) {
SqlSessionFactory sqlSessionFactory;
SubscriberMapper subscriberMapper;
Reader reader = null;
try {
reader = Resources
.getResourceAsReader("mybatis-config.xml"); //Читаємо файл з настройками підключення та налаштування MyBatis
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
subscriberMapper = sqlSessionFactory.openSession().getMapper(SubscriberMapper.class); //Створюємо маппер, з якого і будемо викликати методи getSubscriberById і getSubscribers
List<Subscriber> subscribers = subscriberMapper.getSubscribers();
Subscriber subscriber = subscriberMapper.getSubscriberById(101);
} catch (IOException e) {
e.printStackTrace();
}
}
}

Запити виконано і у нас є об'єкти, з якими ми можемо працювати. Ви можете подивитися, що у об'єктів є id і інші поля заповнені, але не всі. Тут є один нюанс, якщо колонка в БД має таке ж ім'я як змінна, то вона автоматично смапиться на неї. Що б розширити можливості мапинга і створювати складні структури в арсеналі MyBatis є тег ResultMap, який дозволяє настроювати довільний мапінг. Робити зв'язку one-to-one і one-to-many.

ResultMap представляє з себе опис правил зв'язку полів EntityBean з колонками таблиць. Приклад для Subscriber:

<resultMap id="subscriber" type="kz.jazzsoft.dal.Subscriber">
<id property="id" column="id"/>
<result property="name" column="name"/> <!--можна поміняти поле name в Subscriber і подивитися результат, відповідно змінивши властивість property-->
</resultMap>

В результаті маппер для Subscriber буде виглядати наступним чином:

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="kz.jazzsoft.mapper.SubscriberMapper">
<resultMap id="subscriber" type="kz.jazzsoft.dal.Subscriber">
<id property="id" column="id"/>
<result property="name" column="name"/>
</resultMap>
<select id="getSubscriberById" parameterType="java.lang.Integer"
resultMap="subscriber"> <!-- посилання на ResultMap по якому і буде відбуватися мапінг-->
select * from subscriber where id = #{id}
</select>

<select id="getSubscribers" resultMap="tariff">
select * from subscriber
</select>
</mapper>

Зв'язок one-to-one здійснюється не складніше прикладу вище. Але нам спочатку необхідно описати наступний мапер Tariff. Через нього ми будемо отримувати дані для зв'язаного поля в Subscriber.

Створюємо сутність Tariff:

package kz.jazzsoft.dal;
public class Tariff {
private Long id;
private String descr;

public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getDescr() {
return descr;
}
public void setDescr(String descr) {
this.descr = descr;
}
}

Створюємо інтерфейс мапера TariffMapper, нам знадобиться тільки один метод:

package kz.jazzsoft.mapper;
import kz.jazzsoft.dal.Tariff;
public interface TariffMapper {
Tariff getTariffById(id Integer);
}

Створюємо мапер:

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="kz.jazzsoft.mapper.TariffMapper">
<resultMap id="tariff" type="kz.jazzsoft.dal.Tariff">
<id property="id" column="id"/>
<result property="descr" column="descr"/>
</resultMap>
<select id="getTariffById" resultMap="tariff" parameterType="java.lang.Integer">
select * from tariff where id = #{id}
</select>
</mapper>

Тепер можна додати Subscriber c Tariff в SubscriberMaper, в resultMap необхідно додати правило зв'язку:

<resultMap id="subscriber" type="kz.jazzsoft.dal.Subscriber">
<id property="id" column="id"/>
<result property="name" column="name"/>
<association 
property="tariff" <!--властивість в Subscriber -->
column="ref_tariff" <!-- колонка в таблиці Subscriber, по якій власне і буде відбуватися зв'язок з потрібним тарифом-->
javaType="kz.jazzsoft.dal.Tariff" <!--опис типу, який у нас буде повертатися-->
select="kz.jazzsoft .mapper.TariffMapper.getTariffById" <!-- Тут у нас робота передається тарифного мапперу, який виконає sql і замапит все згідно своїх налаштувань.-->
fetchType="eager" <!-- Тип запиту--> />
</resultMap>

Необхідно замінити даний resultMap і можна буде дізнатися на якому тарифному(Tariff) плані у нас знаходиться Абонент(Subscriber)

Subscriber.gettariff().getDescr();

Додамо до Абонента(Subscriber) список його платежів(Payments)(one-to-many):

Для початку необхідно створити EntityBean Payment:

package kz.jazzsoft.dal;
public class Payment {
private Long id;
private Integer summa;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getSumma() {
return discount;
}
public void getSumma(Integer summa) {
this.summa = summa;
}
}

Тепер необхідно створити інтерфейс мапера PaymentMapper, він буде простою. Тільки один метод отримання списку платежів по id користувача.

package kz.jazzsoft.mapper;
import kz.jazzsoft.dal.Payment;
import java.util.List;
public interface PaymentMapper {
List<Payment> getPaymentsByIdSub(id Integer);
}

Необхідно створити xml мапер:

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="kz.jazzsoft.mapper.PaymentMapper">
<resultMap id="payment" type="kz.jazzsoft.dal.Payment">
<id property="id" column="id"/>
<result property="summa" column="date"/>
</resultMap>
<select id="getPaymentsByIdSub" resultMap="payment" parameterType="java.lang.Integer">
select * from payment where ref_subscriber = #{id}
</select>
</mapper>
Зв'яжемо список платежів (payment) з абонентом (Subscriber):

<source lang="xml">
<resultMap id="subscriber" type="kz.jazzsoft.dal.Subscriber">
<id property="id" column="id"/>
<result property="name" column="name"/>
<property association="tariff" column="ref_tariff"
javaType="kz.jazzsoft.dal.Tariff"
select="kz.jazzsoft.mapper.TariffMapper.getTariffById" fetchType="eager"/>
<collection 
property="payments" <!--властивість в bean Subscriber-->
column="id" <!--id Subscriber--> 
javaType="List" <!--тип що отримаємо на виході(список)-->
ofType="Payment" <!--а цим типом буде заповнений список-->
select="kz.jazzsoft.mapper.PaymentMapper.getPaymentsByIdSub" <!-- метод, який необхідно виконати, щоб отримати список платежів по id Абонента-->
fetchType="eager" <!--тип запиту--> 
/>
</resultMap>

Отриманий resultMap замінюємо в SubscriberMapper і можна переглянути всі платежі користувача. Але на цьому все найцікавіше тільки починається.

MyBatis має функціонал, який дозволяє формувати запити sql динамічно в залежності від параметрів, які були в нього передані. Наприклад, нам немає необхідності створювати купу sql на кожну дію(вибірки з однієї таблиці, але за різними параметрами), можна обійтися одним методом, який буде фільтрувати тих же абонентів по декількох колонок або взагалі не буде фільтрувати і поверне всіх в залежності від вхідних даних, але про все по порядку.

Для динамічного формування SQL запитів в арсеналі MyBatis є досить компонентів для вирішення більшості завдань. Розглядати всі ми не будемо, так як їх досить багато і їх можна комбінувати і тп. Для прикладу розглянемо IF оператор, більше інформації можна прочитати в офіційному керівництві: mybatis.github.io/mybatis-3/dynamic-sql.html

IF Оператор:

<select id="getSubscribersWithParam" parameterType="map">
select * from subscriber where (1=1)
<if test="descr != null" >
and decr = #{descr}
</i>
</select>

У запиті наведеному вище виконується перевірка, на те, що об'єкт в Map по ключу descr не null, тоді в запит буде додана рядок в блоці if і таких блоків може бути скільки завгодно, вони можу бути вкладеними.

<update id="updateSubscriber" parameterType="kz.jazzsoft.dal.Subscriber">
udpate subscriber
<set>
<if test="descr != null">
descr = #{descr},
</i>
</set>
where id = #{id}
</update>


MyBatis при розумному використанні може дати відчутний приріст у швидкості роботи програми. Може здатися страшно писати самому запити і правила мапинга, але це тільки здається, Hibirnate теж не так вже простий.

Не існує одного універсального рішення, яке підійшло б усім, в кожному виборі потрібен чіткий розрахунок. MyBatis можна використовувати спільно з Hibernate там де це дійсно потрібно, а це зможете визначити тільки ви.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.