В Java сообществе Hibernate framework де-факто считается стандартом для удобной работы с базой данных. Разработчику трудно выбрать другой фреймфорк, потому что порой он не знает о существовании альтернатив. В этой статье я проведу курс молодого бойца по работе с MyBatis framework. Полностью охватить весь framework не получится, но информации будет достаточно, что бы увидеть преимущества и слабые стороны данного framework'а и начать работать с MyBatis.
MyBatis не реализует JPA спеки, а является альтернативой JPA. Основное отличие MyBatis от Hibernate — это то как производится мапинг объектов. Hibernate мапит таблицы БД на сущности, давая нам доступ к данным. Для получения данных Hibernate генерирует SQL запросы, а генерируемые запросы хороши до поры — до времени, а потом они съедают кучу времени, становятся громоздкими и не управляемыми. MyBatis мапится не на таблицы, а на SQL запросы, за формирование запросов отвечает разработчик и только от него будет зависеть как быстро будет работать приложение.
С преамбулой закончили, теперь можно перейти непосредственно к созданию небольшого проекта с использованием MyBatis, что бы познакомиться с ним поближе. Не буду оригинальным, сделаем пару запросов к БД с использованием MyBatis. В примере буду использовать СУБД MySQL, а вы можете использовать любую другую СУБД, которая вам по душе.
Создадим БД mybatis:
Создадим таблицы subscriber, tariff, payments:
subscriber:
tariff:
payments:
Самое скучное позади — у нас есть БД, из которой мы будем получать данные, теперь приступим непосредственно к работе с MyBatis. Для начала нам необходима библиотека MyBatis. Для получения библиотеки мы будем использовать maven, необходимо добавить зависимость в настройки проекта(pom.xml):
На момент написания статьи последняя версия MyBatis 3.2.8
После того как библиотека успешно загрузилась, необходимо настроить подключение к БД. Настройки осуществляются в конфигурационном файле mybatis-config.xml.
Ниже приведен листинг конфигурационного файла:
В листинге выше я указал 3 мапера — все взаимодействие с БД будет осуществляться через маперы и чем детальнее вы будете понимать как работать с маперами и формировать запросы, тем более производительней будут ваши приложения.
Для корректной работы с MyBatis необходимо создать интерфейс мапера в котором будут предопределены методы, которые будут использоваться и xml файл настроек в котором будут описаны sql запросы, правила их мапинга на объекты и тп.
Создадим интерфейс kz.jazzsoft.mapper.SubscriberMapper:
В данном интерфейсе мы определили два метода:
1. getSubscriberById — вернет одного пользователя по id;
2. getSubscriber — вернет список пользователей;
Но что бы данные методы заработали необходимо создать xml маппер с sql запросами.
Я упустил еще один момент, который необходимо было сделать — это создать классы beanEntity, на которые мы будем мапить результаты выполнения запросов.
Subscriber:
Tariff:
Payment:
Можно сразу посмотреть, как работает код, для этого необходимо подключиться к БД и инициализировать нужный мапер, а пока он у нас один (SubscriberMapper). Создадим класс, в котором будем работать:
Work:
Запросы выполнились и у нас есть объекты, с которыми мы можем работать. Вы можете посмотреть, что у объектов есть id и другие поля заполнены, но не все. Тут есть один нюанс, если колонка в БД имеет такое же имя как переменная, то она автоматически смапиться на нее. Что бы расширить возможности мапинга и создавать сложные структуры в арсенале MyBatis есть тег ResultMap, который позволяет настраивать произвольный мапинг. Делать связи one-to-one и one-to-many.
ResultMap представляет из себя описание правил связи полей EntityBean с колонками из таблиц. Пример для Subscriber:
В итоге маппер для Subscriber будет выглядеть следующим образом:
Связь one-to-one осуществляется не сложнее примера выше. Но нам сначала необходимо будет описать следующий мапер Tariff. Через него мы будем получать данные для связанного поля в Subscriber.
Создаем сущность Tariff:
Создаем интерфейс мапера TariffMapper, нам понадобится только один метод:
Создаем мапер:
Теперь можно добавить Subscriber c Tariff в SubscriberMaper, в resultMap необходимо добавить правило связи:
Необходимо заменить данный resultMap и можно будет узнать на каком тарифном(Tariff) плане у нас находится Абонент(Subscriber)
Subscriber.gettariff().getDescr();
Добавим к Абоненту(Subscriber) список его платежей(Payments)(one-to-many):
Для начала необходимо создать EntityBean Payment:
Теперь необходимо создать интерфейс мапера PaymentMapper, он будет простой. Только один метод получения списка платежей по id пользователя.
Необходимо создать xml мапер:
Полученный resultMap заменяем в SubscriberMapper и можно посмотреть все платежи пользователя. Но на этом все самое интересное только начинается.
MyBatis имеет функционал, который позволяет формировать sql запросы динамически в зависимости от параметров, которые были в него переданы. Например нам нет необходимости создавать кучу sql на каждое действие(выборки из одной таблицы, но по разным параметрам), можно отделаться одним методом, кот��рый будет фильтровать тех же абонентов по нескольким колонкам или вообще не будет фильтровать и вернет всех в зависимости от входных данных, но обо всем по порядку.
Для динамического формирования SQL запросов в арсенале MyBatis имеется достаточно компонентов для решения большинства задач. Рассматривать все мы не будем, так как их достаточно много и их можно комбинировать и тп. Для примера расмотрим IF оператор, больше информации можно прочитать в официальном руководстве: mybatis.github.io/mybatis-3/dynamic-sql.html
IF Оператор:
В запросе приведенном выше выполняется проверка, на то что объект в Map по ключу descr не null, тогда в запрос будет добавлена строка в блоке if и таких блоков может быть сколько угодно, они могу быть вложенными.
MyBatis при разумном использовании может дать ощутимый прирост в скорости работы приложения. Может показаться страшно писать самому запросы и правила мапинга, но это только кажется, Hibirnate тоже не так уж прост.
Не существует одного универсального решения, которое подошло бы всем, в каждом выборе нужен четкий расчет. MyBatis можно использовать совместно с Hibernate там где это действительно нужно, а это сможете определить только вы.
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 ,
`summa` 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(Integer id);
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(Integer id);
}
Создаем мапер:
<!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(Integer id);
}
Необходимо создать 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"/>
<association property="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}
</if>
</select>
В запросе приведенном выше выполняется проверка, на то что объект в Map по ключу descr не null, тогда в запрос будет добавлена строка в блоке if и таких блоков может быть сколько угодно, они могу быть вложенными.
<update id="updateSubscriber" parameterType="kz.jazzsoft.dal.Subscriber">
udpate subscriber
<set>
<if test="descr != null">
descr = #{descr},
</if>
</set>
where id = #{id}
</update>
MyBatis при разумном использовании может дать ощутимый прирост в скорости работы приложения. Может показаться страшно писать самому запросы и правила мапинга, но это только кажется, Hibirnate тоже не так уж прост.
Не существует одного универсального решения, которое подошло бы всем, в каждом выборе нужен четкий расчет. MyBatis можно использовать совместно с Hibernate там где это действительно нужно, а это сможете определить только вы.
