Попробовав написать код, я окончательно запутался в 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 в качестве фабрики создания транзакций.
Комментариев нет:
Отправить комментарий