我有一种情况,我必须在一个应用程序中处理多个客户端,每个客户端都有单独的数据库。为了支持我使用Spring自定义范围,非常类似于内置的请求范围。用户在每个请求中进行身份验证,并可以设置基于传递凭据的上下文客户端ID范围本身似乎正常运作。
所以我使用自定义范围为我的DataSource
创建一个范围代理,以支持每个客户端的不同数据库。我得到了与正确数据库的联系。
我为EntityManagerFactory
创建了一个范围代理来使用JPA。而且这部分看起来还不错。
我为PlatformTransactionManager
添加了一个范围代理,用于声明式事务管理。我在服务层使用@Transactional
,它很好地传播到我的SpringData驱动的存储库层。
一切都很好,只要我使用JPA就可以正常工作。我甚至可以在请求中将上下文切换到不同的客户端(我在引擎盖下使用ThreadLocals),并且正确处理两个数据库的事务。
当我尝试在我的一个自定义存储库中使用JDBCTempate
时,问题就开始了。乍一看,所有看起来都不错,因为没有例外。但是,当我检查数据库中我认为我使用自定义的基于JDBC的存储库插入的对象时,不存在!
我确信我可以通过仅声明JpaTransactionManager
并将DataSource
和EntityManagerFactory
传递给它来一起使用JPA和JDBC - 我检查了它并且没有作用域代理和它有效。
所以问题是当我对JpaTransactionManager
,DataSource
和EntityManagerFactory
bean进行作用域代理时,如何使用PlatformTransactionManager
使JDBC与JPA一起工作?我提醒说,只使用JPA可以很好地工作,但是在混合中添加普通JDBC是行不通的。
UPDATE1:还有一件事:所有readonly(SELECT)操作也适用于JDBC - 只写(INSERT,UPDATE,DELETE)最终没有提交或回滚。
UPDATE2:正如@Tomasz建议我从EntityManagerFactory
和PlatformTransactionManager
删除了作用域代理,因为这些代理确实不需要,并提供比其他任何内容更多的混淆。
真正的问题似乎是在事务中切换范围上下文 。 TransactionSynchronizationManager
约束事务资源(即EMF
或DS
)以在事务开始时进行线程化。它具有解包范围代理的能力,因此它在启动事务时将活动范围中的资源的实际实例绑定。然后,当我在事务中更改上下文时,它们都会搞砸了。
似乎我需要暂停活动事务并将当前事务上下文存储在一边以便能够在进入另一个范围时清除它以使Spring认为它不再在事务内部并且强制它为新事务创建一个适用范围。然后当离开范围时,我必须恢复先前暂停的事务。不幸的是,我还没有提出一个有效的实施方案。任何提示都赞赏。
以下是我的一些代码,但除了范围代理外,它是非常标准的。
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" />