Каждая реляционная
база данных поддерживает транзакции, под
транзакцией понимается законченная работа над данными, результатом транзакции
может быть одно значение: фиксация
работы над данными или откат к прежнему
состоянию в случае ошибки. Все
транзакции разделяются на два типа, на управляемые и неуправляемые. Начнем с самых
простых, неуправляемых они реализуются посредством интерфейса JDBC API. Вы начинаете транзакцию,
обычно с вызова метода connection. setAutoCommit(false); и заканчиваете методом .commit(); в случае успеха или .rollback();
в случае неудачи. Все примеры, которые мы реализовали с помощью hibernate в прошлых главах, использовали по
умолчанию соединение посредством интерфейса JDBC API. Мы
конечно не прямо работали с интерфейсом JDBC API, а косвенно через hibernate
обертку,
конкретно с транзакциями работали через
интерфейс org.hibernate.Transaction.
В
программах, которые работают с несколькими системами баз данных и одновременно меняют данные в двух или более
системах нужен другой механизм фиксации изменений. Для этого используют менеджер транзакций и распределенные
транзакции с двух фазным коммитом, эти транзакции реализуются посредством Java
Transaction API (JTA) . Такие
транзакции называются управляемыми, и работа с ними происходит обычно на сервере приложений, но есть так же Open Source реализации JTA, такие например как Atomikos. JTA используется не только как
распределенные транзакции, но и в декларативных управляемых транзакциях
контейнера, называемых Container Manager
Transaction (CMT). У CMT начало
транзакции определяется на основе какого-либо дескриптора, который обычно применим к методу “DAO класса” или
методу EJB.
Рассмотрим
простой пример использования jta транзакции, для этого нам потребуется две
вещи, нужен менеджер транзакций, возьмем
Atomikos, и две библиотеки которые позволят нам создать JNDI сервер и разместить в нем наш connection pool. И так внесем изменения в
наш файл настроек hibernate.cfg.xml:
<?xml
version='1.0' encoding='utf-8'?>
<hibernate-configuration>
<session-factory>
<!-- работаем с нашим
JNDI пуллом
-->
<property
name="connection.datasource">testDS1</property>
<property
name="dialect">org.hibernate.dialect.DerbyDialect</property>
<!--
устанавливаем тип сессии для работы c
jta транзакциями -->
<property
name="current_session_context_class">jta</property>
<!—дадим
подсказку hibernate
через какой класс он может взаимодействовать с менеджером транзакций Atomikos
-->
<property
name="transaction.manager_lookup_class">
com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup</property>
…
<mapping
class="org.vit.ch1.Person"/>
</session-factory>
</hibernate-configuration>
Смотрим на
листинг примера, комментарии по ходу кода:
public class
Main {
private static UserTransaction
userTransaction;
private static AtomikosDataSourceBean
dataSource1;
private static AtomikosDataSourceBean
dataSource2;
public static void main(String[] args)
throws Exception {
initDataSources(); /* создаем два datasources и
привязываем их к нашему JNDI серверу. */
initTransactionManager(); /* подготавливаем транзакцию. */
SessionFactory sf1 = new
Configuration().configure("/hibernate.cfg.xml").buildSessionFactory();
SessionFactory sf2 = new
Configuration().configure("/hibernate1.cfg.xml").buildSessionFactory();
userTransaction.setTransactionTimeout(600);
userTransaction.begin();// начинаем jta транзакцию
try {
System.out.println("*** работаем с первой DB1 ***");
doWork(sf1, "user");
System.out.println("***работаем со второй DB2 ***");
doWork(sf2, "user");
userTransaction.commit();// подтверждаем нашу jta транзакцию
} catch (Exception ex) {
ex.printStackTrace();
userTransaction.rollback();//откат
транзакции если что-то пошло не так.
}
sf1.close();
sf2.close();
dataSource1.close();
dataSource2.close();
System.out.println("Both databases
updated successfully");
}
/**
создаем и выводим данные. */
private static void doWork(SessionFactory
sf, String username) {
Session session =
sf.getCurrentSession();
persistPerson(session, username);
listPersons(session);
session.close();
}
/** выводим все данные. */
private static void listPersons(Session
session) {
List users =
session.createQuery("from Person ").list();
for (int i = 0; i < users.size();
i++) {
Person person = (Person)
users.get(i);
System.out.println(person.toString());
}
}
/**
создаем person */
private static void persistPerson(Session
session, String userName) {
Person u = new Person();
u.setFirstName(userName);
session.save(u);
}
/** подготавливаем транзакцию. */
private static void
initTransactionManager() {
userTransaction = new
com.atomikos.icatch.jta.UserTransactionImp();
}
/** создаем два datasources
и привязываем их к нашему JNDI серверу. */
private static void initDataSources()
throws NamingException {
Context ctx = new InitialContext();
dataSource1 = getDataSource_db1();
ctx.rebind("testDS1",
dataSource1);
dataSource2 = getDataSource_db2();
ctx.rebind("testDS2",
dataSource2);
ctx.close();
}
/*
создаем первый datasource.
*/
private static AtomikosDataSourceBean
getDataSource_db1() {
AtomikosDataSourceBean ds = new
AtomikosDataSourceBean();
ds.setUniqueResourceName("derby1");
ds.setXaDataSourceClassName("org.apache.derby.jdbc.EmbeddedXADataSource");
Properties p = new Properties();
p.setProperty("databaseName","users1");
ds.setXaProperties(p);
ds.setPoolSize(3);
return ds;
}
/* создаем второй
datasource. */
private static AtomikosDataSourceBean
getDataSource_db2() {
AtomikosDataSourceBean ds = new
AtomikosDataSourceBean();
ds.setUniqueResourceName("derby2");
ds.setXaDataSourceClassName("org.apache.derby.jdbc.EmbeddedXADataSource");
Properties p = new Properties();
p.setProperty("databaseName","users2");
ds.setXaProperties(p);
ds.setPoolSize(3);
return
ds;
}
}