Modern technology gives us many things.

MyBatis «на минималках»

92

Уровень сложности Простой Время на прочтение 11 мин Количество просмотров 2.5K Блог компании CDEK Программирование *Java *Doctrine ORM * Туториал

MyBatis «на минималках»

Привет, Хабр! Меня зовут Пётр Гусаров, я Java‑программист в CDEK. В этой статье расскажу про не очень распространённый фреймворк MyBatis.

Почему MyBatis? Потому что мы в CDEK используем его в большинстве проектов, и в деле он весьма неплохо себя показал. Немного сложен и непривычен на этапе входа, но все эти минусы перекрываются его гибкостью. «Да есть Hibernate, Jooq, JDBC и еще что‑то», — скажут бывалые. Есть, но в данной статье речь пойдёт о MyBatis.

Статья будет полезна новичкам, которые хотели попробовать данный фреймворк или попробовали, но что‑то не получилось.

Содержание

  • Что мы сделаем

  • Почему MyBatis

  • Запустим птичку

  • Стартуем на малых оборотах

  • Посмотрим, как это всё работает

  • Занавес

Что мы сделаем

  • Посмотрим на плюсы и минусы данного фреймворка

  • Развернём MyBatis на основе Spring Boot (так шустрее)

  • Напишем и запустим пару‑тройку запросов

  • Посмотрим, в каких случаях применять MyBatis лучше, чем другие фреймворки.

Почему MyBatis

У меня две новости: хорошая и плохая. С какой начать? Хорошо, начнём с плохой, точнее — с недостатков:

  • все запросы придётся писать на нативном SQL в XML-файлах. Стойте, не уходите! Не все так страшно, как звучит 🙂

MyBatis «на минималках»

А теперь послушай, птичка: ты будешь писать SQL на XML (кадр из м/ф «Крылья, ноги и хвосты»)

Теперь к достоинствам:

  • полный контроль над запросами к БД;

  • намного легче накладывать логику на legacy‑схемы БД (просто мапим запросы на сущности, остальное делает «птичка»);

  • при развитии и усложнении продукта вы потратите меньше времени на оптимизацию запросов, чем в других фреймворках;

  • скорость обработки данных выше. Но здесь есть нюансы: за формирование запросов отвечает разработчик, и только от него зависит, как быстро будет работать обмен данными между приложением и БД;

  • не нужны знания о дополнительных состояниях вашего объекта, как в Hibernate‑е.

Итак, начнём. Что многие обычно делают, когда начинают изучать новый фреймворк? Открывают официальную документацию подключают его и начинают «тыкать» с разных сторон. В крайнем случае пробуют найти готовый примерчик в сети. Фреймворк считается успешным, если получается поднять его, добавив зависимость и пару‑тройку аннотаций, и всё работает. С MyBatis немного по‑другому — здесь подходит фраза из мультика: «лучше день потерять, потом за 5 минут долететь».

MyBatis «на минималках»

*в рамках данной статьи мы затронем только мапперы

Запустим птичку

Для самых нетерпеливых ссылка на пример лежит здесь.

Создадим проект на основе Spring Boot. Не буду описывать подробности, вы и так знаете, как это делается. Кто не в курсе — вам сюда. Добавьте такие зависимости как mybatis, h2, lombok. Или просто возьмите их из этого pom‑файла:

pom.xml<?xml version=»1.0″ encoding=»UTF-8″?> <project xmlns=»http://maven.apache.org/POM/4.0.0″ xmlns:xsi=»http://www.w3.org/2001/XMLSchema-instance» xsi:schemaLocation=»http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd»> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.1.3</version> <relativePath/> </parent> <groupId>ru.gpm.example</groupId> <artifactId>mybatis-minimum</artifactId> <version>0.0.1-SNAPSHOT</version> <name>mybatis-minimum</name> <description>Demo MyBatis Spring Boot</description> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.2</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter-test</artifactId> <version>3.0.2</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>

Читать на TechLife:  Опять починяем банкоматы

Можем уже запустить проект. Он запустится, но делать ничего не будет. Это самая лучшая программа: ничего не делает, ничего не ломает. Но нас не так учили: нам нужен движ, печеньки и зарплата 🙂

Стартуем на малых оборотах

Мы не будем писать «hello world», а напишем более приближенный к реальности проект, потому что именно такой подход, по моему мнению, покажет этот инструмент в деле. Сделаем сервис по управлению складом и остатками товара на нём.

Настроим MyBatis (здесь будет немного XML). Рядом с application.yml положим файл 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> <typeAliases> <package name=»ru.gpm.example.mybatis.min.domain»/> </typeAliases> </configuration>

Здесь мы показали «птичке», где у нас будут лежать объекты, отображающие данные из БД.

Создадим файл schema.sql и положим его в корень ресурсов приложения. Это будет скелет нашей высоконагруженной БД — как мы любим.

schema.sqlset mode MySQL; CREATE TABLE IF NOT EXISTS product ( id integer NOT NULL auto_increment, name varchar, sku varchar, PRIMARY KEY (id) ); CREATE TABLE IF NOT EXISTS warehouse ( id integer NOT NULL auto_increment, name varchar, PRIMARY KEY (id) ); CREATE TABLE IF NOT EXISTS stock ( product_id integer, warehouse_id integer, qty integer, PRIMARY KEY (product_id, warehouse_id) )

mode MySql нужен для поддержки базой H2 некоторых удобных функций при обновлении данных upsert.

Там же создадим папку с названием mappers, в нее мы будем класть наши файлы с запросами к базе. Теперь скажем Spring’у, где у нас этот XML‑файл лежит и откуда брать запросы к БД. Для этого в application.yml напишем следующее:

mybatis: config-location: classpath:mybatis-config.xml mapper-locations: classpath*:mappers/*.xml spring.sql.init.mode: always

  • config‑location — показывает, где настройки MyBatis;

  • mapper‑locations — где «птичка» будет брать запросы;

  • spring.sql.init.mode — указывает Spring’у, когда инициировать скрипт schema.sql (в нашем случае — каждый раз при запуске).

Закончили с настройками, приступим к написанию кода. Создадим 3 класса в пакете domain: товар, склад и остатки.

@Data @Accessors(chain = true) public class Product { private Integer id; private String name; private String sku; } @Data @Accessors(chain = true) public class Stock { private Product product; private Warehouse warehouse; private int count; } @Data @Accessors(chain = true) public class Warehouse { private Integer id; private String name; }

Далее напишем сами запросы, репозитории и сервисы в привычном нам стиле. Начнём с репозиториев. Точнее, здесь они называются «мапперы».

По‑хорошему, лучше использовать репозитории с DI мапперов в них, так как это создает правильный слой данных и даст возможность управлять форматом данных в репозитории. Но у нас в рамках примера таких сложностей не ожидается.

@Mapper public interface ProductRepository { void save(Product product); List<Product> findAll(); } @Mapper public interface WarehouseRepository { void save(Warehouse warehouse); Warehouse findOne(int id); } @Mapper public interface StockRepository { List<Stock> findStockByWarehouse(Warehouse warehouse); void save(Stock stock); }

Читать на TechLife:  Деловая репутация? Не, в этом году не

Почти всё готово, осталось написать запросы SQL. Да‑да, запросы на старом добром SQL ).

mappers/product-mapper.xml<?xml version=’1.0′ encoding=’UTF-8′ ?> <!DOCTYPE mapper PUBLIC ‘-//mybatis.org//DTD Mapper 3.0//EN’ ‘http://mybatis.org/dtd/mybatis-3-mapper.dtd’> <mapper namespace=»ru.gpm.example.mybatis.min.repository.ProductRepository»> <insert id=»save» keyProperty=»id» useGeneratedKeys=»true»> INSERT INTO product (name, sku) VALUES (#{name}, #{sku}); </insert> <select id=»findAll» resultMap=»ProductMap»> SELECT id, name, sku FROM product; </select> <resultMap id=»ProductMap» type=»Product» autoMapping=»true»/> </mapper>

mappers/warehouse-mapper.xml<?xml version=’1.0′ encoding=’UTF-8′ ?> <!DOCTYPE mapper PUBLIC ‘-//mybatis.org//DTD Mapper 3.0//EN’ ‘http://mybatis.org/dtd/mybatis-3-mapper.dtd’> <mapper namespace=»ru.gpm.example.mybatis.min.repository.WarehouseRepository»> <insert id=»save» useGeneratedKeys=»true» keyProperty=»id»> <if test=»id == null»> INSERT INTO warehouse (name) values (#{name}); </if> <if test=»id != null»> UPDATE warehouse SET name=#{name} where id=#{id}; </if> </insert> <select id=»findOne» resultMap=»WarehouseMap»> SELECT id, name FROM warehouse WHERE id = #{id}; </select> <resultMap id=»WarehouseMap» type=»Warehouse» autoMapping=»true»/> </mapper>

Обратите внимание: id блоков в XML‑схеме полностью идентичны наименованию методов в интерфейсах репозиториев. Так запросы в XML автоматически линкуются с Java‑интерфейсами репозиториев. Есть и другие варианты, но усложняться не будем.

Здесь мы задействовали такие элементы как insert select и resultMap. Немного остановимся на них:

  • insert — выполняет вставку в БД.
    id = «…» — это id statement’а для соответствия репозиторию, который будет мапиться на этот запрос;
    useGeneratedKeys=»true» говорит о том, что запрос генерирует значение ключа;
    keyProperty=»id» сообщает «птичке», какое свойство у класса‑модели отвечает за ID и устанавливает его в значение в объекте после сохранения.

  • select — выполняет чтение данных из базы.
    resultMap = «StockMap» используется в блоке <select> и сообщает «птичке», какой маппер использовать для выгрузки данных в объект.

  • resultMap — собственно, сам маппер. Здесь описываются правила маппинга результата запроса (описанного в блоке <select>) на доменный объект. Вариантов множество. В рамках данной статьи все рассматривать не будем. Остановимся на основных.
    autoMapping=»true» объявляет «птичке»: «Cделай все сама». Но это работает, когда у класса и алиаса в ответе однотипные именования полей, иначе придется описывать правила маппинга.

На следующем маппере (mappers/stock‑mapper.xml) остановимся подробнее.

<?xml version=’1.0′ encoding=’UTF-8′ ?> <!DOCTYPE mapper PUBLIC ‘-//mybatis.org//DTD Mapper 3.0//EN’ ‘http://mybatis.org/dtd/mybatis-3-mapper.dtd’> <!—suppress ALL —> <mapper namespace=»ru.gpm.example.mybatis.min.repository.StockRepository»> <insert id=»save»> INSERT INTO stock (product_id, warehouse_id, qty) VALUES (#{product.id}, #{warehouse.id}, #{count}) ON DUPLICATE KEY UPDATE qty = #{count} </insert> <sql id=»stock-select-request»> SELECT p.id AS p_id, p.name AS p_name, p.sku AS p_sku, w.id AS w_id, w.name AS w_name, s.qty FROM stock s LEFT JOIN product p ON s.product_id = p.id LEFT JOIN warehouse w ON s.warehouse_id = w.id </sql> <select id=»findStockByWarehouse» resultMap=»StockMap» parameterType=»Warehouse»> <include refid=»stock-select-request»/> WHERE w.id = #{id} </select> <select id=»findStockByWarehouseAndProduct» resultMap=»StockMap»> <include refid=»stock-select-request»/> WHERE w.id = #{warehouseId} AND p.id = #{productId} </select> <resultMap id=»StockMap» type=»Stock»> <result property=»count» column=»qty»/> <association property=»product» columnPrefix=»p_» javaType=»Product»> <result property=»id» column=»id»/> <result property=»name» column=»name»/> <result property=»sku» column=»sku»/> </association> <association property=»warehouse» columnPrefix=»w_» javaType=»Warehouse»> <result property=»id» column=»id»/> <result property=»name» column=»name»/> </association> </resultMap> </mapper>

Читать на TechLife:  Бюджетный смартфон Moto G Play 2024 представлен официально

Рассмотрим новые элементы:

  • sql — шаблонная конструкция запроса, которая может неоднократно использоваться.
    id = «…» — это id шаблона.

  • include refid = «…» — собственно, сама вставка шаблона. Она применяется здесь в двух запросах с разными условиями where.

  • parameterType=»Warehouse» сообщает «птичке», какой класс объекта передается в параметрах запроса.

  • resultMap — более развернутый маппер. Здесь видно, как алиасы ответа накладываются на вложенные объекты.
    result property = «count» column = «qty» — настраивает отношение свойств класса и наименования полей ответа;
    association — настраивает отношение вложенных объектов в класс. Таким образом, мы реализуем отношение one‑to‑one. Где property указывает свойство класса вложенного объекта, а columnPrefix — это своеобразный фильтр алиасов в ответе для данного объекта.

MyBatis «на минималках»

Ну, вот. Теперь мы снова олдскульные ребята и девчата! (кадр из м/ф «Крылья, ноги и хвосты»)

И самое сердце нашего приложения — сервисы. Чтобы не грузить логикой этот пример, напишем один сервис для получения остатков товаров на складах. Сохранение сделаем в тестах напрямую через репозитории.

@Service @RequiredArgsConstructor public class StockService { private final StockRepository repository; public Stock save(Stock stock) { repository.save(stock); return stock; } public List<Stock> getAllByWarehouse(Warehouse warehouse) { return repository.findStockByWarehouse(warehouse); } public Stock getBy(Warehouse warehouse, Product product) { return repository.findStockByWarehouseAndProduct(warehouse.getId(), product.getId()); } }

Вот и всё — можем запускать наш проект и ловить баги наслаждаться жизнью. Не будем дергать это всё великолепие REST‑контроллерами, а просто напишем тест, который покажет, как это всё работает.

Посмотрим, как это всё работает

MyBatis «на минималках»

кадр из м/ф «Крылья, ноги и хвосты»@Slf4j @SpringBootTest class MyBatisApplicationTest { @Autowired private StockService stockService; @Autowired private ProductRepository productRepository; @Autowired private WarehouseRepository warehouseRepository; @Test void init() { // Добавим товары в БД productRepository.save(new Product().setName(«name-1»).setSku(«sku-1»)); productRepository.save(new Product().setName(«name-2»).setSku(«sku-2»)); final List<Product> all = productRepository.findAll(); Assertions.assertEquals(2, all.size()); // Добавим склад. final Warehouse warehouse = new Warehouse().setName(«склад-1»); warehouseRepository.save(warehouse); Assertions.assertNotNull(warehouseRepository.findOne(warehouse.getId())); // Сохраним остатки по товарам на складе final Stock stock1 = new Stock().setProduct(all.get(0)).setWarehouse(warehouse).setCount(10); final Stock stock2 = new Stock().setProduct(all.get(1)).setWarehouse(warehouse).setCount(50); stockService.save(stock1); stockService.save(stock2); // Получим текущие остатки на складе List<Stock> allByWarehouse = stockService.getAllByWarehouse(warehouse); Assertions.assertEquals(2, allByWarehouse.size()); log.info(«{}», allByWarehouse); // Поменяем остаток товара stockService.save(stock1.setCount(20)); allByWarehouse = stockService.getAllByWarehouse(warehouse); Assertions.assertEquals(2, allByWarehouse.size()); final Stock stockEdit = stockService.getBy(warehouse, stock1.getProduct()); Assertions.assertNotNull(stockEdit); Assertions.assertEquals(20, stockEdit.getCount()); log.info(«{}», allByWarehouse); } }

Занавес

Думаю, достаточно для первого знакомства с «птичкой». Подозреваю, многие скажут, что неудобно всё это ручками писать, если есть фреймворки, делающие всё за тебя. Отчасти вы будете правы: они очень удобны, чтобы быстренько написать стандартное приложение. Но когда приходится подключать эти «суперавтоматы» к legacy‑базам или требуется оптимизация запросов в связи с возросшим размером данных, то MyBatis реально выручает.
Возможно, кому‑то данная статья поможет в выборе технологии, а у кого‑то я просто украл время.

Всегда готов к конструктивной критике, так как она поднимает знания и опыт критикуемого 🙂

Ссылки: GitHub примера, Мультфильм: «Крылья, ноги и хвосты» (скрины и цитаты).

Теги:

  • mybatis
  • junior
  • orm
  • orm всегда медленный

Хабы:

  • Блог компании CDEK
  • Программирование
  • Java
  • Doctrine ORM

Источник

Каталог товаров с купонами и промокодами онлайн

Оставьте ответ

Ваш электронный адрес не будет опубликован.

©Купоно-Мания.ру