Одна из основных трудностей, с которыми я сталкиваюсь с Мулом, когда я работаю над интеграционными проектами, — это, пожалуй, одна из самых простых вещей, которую можно сделать с Мулом; интегрировать с базами данных. Базы данных составляют большую часть любой серверной системы, и большинство проектов, использующих Mule в качестве ESB, будут использовать базу данных в части системы.
Когда мы говорим о базах данных SQL, почти предполагается, что некоторые операции должны быть связаны в транзакции. Например, представьте, что у нас есть две таблицы — одна представляет пользователей, а вторая представляет адреса электронной почты — где у одного пользователя может быть много адресов электронной почты, но адреса электронной почты уникальны.
Очень распространенный случай использования — когда Mule должен предложить интерфейс REST для создания пользователей. Итак, в основном нам нужен поток с HTTP-входом и двумя конечными точками JDBC-выхода, чтобы мы могли выполнять вставки, одну для пользователя и одну (заключенную в цикл for) для адресов электронной почты.
В идеале эти операции выполняются в транзакции. Зачем? Так что, если адрес электронной почты уже существует, транзакция откатывается. Кто-то может возразить, что мы могли бы выполнить проверку заранее, но это все равно не сработало бы, потому что мы все знаем, что такой обходной путь далеко не близок к поточной безопасности. И здесь Mule не хватает версий ранее 3.4.0.
Также здесь есть еще одна проблема. Когда мы вставляем пользователя, если мы используем автоматически сгенерированные идентификаторы, нам как-то нужно извлечь сгенерированный ключ, чтобы иметь возможность использовать его в качестве внешнего ключа в адресе. Ну, как бы глупо это ни было, в Mule вам нужно сделать еще один оператор select, чтобы получить ключ самостоятельно. В 2012 году я написал патч для автоматического получения сгенерированных ключей: http://www.mulesoft.org/jira/browse/MULE-6306 . Он был возвращен сообществу, но никогда не включался в транспорт Mule JDBC. Это верно даже для Mule 3.4.0.
До версии 3.4.0 транзакция могла быть запущена только на входящей конечной точке. Это сделало описанный выше случай использования, смехотворно сложным для реализации с использованием только готовых функций.
На мой взгляд, есть два варианта. Первый разделит поток на 2. Начните с получения сообщения в конечной точке HTTP и используйте очередь VM или JMS для запуска транзакции XA во втором потоке. А если серьезно, то здесь речь идет о транзакции XA, чтобы иметь возможность реализовать простой вариант использования. С архитектурной точки зрения это совершенно ****.
Опция, позволяющая избежать транзакции XA, заключалась бы в том, чтобы сначала записать данные во временную таблицу, а затем создать второй поток, который считывает временные данные и в транзакции вставляет данные на свое место. Опять же, это далеко от оптимального решения.
Гораздо более чистым решением было бы отказаться от поддержки Mule для транзакций и обработать ее самостоятельно, используя простой JDBC, или что-то вроде поддержки Spring JDBC http://static.springsource.org/spring/docs/3.0.x/reference/jdbc.html. или JPA. Но разве это не победит основную цель нашего ESB? Конечно, это будет! Так как насчет интеграции других фреймворков в Mule, таких как JPA?
Это еще один вариант. Модуль для JPA был написан для Mule: https://github.com/mulesoft/mule-module-jpa . К сожалению, этот модуль так и не попал в основной продукт и никогда не поставлялся из коробки с Mule (что грустно, учитывая тот факт, что JPA долгое время был стандартом). Также это не дает нам гибкости транзакций, которые мы ищем.
Еще одна область, в которой Mule не может обеспечить оптимальное решение, — это динамический SQL. Например, в нашем случае использования мы хотим предоставить интерфейс REST, который предоставляет функции для поиска пользователей по некоторым полям, таким как электронная почта, имя и фамилия. Пользователь имеет право вводить все комбинации параметров. Как бы вы реализовали это с Мулом? Если вы начнете думать об этом, он очень быстро станет волосатым. Если вы не реализуете собственное решение, вам придется отбрасывать именованные запросы, что означает, что теперь у вас есть новая проблема, внедрение SQL.
Итак, мы сделали что-то, чтобы помочь здесь? Да, мы сделали. Было бы неплохо разглагольствовать и не действовать, правда? Мне очень нравится идея JPA, особенно тот факт, что вам не нужно реализовывать преобразователи для преобразования ваших данных в POJO. Однако мне не очень нравится тот факт, что большинство платформ JPA громоздки. Хотя я должен сказать, что функция, которую код пишется один раз и запускается в любой базе данных, является убийцей. Мне также не нравится тот факт, что вы не имеете 100% контроля над SQL, который создает среда. Поэтому я влюбился в MyBatis. Я не собираюсь давать подробное объяснение того, что может делать MyBatis, потому что вы можете прочитать об этом здесь; http://mybatis.github.io, Для начала у него есть функция «из коробки» для динамического SQL. Это очень легко, и мне также нравится тот факт, что MyBatis имеет встроенную поддержку Spring, все это описано здесь; http://mybatis.github.io/spring . Более того, MyBatis отлично справляется, когда нам нужно интегрироваться с устаревшими базами данных, где у нас нет контроля над таблицами, именами, связями и т. Д.
Так что это выглядит как очень хорошее начало. Что если бы существовал простой способ использовать MyBatis в Mule? Ну, теперь есть! Я рад представить вам новый модуль MyBatis для Mule. Он все еще находится на ранней стадии разработки, однако базовая функциональность уже есть. И когда я говорю «базовый», я должен квалифицировать это, говоря, что он, по крайней мере, включает в себя лекарство от всех недостатков, упомянутых в начале этого сообщения в блоге…
Итак, давайте начнем с того, что покажем, как легко реализовать упомянутый вариант использования. Давайте пока проигнорируем транзакции и сконцентрируемся на интеграции базы данных. Также в целях простоты я собираюсь игнорировать часть REST, включая преобразование в объекты Java, JSON или XML…
Допустим, у нас есть следующие POJO:
public class Address { private Integer id; private String address; private Integer personId; //getters and setters here } public class Person { private Integer id; private String name; private String surname; private Integer age; private List<Address> addresses; //getters and setters here }
И следующий SQL DDL:
CREATE TABLE person ( id int auto_increment PRIMARY KEY, name varchar(255), surname varchar(255), age int ); CREATE TABLE address ( id int auto_increment PRIMARY KEY, address varchar(255), personId int ); ALTER TABLE address ADD CONSTRAINT address_person_fk FOREIGN KEY (personId) REFERENCES person (id);
Первые предметы, которые нам нужны — это картографы MyBatis. К ним относятся операторы SQL и преобразователи для набора результатов в преобразование POJO (большая часть преобразования выполняется автоматически). Это все MyBatis здесь:
<?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="org.mybatis.sql.AddressMapper"> <select id="selectAddress" parameterType="int" resultType="Address"> SELECT * FROM address WHERE id = #{id} </select> <select id="selectAddressesByPersonId" parameterType="int" resultType="Address"> SELECT * FROM address WHERE personId = #{id} </select> <insert id="insertAddress" parameterType="Address" useGeneratedKeys="true" keyProperty="id"> INSERT INTO address (address, personId) VALUES (#{address}, #{personId}) </insert> <update id="updateAddress" parameterType="Address"> UPDATE address SET address=#{address} WHERE id=#{id} </update> </mapper>
<?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="org.mybatis.sql.PersonMapper"> <resultMap id="personResultMap" type="Person"> <id property="id" column="id" /> <collection property="addresses" column="id" ofType="Address" select="org.mybatis.sql.AddressMapper.selectAddressesByPersonId"/> </resultMap> <select id="selectPerson" parameterType="int" resultMap="personResultMap"> SELECT * FROM person WHERE id = #{id} </select> <insert id="insertPerson" parameterType="Person" useGeneratedKeys="true" keyProperty="id"> INSERT INTO person (name, surname, age) VALUES (#{name}, #{surname}, #{age}) </insert> <update id="updatePerson" parameterType="Person"> UPDATE person SET name=#{name}, surname=#{surname}, age=#{age} WHERE id=#{id} </update> </mapper>
С этой конфигурацией теперь нам не нужны преобразователи Mule, MyBatis будет возвращать заполненный POJO или список POJO, где это необходимо.
Также из MyBatis 2.x была представлена новая функция, позволяющая связывать эти сопоставители с интерфейсами для лучшей обработки типов. Для завершения мы включаем интерфейсы здесь:
public interface AddressMapper { public Address selectAddress(Integer id); public List<Address> selectAddressesByPersonId(Integer id); public void insertAddress(Address person); public void updateAddress(Address person); } public interface PersonMapper { public Person selectPerson(Integer id); public void insertPerson(Person person); public void updatePerson(Person person); public void deletePerson(Integer id); }
MyBatis 2.x также поддерживает использование аннотаций непосредственно на интерфейсе для устранения картостроителей и получения более чистого решения. Хотя это поддерживается нашим модулем, мы не собираемся рассказывать об этом в этом блоге.
Когда у нас есть готовые средства отображения и интерфейсы, нам нужна конфигурация MyBatis.
<?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> <settings> <setting name="useGeneratedKeys" value="true" /> </settings> <typeAliases> <typeAlias alias="Person" type="org.mybatis.domain.Person" /> <typeAlias alias="Address" type="org.mybatis.domain.Address" /> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="org.h2.Driver" /> <property name="url" value="jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1" /> <property name="username" value="root" /> <property name="password" value="" /> </dataSource> </environment> </environments> <mappers> <mapper resource="org/mybatis/sql/person-mapper.xml" /> <mapper resource="org/mybatis/sql/address-mapper.xml" /> </mappers> </configuration>
Что все это говорит? Он начинается с установки некоторых параметров MyBatis, таких как включение использования сгенерированных ключей. Затем мы определяем псевдонимы типов, поэтому, где бы вы ни видели Person в средствах отображения, это на самом деле означает «org.mybatis.domain.Person». В следующем разделе определяется наш источник данных, а в последнем разделе указывается, что MyBatis должны были найти файлы конфигурации mapper. Для получения более подробной информации о конфигурации MyBatis, пожалуйста, посетите http://mybatis.github.io/mybatis-3/configuration.html .
Теперь, когда мы завершили всю конфигурацию MyBatis, давайте перейдем к нашим потокам Mule. Идея состоит в том, что входом в этот поток является POJO типа Person. Сначала мы вставляем объект Person, и MyBatis автоматически заполняет новый сгенерированный ключ в поле id.
Затем мы зацикливаемся на адресах, сначала устанавливаем внешний ключ, затем сохраняем в базе данных. Это все, что нужно сделать:
<mybatis:config name="mybatis" configFile="mybatis-config.xml"/> <sub-flow name="CreateUser"> <set-variable variableName="person" value="#[payload]" /> <mybatis:execute mapper="org.mybatis.sql.PersonMapper" method="insertPerson" /> <foreach collection="#[flowVars.person.addresses]"> <expression-component> payload.personId = flowVars.person.id; </expression-component> <mybatis:execute mapper="org.mybatis.sql.AddressMapper" method="insertAddress" /> </foreach> </sub-flow>
Это так просто!
Теперь о крутых вещах! Как мы можем заключить все эти операции в транзакцию? Чрезвычайно легко! Модуль предоставляет 3 процессора, чтобы помочь с транзакциями:
<mybatis:begin-transaction /> <mybatis:commit-transaction /> <mybatis:rollback-transaction />
Вот как мы могли бы использовать вышеупомянутое в нашем новом потоке:
<flow name="CreateUserTransacted"> <mybatis:begin-transaction /> <flow-ref name=”CreateUser” /> <mybatis:commit-transaction /> <rollback-exception-strategy> <mybatis:rollback-transaction /> </rollback-exception-strategy> </flow>
Это оно! MyBatis позаботится обо всем, включая управление транзакциями. Если все будет хорошо, сделка будет совершена. Если выдается исключение, например «Исключение с дублированным ключом», вся транзакция будет откатываться.
Для тех, кто использует MyBatis по старому стилю (только с мапперами и без интерфейсов), вы все равно можете использовать самые распространенные функции MyBatis, такие как:
<mybatis:select-one statement="org.mybastis.sql.PersonMapper.selectOne"/> <mybatis:select-list statement="org.mybastis.sql.PersonMapper.selectList"/> <mybatis:select-map statement="org.mybastis.sql.PersonMapper.selectMap" mapKey="id"/> <mybatis:update statement="org.mybastis.sql.PersonMapper.update"/> <mybatis:insert statement="org.mybastis.sql.PersonMapper.selectOne"/> <mybatis:delete statement="org.mybastis.sql.PersonMapper.selectOne"/>
Также имеется поддержка модуля MyBatis Spring, что означает, что некоторую конфигурацию MyBatis можно перенести в Spring, а не в файл конфигурации MyBatis.
<mybatis:config name="mybatis" sqlSessionFactory-ref="sqlSessionFactory" /> <spring:beans> <spring:bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <spring:property name="driverClassName" value="org.h2.Driver" /> <spring:property name="url" value="jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1" /> <spring:property name="username" value="root" /> <spring:property name="password" value="" /> </spring:bean> <spring:bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <spring:property name="dataSource" ref="dataSource" /> <spring:property name="configLocation" value="classpath:mybatis-config.xml" /> </spring:bean> </spring:beans>
Модуль имеет открытый исходный код, и этот вариант использования (с некоторыми изменениями) можно найти как часть контрольных примеров и в качестве рабочего примера.
Что дальше? Ну, этот модуль был разработан для Mule до версии 3.4.0, где поддержка транзакций была не совсем адекватна для таких случаев использования, как эти. В Mule 3.4.0 есть новая функция под названием Transaction Scope, которая позволяет вам начинать транзакцию в любом месте потока (аналогично тому, что мы делаем здесь).
Следующим пунктом в списке задач для этого модуля является интеграция транзакций MyBatis с областью транзакций Mule.
Этот модуль в настоящее время не поддерживает транзакции XA, поэтому он также находится в списке задач.
Надеюсь, вам нравится пользоваться этим модулем так же, как мне нравилось его разрабатывать, и это поможет вам избежать ненужной боли!
Конечно, все обсуждаемое — мое личное мнение, и другие могут не согласиться.
Этот модуль будет доступен на нашем сайте в ближайшее время.
Приветствия.