Весенний транзакционный пул. Какой из них я использую?
Я изначально настроил Spring с помощью Xapool, но оказалось, что это мертвый проект, и, похоже, у него много проблем.
Я переключился на c3p0, но теперь узнаю, что аннотации @Transactional на самом деле не создают транзакции при использовании с c3p0. Если я сделаю следующее, он вставит строку в Foo даже через исключение, брошенное внутри метода:
@Service
public class FooTst
{
@PersistenceContext(unitName="accessControlDb") private EntityManager em;
@Transactional
public void insertFoo() {
em.createNativeQuery("INSERT INTO Foo (id) VALUES (:id)")
.setParameter("id", System.currentTimeMillis() % Integer.MAX_VALUE )
.executeUpdate();
throw new RuntimeException("Foo");
}
}
Это странно, потому что, если я закомментирую аннотацию @Transactional, она на самом деле потерпит неудачу и будет жаловаться на то, что для транзакции установлен только откат:
java.lang.IllegalStateException: Cannot get Transaction for setRollbackOnly
at org.objectweb.jotm.Current.setRollbackOnly(Current.java:568)
at org.hibernate.ejb.AbstractEntityManagerImpl.markAsRollback(AbstractEntityManagerImpl.java:421)
at org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:576)
at org.hibernate.ejb.QueryImpl.executeUpdate(QueryImpl.java:48)
at com.ipass.rbac.svc.FooTst.insertFoo(FooTst.java:21)
at com.ipass.rbac.svc.SingleTst.testHasPriv(SingleTst.java:78)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.test.context.junit4.SpringTestMethod.invoke(SpringTestMethod.java:160)
at org.springframework.test.context.junit4.SpringMethodRoadie.runTestMethod(SpringMethodRoadie.java:233)
at org.springframework.test.context.junit4.SpringMethodRoadie$RunBeforesThenTestThenAfters.run(SpringMethodRoadie.java:333)
at org.springframework.test.context.junit4.SpringMethodRoadie.runWithRepetitions(SpringMethodRoadie.java:217)
at org.springframework.test.context.junit4.SpringMethodRoadie.runTest(SpringMethodRoadie.java:197)
at org.springframework.test.context.junit4.SpringMethodRoadie.run(SpringMethodRoadie.java:143)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.invokeTestMethod(SpringJUnit4ClassRunner.java:160)
at org.junit.internal.runners.JUnit4ClassRunner.runMethods(JUnit4ClassRunner.java:51)
at org.junit.internal.runners.JUnit4ClassRunner$1.run(JUnit4ClassRunner.java:44)
at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:27)
at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:37)
at org.junit.internal.runners.JUnit4ClassRunner.run(JUnit4ClassRunner.java:42)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:97)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:45)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
Таким образом, он явно замечает аннотацию @Transactional. Но на самом деле он не отключает autocommit в начале метода.
Вот как у меня настроен транзакционный материал в applicationContext.xml. Это правильно? Если нет, то что это должно быть?
<bean id="jotm" class="org.springframework.transaction.jta.JotmFactoryBean"/>
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager" ref="jotm"/>
<property name="userTransaction" ref="jotm"/>
<property name="allowCustomIsolationLevels" value="true"/>
</bean>
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="false"/>
После нескольких поисков я обнаружил пул соединений под названием Bitronix, но на их странице весенней настройки описаны вещи о JMS, которые даже не имеют никакого смысла. Какое отношение имеет JMS к настройке пула соединений?
Так что я застрял. Что я на самом деле должен делать? Я не понимаю, почему пул соединений должен поддерживать транзакции. Все соединения поддерживают включение и выключение автоматической фиксации, поэтому я не знаю, в чем здесь проблема.
2 ответа
Потребовалось много поисков и экспериментов, но я наконец-то начал работать. Вот мои результаты:
- Engeydra Xapool ужасный пул соединений. Я не буду перечислять проблемы, которые это вызвало, потому что это не имеет значения. Последняя версия этого пула не обновлялась с декабря 2006 года. Это мертвый проект.
- Я поместил c3p0 в контекст своего приложения и получил его довольно легко. Но по какой-то причине он просто не поддерживает откат даже внутри одного метода. Если я помечаю метод как @Transactional, затем делаю вставку в таблицу и затем генерирую исключение RuntimeException (которое определенно не объявлено в списке бросков метода, потому что в методе нет списка бросков), оно все равно будет сохранять вставку в этот стол. Это не откатится.
- Я собирался попробовать Apache DBCP, но мой поиск вызвал множество жалоб по этому поводу, поэтому я не стал беспокоиться.
- Я попробовал Bitronix и у меня было много проблем, чтобы заставить его работать должным образом под Tomcat, но как только я выяснил волшебную конфигурацию, она прекрасно работает. Далее следует все, что вам нужно сделать, чтобы правильно его настроить.
- Я кратко баловался с пулом соединений Atomkos. Похоже, что это должно быть хорошо, но я сначала запустил Bitronix, поэтому я не пытался его использовать.
Приведенная ниже конфигурация работает в автономных модульных тестах и под Tomcat. Это была главная проблема, которая у меня была. Большинство примеров, которые я нашел о том, как настроить Spring с Bitronix, предполагают, что я использую JBoss или какой-то другой полный контейнер.
Первый бит конфигурации - это часть, которая устанавливает менеджер транзакций Bitronix.
<!-- Bitronix transaction manager -->
<bean id="btmConfig" factory-method="getConfiguration" class="bitronix.tm.TransactionManagerServices">
<property name="disableJmx" value="true" />
</bean>
<bean id="btmManager" factory-method="getTransactionManager" class="bitronix.tm.TransactionManagerServices" depends-on="btmConfig" destroy-method="shutdown"/>
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager" ref="btmManager" />
<property name="userTransaction" ref="btmManager" />
<property name="allowCustomIsolationLevels" value="true" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
Основное различие между этим кодом и примерами, которые я нашел, заключается в свойстве disableJmx. Он генерирует исключения во время выполнения, если вы не используете JMX, но оставляете его включенным.
Следующий бит конфигурации - источник данных пула соединений. Обратите внимание, что имя класса пула соединений не является обычным классом оракула "oracle.jdbc.driver.OracleDriver". Это источник данных XA. Я не знаю, какой эквивалентный класс будет в других базах данных.
<bean id="dataSource" class="bitronix.tm.resource.jdbc.PoolingDataSource" init-method="init" destroy-method="close">
<property name="uniqueName" value="dataSource-BTM" />
<property name="minPoolSize" value="1" />
<property name="maxPoolSize" value="4" />
<property name="testQuery" value="SELECT 1 FROM dual" />
<property name="driverProperties"><props>
<prop key="URL">${jdbc.url}</prop>
<prop key="user">${jdbc.username}</prop>
<prop key="password">${jdbc.password}</prop>
</props></property>
<property name="className" value="oracle.jdbc.xa.client.OracleXADataSource" />
<property name="allowLocalTransactions" value="true" />
</bean>
Также обратите внимание, что uniqueName должно отличаться от любых других настроенных вами источников данных.
Конечно, testQuery должен соответствовать конкретной базе данных, которую вы используете. Свойства драйвера зависят от класса базы данных, который я использую. OracleXADataSource по какой-то глупой причине имеет разные имена установщиков для OracleDriver для одного и того же значения.
Параметр allowLocalTransactions должен был иметь значение true для меня. Я нашел рекомендации НЕ устанавливать его в режиме онлайн. Но это кажется невозможным. Это просто не будет работать, если установлено значение false. Я недостаточно осведомлен об этих вещах, чтобы понять, почему это так.
Наконец, нам нужно настроить фабрику диспетчера сущностей.
<util:map id="jpa_property_map">
<entry key="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.BTMTransactionManagerLookup"/>
<entry key="hibernate.current_session_context_class" value="jta"/>
</util:map>
<bean id="dataSource-emf" name="accessControlDb" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="persistenceXmlLocation" value="classpath*:META-INF/foo-persistence.xml" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true"/>
<property name="databasePlatform" value="org.hibernate.dialect.Oracle10gDialect"/>
</bean>
</property>
<property name="jpaPropertyMap" ref="jpa_property_map"/>
<property name="jpaDialect"><bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/></property>
</bean>
Обратите внимание, что свойство dataSource ссылается на идентификатор объявленного мной источника данных. PersistenceXmlLocation относится к xml-файлу постоянства, который существует где-то в пути к классам. Путь к классу *: указывает, что это может быть в любой банке. Без * он не найдет его, если по какой-то причине окажется в банке.
Я обнаружил, что util: map - это удобный способ разместить значения jpaPropertyMap в одном месте, чтобы мне не нужно было повторять их, когда я использую несколько фабрик менеджера сущностей в одном контексте приложения.
Обратите внимание, что util: map выше не будет работать, если вы не включите правильные настройки во внешний элемент Beans. Вот заголовок XML-файла, который я использую:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
Наконец, для того, чтобы Bitronix (или, по-видимому, любой пул, который поддерживает двухфазную фиксацию) работал с Oracle, вы должны запустить следующие привилегии как пользователь SYS. (См. http://publib.boulder.ibm.com/infocenter/wasinfo/v6r0/index.jsp?topic=/com.ibm.websphere.express.doc/info/exp/ae/rtrb_dsaccess2.html и http://docs.codehaus.org/display/BTM/FAQ и http://docs.codehaus.org/display/BTM/JdbcXaSupportEvaluation)
grant select on pending_trans$ to <someUsername>;
grant select on dba_2pc_pending to <someUsername>;
grant select on dba_pending_transactions to <someUsername>;
grant execute on dbms_system to <someUsername>;
Эти гранты должны выполняться для любого пользователя, для которого настроен пул соединений, независимо от того, выполняете ли вы какие-либо изменения. Очевидно, он ищет эти таблицы, когда соединение установлено.
Несколько других вопросов:
- Вы не можете запрашивать таблицы, которые являются удаленными синонимами в Oracle, находясь внутри блока Spring @Transactional при использовании Bitronix (вы получите ORA-24777). Используйте материализованные представления или отдельный EntityManager, который вместо этого напрямую указывает на другую БД.
- По какой-то причине btmConfig в applicationContext.xml имеет проблемы с настройкой значений конфигурации. Вместо этого создайте файл bitronix-default-config.properties. Значения конфигурации, которые вы можете использовать, находятся по адресу http://docs.codehaus.org/display/BTM/Configuration13. Некоторая другая информация о конфигурации для этого файла находится по адресу http://docs.codehaus.org/display/BTM/JdbcConfiguration13 но я не использовал его.
- Bitronix использует некоторые локальные файлы для хранения транзакционных данных. Я не знаю почему, но я знаю, что если у вас есть несколько веб-приложений с локальными пулами подключений, у вас будут проблемы, потому что они оба попытаются получить доступ к одним и тем же файлам. Чтобы исправить это, укажите разные значения для bitronix.tm.journal.disk.logPart1Filename и bitronix.tm.journal.disk.logPart2Filename в bitronix-default-config.properties для каждого приложения.
- Javadocs Bitronix находится по адресу http://www.bitronix.be/uploads/api/index.html.
Вот и все. Очень сложно заставить его работать, но сейчас оно работает, и я счастлив. Я надеюсь, что все это поможет другим, переживающим те же проблемы, что и я, чтобы все это заработало.
Когда я выполняю пул соединений, я склонен использовать тот, который предоставляется сервером приложений, на котором я развертываю. Это просто имя JNDI для Spring в тот момент.
Поскольку я не хочу беспокоиться о сервере приложений во время тестирования, я использую DriverManagerDataSource и связанный с ним менеджер транзакций, когда я тестирую модули. Я не так обеспокоен пулированием или производительностью при тестировании. Я хочу, чтобы тесты выполнялись эффективно, но объединение в этом случае не является преградой.