Попробовав написать код, я окончательно запутался в xml описании компонентов (bean) для Spring. Да тут еще и появилась возможность реализовать систему на основе Apache Camel Framework. Изучая данный framework, я понял, что мне прямая дорога в OSGi.
В относительно новой редакции OSGi V4.2 есть своя реализация xml описании компонентов, похожая на Spring и очень сильно отличающаяся. Короче, мне не по душе стало склеивать OSGi Blueprint и Spring Framework. Но сама идея OSGi Blueprint не подразумевает AOP ориентированного подхода. В Spring за всех старается некий AOP Invoker при обнаружении аннотации @Transaction. Мне так нравятся эти аннотации. Значит, надо искать что-то из CDI подобных реализаций. Для MyBatis есть специальная библиотека MyBatis-Guice. Это как раз то, что мне надо. В OSGi пока только рассматривается возможность использования CDI совместно с xml описанием OSGi Blueprint. Проект Google Guice успешно работает под управлением OSGi.
Мои муки выбора остановились на использовании MyBatis-Guice. Главное достоинство - это лаконичный java код с использованием аннотаций @Transaction и @Injection для моих любимых интерфейсов, скрывающих все муки JDBC SQL запросов.
И так, приступим к самому интересному. Это транзакции. Как известно, транзакции бывают локальными и глобальными. Не буду вдаваясь в детали описаний. Локальные - это те, что происходят на уровне текущего соединения с базой данных java.sql.Connection. Глобальные - это те, что помогают выполнить действия разных систем (сервисов, служб) в рамках одной задачи.
Короче, в MyBatis напрочь отсутствует система взаимодействия с глобальными транзакциями. Ну, это частично понятно. Реализация данного функционала тянет за собой кусок Java EE спецификации и требует наличия менеджера управления транзакциями. В небольшом java проекте весь этот ворох дополнительных библиотек будет только вызывать одно раздражение.
Поскольку я решился взять за основу OSGi Enterprise V4.2 c OSGi Blueprint, то мне менеджер транзакций достается в качестве полезного бонуса. В старой версии iBatis была поддержка JTA транзакций. Мне надо всего лишь переработать старый код под новый функционал MyBatis.
Далее идет большое количество кода.
Класс JtaTransactionFactory
/* * Copyright 2009-2012 The MyBatis Team * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.mybatis.transaction.jta; import java.sql.Connection; import java.util.Properties; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.sql.DataSource; import javax.transaction.UserTransaction; import org.apache.ibatis.exceptions.ExceptionFactory; import org.apache.ibatis.session.TransactionIsolationLevel; import org.apache.ibatis.transaction.Transaction; import org.apache.ibatis.transaction.TransactionFactory; public class JtaTransactionFactory implements TransactionFactory { private UserTransaction userTransaction; private boolean closeConnection = true; public JtaTransactionFactory() { } @Override public void setProperties(Properties props) { if (props != null) { String utxName = null; try { utxName = (String) props.get("UserTransaction"); InitialContext initCtx = new InitialContext(); userTransaction = (UserTransaction) initCtx.lookup(utxName); } catch (NamingException e) { throw ExceptionFactory.wrapException( "Error initializing JtaTransactionConfig while looking up UserTransaction (" + utxName + ").", e); } String closeConnectionProperty = props.getProperty("closeConnection"); if (closeConnectionProperty != null) { closeConnection = Boolean.valueOf(closeConnectionProperty); } } } @Override public Transaction newTransaction(Connection conn) { return new JtaTransaction(userTransaction, conn, closeConnection); } @Override public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) { return new JtaTransaction(userTransaction, dataSource, level, autoCommit); } }
Класс JtaTransaction
/* * Copyright 2009-2012 The MyBatis Team * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.mybatis.transaction.jta; import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; import javax.transaction.Status; import javax.transaction.UserTransaction; import org.apache.ibatis.exceptions.ExceptionFactory; import org.apache.ibatis.logging.Log; import org.apache.ibatis.logging.LogFactory; import org.apache.ibatis.session.TransactionIsolationLevel; import org.apache.ibatis.transaction.Transaction; import org.apache.ibatis.transaction.managed.ManagedTransaction; public class JtaTransaction implements Transaction { private static final Log log = LogFactory.getLog(ManagedTransaction.class); private final UserTransaction userTransaction; private DataSource dataSource; private TransactionIsolationLevel level; private Connection connection; private final boolean closeConnection; private boolean commmitted = false; private boolean newTransaction = false; public JtaTransaction(UserTransaction userTransaction, Connection connection, boolean closeConnection) { this.userTransaction = userTransaction; this.connection = connection; this.closeConnection = closeConnection; if(userTransaction == null) { throw ExceptionFactory.wrapException("JtaTransaction initialization failed. UserTransaction was null.", null); } if(connection == null) { throw ExceptionFactory.wrapException("JtaTransaction initialization failed. Connection was null.", null); } init(); } public JtaTransaction(UserTransaction userTransaction, DataSource ds, TransactionIsolationLevel level, boolean closeConnection) { this.userTransaction = userTransaction; this.dataSource = ds; this.level = level; this.closeConnection = closeConnection; if(userTransaction == null) { throw ExceptionFactory.wrapException("JtaTransaction initialization failed. UserTransaction was null.", null); } if(dataSource == null) { throw ExceptionFactory.wrapException("JtaTransaction initialization failed. DataSource was null.", null); } init(); } private void init() { try { newTransaction = userTransaction.getStatus() == Status.STATUS_NO_TRANSACTION; if (newTransaction) { userTransaction.begin(); } } catch (Exception e) { throw ExceptionFactory.wrapException("JtaTransaction could not start transaction. Cause: ", e); } } @Override public Connection getConnection() throws SQLException { if (this.connection == null) { openConnection(); } return this.connection; } @Override public void commit() throws SQLException { if (commmitted) { throw ExceptionFactory.wrapException("JtaTransaction could not commit because this transaction has already been committed.", null); } try { if (newTransaction) { userTransaction.commit(); } } catch (Exception e) { throw ExceptionFactory.wrapException("JtaTransaction could not commit. Cause: ", e); } commmitted = true; } @Override public void rollback() throws SQLException { if (!commmitted) { try { if (userTransaction != null) { if (newTransaction) { userTransaction.rollback(); } else { userTransaction.setRollbackOnly(); } } } catch (Exception e) { throw ExceptionFactory.wrapException("JtaTransaction could not rollback. Cause: ", e); } } } @Override public void close() throws SQLException { if (this.closeConnection && this.connection != null) { if (log.isDebugEnabled()) { log.debug("Closing JDBC Connection [" + this.connection + "]"); } this.connection.close(); } } protected void openConnection() throws SQLException { if (log.isDebugEnabled()) { log.debug("Openning JDBC Connection"); } this.connection = this.dataSource.getConnection(); if (level != null) { connection.setTransactionIsolation(level.getLevel()); } if (connection.getAutoCommit()) { connection.setAutoCommit(false); } } }Теперь осталось дело за малым. Надо создать SqlSessionFactory и в конфигурации указать использование JtaTransactionFactory в качестве фабрики создания транзакций.
Комментариев нет:
Отправить комментарий