В 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 ,
`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 там где это действительно нужно, а это сможете определить только вы.