У меня есть ситуация, когда мне приходится обрабатывать несколько клиентов в одном приложении, и у каждого клиента есть отдельная база данных. Для поддержки этого я использую пользовательскую область Spring, очень похожую на встроенную область запросов. Пользователь аутентифицируется в каждом запросе и может устанавливать контекстный идентификатор клиента на основе переданных учетных данных. Кажется, что само определение объема работы работает правильно.
Таким образом, я использовал свою собственную область видимости для создания прокси-сервера scoped для моего DataSource
для поддержки различных баз данных для каждого клиента. И я получаю соединения с соответствующими базами данных.
Чем я создал прокси-сервер для EntityManagerFactory
для использования JPA. И эта часть также выглядит хорошо.
Чем я добавил scoped-прокси для PlatformTransactionManager
для декларативного управления транзакциями. Я использую @Transactional
на своем сервисном уровне, и он хорошо распространяется на мой слой репозитория с поддержкой SpringData.
Все хорошо и работает правильно, пока я использую только JPA. Я даже могу переключить контекст на другой клиент в запросе (я использую ThreadLocals под капотом), и транзакции в обе базы данных обрабатываются правильно.
Проблемы начинаются, когда я пытаюсь использовать JDBCTempate
в одном из моих пользовательских хранилищ. С первого взгляда все выглядит нормально, исключений нет. Но когда я проверял базу данных на предмет объектов, я думал, что вставил в свое собственное хранилище на основе JDBC, и меня там нет!
Я точно знаю, что могу использовать JPA и JDBC вместе, объявив только JpaTransactionManager
и передав ему DataSource
и EntityManagerFactory
- я проверил его и без прокси-серверов scoped, и он работает.
Таким образом, вопрос заключается в том, как заставить JDBC работать вместе с JPA, используя JpaTransactionManager
, когда я прокси-прокси прокси DataSource
, EntityManagerFactory
и PlatformTransactionManager
? Я напоминаю, что при использовании отлично работает только JPA, но добавление простого JDBC в микс не работает.
UPDATE1: И еще одна вещь: все операции только для чтения (SELECT) работают также и с JDBC - только записи (INSERT, UPDATE, DELETE) заканчиваются тем, что не были зафиксированы или откатаны.
UPDATE2: Как @Tomasz предложил, я удалил прокси с областью действия из EntityManagerFactory
и PlatformTransactionManager
, поскольку они действительно не нужны и дают больше путаницы, чем что-либо еще.
Кажется, что настоящая проблема заключается в переключении контекста контекста внутри транзакции. TransactionSynchronizationManager
связывает транзакционные ресурсы (т. Е. EMF
или DS
) для потоков в начале транзакции. У него есть возможность развернуть прокси с заданной областью, поэтому он связывает фактический экземпляр ресурса с областью, активной во время запуска транзакции. Затем, когда я изменяю контекст внутри транзакции, все портится.
Похоже, мне нужно приостановить активную транзакцию и сохранить в стороне текущий контекст транзакции, чтобы иметь возможность очистить его при входе в другую область видимости, чтобы Spring подумал, что она больше не внутри транзакции, и вынудил его создать новую для новой области. сфера при необходимости. И затем, выходя из области действия, мне придется восстановить ранее приостановленную транзакцию. К сожалению, я пока не смог придумать работающую реализацию. Любые советы приветствуются.
А ниже приведен мой код, но он довольно стандартный, за исключением прокси-серверов scoped.
DataSource
:
<!-- provides database name based on client context -->
<bean id="clientDatabaseNameProvider"
class="com.example.common.spring.scope.ClientScopedNameProviderImpl"
c:clientScopeHolder-ref="clientScopeHolder"
p:databaseName="${base.db.name}" />
<!-- an extension of org.apache.commons.dbcp.BasicDataSource that
uses proper database URL based on database name given by above provider -->
<bean id="jpaDataSource" scope="client"
class="com.example.common.spring.datasource.MysqlDbInitializingDataSource"
destroy-method="close"
p:driverClassName="${mysql.driver}"
p:url="${mysql.url}"
p:databaseNameProvider-ref="clientDatabaseNameProvider"
p:username="${mysql.username}"
p:password="${mysql.password}"
p:defaultAutoCommit="false"
p:connectionProperties="sessionVariables=storage_engine=InnoDB">
<aop:scoped-proxy proxy-target-class="false" />
</bean>
EntityManagerFactory
:
<bean id="jpaVendorAdapter"
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
p:database="MYSQL"
p:generateDdl="true"
p:showSql="true" />
<util:properties id="jpaProperties">
<!-- omitted for readability -->
</util:properties>
<bean id="jpaDialect"
class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:packagesToScan="com.example.model.core"
p:jpaVendorAdapter-ref="jpaVendorAdapter"
p:dataSource-ref="jpaDataSource"
p:jpaDialect-ref="jpaDialect"
p:jpaProperties-ref="jpaProperties" />
PlatformTracsactionManager
:
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager"
p:dataSource-ref="jpaDataSource"
p:entityManagerFactory-ref="entityManagerFactory" />
<tx:annotation-driven proxy-target-class="false" mode="proxy"
transaction-manager="transactionManager" />