Настройка системы
В предыдущей статье я упомянул об использовании MyBatis-Guice. В этой статье я покажу способ настройки Guice модуля.Для начала напишем базовые xml файлы настройки MyBatis. Можно было бы обойтись и без них, но мне не очень нравится избыточное количество аннотаций каждого метода.
mapping.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>
<typeAlias type="org.myproject.model.ReceiptDoc" alias="ReceiptDoc"/>
<typeAlias type="org.myproject.impl.model.entity.ReceiptDocEntity" alias="ReceiptDocEntity"/>
</typeAliases>
<mappers>
<mapper resource="org/myproject/db/ReceiptDocMapper.xml"/>
</mappers>
</configuration>
Файл с SQL запросами
org/myproject/db/ReceiptDocMapper.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="org.myproject.impl.provider.ReceiptDocMapper">
<resultMap id="docMap" type="ReceiptDocEntity">
<id property="id" column="ID" jdbcType="VARCHAR"/>
<result property="dateLoad" column="DATELOAD" jdbcType="TIMESTAMP"/>
<result property="docUrl" column="DOC_URL" jdbcType="VARCHAR"/>
<result property="docName" column="DOC_NAME" jdbcType="VARCHAR"/>
<result property="fileName" column="FILE_NAME" jdbcType="VARCHAR"/>
</resultMap>
<!-- Поиск документа по коду -->
<select id="findReceiptDoc" resultMap="docMap" parameterType="string">
select ID, DATELOAD, DOC_URL, DOC_NAME, FILE_NAME
from RECEIPT_DOC where ID = #{id}
</select>
<!-- Поиск документа по названию -->
<select id="findReceiptDocByName" resultMap="docMap" parameterType="string">
select ID, DATELOAD, DOC_URL, DOC_NAME, FILE_NAME
from RECEIPT_DOC where DOC_NAME = #{name}
</select>
<!-- Добавление нового документа -->
<insert id="insert" parameterType="ReceiptDoc">
insert into RECEIPT_DOC (
ID, DATELOAD, DOC_URL, DOC_NAME, FILE_NAME
) values (
#{id, jdbcType=VARCHAR},
#{dateLoad, jdbcType=TIMESTAMP},
#{docUrl, jdbcType=VARCHAR},
#{docName, jdbcType=VARCHAR},
#{fileName, jdbcType=VARCHAR}
)
</insert>
</mapper>
Далее, составим модель объектов.
ReceiptDoc - это интерфейс сохраняемого объекта.
ReceiptDocEntity - это сохраняемый объект.
ReceiptDocMapper - это интерфейс генерируемого объекта, за которым MyBatis прячет весь функционал
взаимодействия с базой данных.
Интерфейс манипуляции с базой данных ReceiptDocMapper
package org.myproject.impl.provider;
import org.myproject.model.ReceiptDoc;
public interface ReceiptDocMapper {
ReceiptDoc findReceiptDoc(String id);
List<ReceiptDoc> findReceiptDocByName(String name);
void insert(ReceiptDoc receiptDoc);
}
Интерфейс данных ReceiptDoc
package org.myproject.model;
import java.util.Date;
public interface ReceiptDoc {
String getId();
void setId(String id);
Date getDateLoad();
void setDateLoad(Date dateLoad);
String getDocUrl();
void setDocUrl(String docUrl);
String getDocName();
void setDocName(String docName);
String getFileName();
voi setFileName(String fileName);
}
Сохраняемый объект ReceiptDocEntity
package org.myproject.impl.model.entity;
import java.util.Date;
import org.myproject.model.ReceiptDoc;
public class ReceiptDocEntity implements ReceiptDoc, Serializable {
private static final long serialVersionUID = 1L;
// Далее идет реализация интерфейса ReceiptDoc
}
Теперь у нас есть данные для хранения, интерфейс поиска и добавления записей в базу данных. Так же, есть файлы настроек системы MyBatis. Далее, нам понадобится объект, методы которого будут работать с использованием транзакций.
Для этого создадим новый интерфейс объекта, который, для простоты, будет реализовывать те же методы, что и ранее представленный интерфейс ReceiptDocMapper.
package org.myproject.provider;
import org.apache.ibatis.session.SqlSessionFactory;
import org.myproject.model.ReceiptDoc;
public interface Provider {
ReceiptDoc findReceiptDoc(String id);
List<ReceiptDoc> findReceiptDocByName(String name);
void insert(ReceiptDoc receiptDoc);
SqlSessionFactory getSqlSessionFactory();
}
Теперь напишем реализацию данного интерфейса.
Реализация одного из методов интерфейса Provider могла бы выглядеть примерно так:
private SqlSessionManager sqlSessionManager;
@Override
public ReceiptDoc findReceiptDoc(String id) {
boolean isSessionInherited = sqlSessionManager.isManagedSessionStarted();
if(!isSessionInherited) {
sqlSessionManager.startManagedSession();
}
try {
ReceiptDocMapper mapper = sqlSessionManager.getMapper(ReceiptDocMapper.class);
// основная задача данного метода
ReceiptDoc receiptDoc = findReceiptDoc(id);
if(!isSessionInherited) {
sqlSessionManager.commit();
}
return receiptDoc;
}
catch(Exception e) {
sqlSessionManager.rollback();
}
finnaly {
if(!isSessionInherited) {
sqlSessionManager.close();
}
}
}
Теперь сделаем то же самое с использованием mybatis-guice
@Inject
private ReceiptDocMapper mapper;
@Override
@Transactional(force = true)
public ReceiptDoc findReceiptDoc(String id) {
return mapper.findReceiptDoc(id);
}
Вот как красиво и лаконично выглядит функционал взаимодействия с базой данных. Весь функционал управления сессией и транзакциями переместился в обработчик аннотации @Transactional.
Нечто подобное можно постараться реализовать на CDI JSR-299 Weld. Но меня напрягает тот факт, что ReceiptDocMapper - это интерфейс. Рисовать методы провайдера для каждого интерфейса не совсем правильно.
Осталось дело за малым - настроить нашу CDI систему. Напишем небольшой класс, помогающий нам выполнить настройки.
package org.myproject.impl.provider;
import javax.sql.DataSource;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class ServicesUtil {
/**
* Создание JSR-330 Guice Injector. Поднятие MyBatis.
* @param dataSource соединение с базой данных
* @param useXA учитывать систему распределенных транзакций или нет
* @param environment окружение для MyBatis
* @param mappings файл настроек для MyBatis
* @return
*/
public static Injector createInjector(DataSource dataSource, boolean useXA, String environment, String mappings) {
// Нам нужен ClassLoader приватного класса в текущем OSGI Bundle.
// В настройка MyBatis используются приватные классы.
SessionModule module = new SessionModule(ServicesUtil.class.getClassLoader(), environment, mappings);
Injector injector = Guice.createInjector(module);
module.configure(
injector.getInstance(Provider.class).getSqlSessionFactory(),
dataSource,
useXA);
return injector;
}
}
На следующий код надо запастись терпением. Он достаточно длинный.
package com.comita.esb.impl.aisgz.exchange.oos.model;
import java.util.Properties;
import javax.sql.DataSource;
import javax.transaction.UserTransaction;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.mybatis.guice.XMLMyBatisModule;
import org.mybatis.transaction.jta.JtaTransactionFactory;
import com.google.inject.PrivateModule;
import org.myproject.provider.Provider;
public class SessionModule extends PrivateModule {
private final String environmentId;
private final ClassLoader classLoader;
private final String classPathResource;
public SessionModule(
ClassLoader classLoader,
String environmentId,
String classPathResource) {
this.environmentId = environmentId;
this.classLoader = classLoader;
this.classPathResource = classPathResource;
}
@Override
protected void configure() {
install(new XMLMyBatisModule(){
@Override
protected void initialize() {
useResourceClassLoader(classLoader);
setEnvironmentId(environmentId);
setClassPathResource(classPathResource);
}
});
bind(Provider.class).to(ProviderImpl.class);
expose(Provider.class);
}
public void configure(
SqlSessionFactory sqlSessionFactory,
DataSource dataSource,
boolean useXaDataSource) {
Configuration configuration = sqlSessionFactory.getConfiguration();
TransactionFactory transactionFactory = configTransaction(configuration, useXaDataSource);
Environment env = new Environment(environmentId, transactionFactory, dataSource);
configuration.setEnvironment(env);
}
private TransactionFactory configTransaction(Configuration configuration, boolean useXaDataSource) {
TransactionFactory transactionFactory;
if(useXaDataSource) {
transactionFactory = new JtaTransactionFactory();
// запрещаем данному менеджеру автоматически закрывать соединения
Properties prop = new Properties();
prop.setProperty("closeConnection", "false");
prop.setProperty("UserTransaction", "osgi:service/" + UserTransaction.class.getName());
transactionFactory.setProperties(prop);
}
else {
transactionFactory = new JdbcTransactionFactory();
}
return transactionFactory;
}
}
Обратите внимание, что в свойство "UserTransaction" я указываю название OSGi JNDI ресурс.
В JavaEE 6 надо указывать другое название ресурса. Поскольку, весь проект ориентирован на OSGi,
не было необходимости как-то усложнять данный функционал.