gpt4 book ai didi

java - 在使用 AbstractRoutingDataSource 切换数据源时共享事务

转载 作者:塔克拉玛干 更新时间:2023-11-02 08:55:46 27 4
gpt4 key购买 nike

使用 AbstractRoutingDataSource 时如何在数据源之间共享事务切换 Activity 数据源?

到目前为止,在没有事务的情况下,查询会在两个数据库上正确执行,但是当我开始事务时,所有内容都在同一个数据库上执行(即我不能再切换到第二个数据库)。

有什么想法吗?

@Transactional
public void crossDbTransactionTest() {
// Selects a datasource from my pool of AbstractRoutingDataSources
DbConnectionContextHolder.setDbConnectionByYear(2012);

// execute something in the first database
this.executeSomeJpaQuery("xyz");

// switch to the second database
DbConnectionContextHolder.setDbConnectionByYear(2011);

// execute something in the second database
this.executeSomeJpaQuery("xyz"); // on any errors rollback changes in both databases
}

EDIT1(添加配置文件):

持久性.xml:

<persistence-unit name="primarnaKonekcija" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.SQLServerDialect" />
<property name="hibernate.max_fetch_depth" value="1" />
<property name="hibernate.transaction.manager_lookup_class"
value="org.hibernate.transaction.JBossTransactionManagerLookup" />
</properties>
</persistence-unit>

spring-jpa.xml:

<!-- Shared DB credentials -->
<context:property-placeholder location="classpath:config.properties" />

<!-- DB connections by year -->
<bean id="parentDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" abstract="true">
<property name="driverClassName" value="${db.driver}" />
<property name="username" value="${db.user}" />
<property name="password" value="${db.password}" />
</bean>
<bean id="dataSource" class="myPackage.DbConnectionRoutingDataSource">
<!-- Placeholder that is replaced in BeanFactoryPostProcessor -->
<property name="targetDataSources">
<map key-type="int">
<entry key="0" value-ref="placeholderDs" />
</map>
</property>
<property name="defaultTargetDataSource" ref="placeholderDs" />
</bean>

<!-- EntityManager configuration -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="primarnaKonekcija" />
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="databasePlatform" value="org.hibernate.dialect.SQLServerDialect" />
<property name="showSql" value="true" />
</bean>
</property>
</bean>

<tx:annotation-driven />
<tx:jta-transaction-manager />

编辑 2:

尝试将所有内容切换到 JTA 和 JNDI 提供的数据源。

将 transaction-type="RESOURCE_LOCAL"更改为 transaction-type="JTA"也不起作用 - JtaStatusHelper 抛出 NullPointerException,表示 transactionManager 为空。

编辑 3:

将 JBossTransactionManagerLookup 添加到 persistence.xml,现在在事务中切换到第二个数据源时出现“不允许添加多个最后资源”。

编辑 4:

已尝试 setting JBOSS所以我克服了那个错误 - 数据库切换现在可以使用预期的警告:“多个最后资源已添加到当前事务中。这在事务上是不安全的,不应依赖。”。接下来将尝试在 JBOSS 中配置 MSSQL XA 驱动程序。

编辑 5:

configuring MSSQL XA 之后,一切都按预期工作,将发布一个答案,其中包含设置所需的步骤。

最佳答案

除非您别无选择,否则我不会推荐这种解决方案。二级缓存不可能与这样的解决方案一起工作,但它是一个(稳定的)解决方案,我被迫使用它来争取时间,直到底层遗留数据库合并为一个。

首先在 JBoss standalone.xml 中将您的数据库连接设置为 XA 数据源。如果使用 MS SQL 服务器,请按照说明如何在 http://msdn.microsoft.com/en-us/library/aa342335.aspx 正确设置 XA。

standalone.xml

<datasources>
<datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true">
<connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1</connection-url>
<driver>h2</driver>
<security>
<user-name>sa</user-name>
<password>sa</password>
</security>
</datasource>
<xa-datasource jta="true" jndi-name="java:jboss/datasources/MYDB_ONE" pool-name="MYDB_ONE" enabled="true" use-java-context="true" use-ccm="true">
<xa-datasource-property name="ServerName">
localhost
</xa-datasource-property>
<xa-datasource-property name="DatabaseName">
MYDB_ONE
</xa-datasource-property>
<xa-datasource-property name="SelectMethod">
cursor
</xa-datasource-property>
<xa-datasource-class>com.microsoft.sqlserver.jdbc.SQLServerXADataSource</xa-datasource-class>
<driver>sqljdbc</driver>
<xa-pool>
<is-same-rm-override>false</is-same-rm-override>
</xa-pool>
<security>
<user-name>some_user</user-name>
<password>some_password</password>
</security>
<validation>
<valid-connection-checker class-name="org.jboss.jca.adapters.jdbc.extensions.mssql.MSSQLValidConnectionChecker"/>
</validation>
</xa-datasource>
<xa-datasource jta="true" jndi-name="java:jboss/datasources/MYDB_TWO" pool-name="MYDB_TWO" enabled="true" use-java-context="true" use-ccm="true">
<xa-datasource-property name="ServerName">
localhost
</xa-datasource-property>
<xa-datasource-property name="DatabaseName">
MYDB_TWO
</xa-datasource-property>
<xa-datasource-property name="SelectMethod">
cursor
</xa-datasource-property>
<xa-datasource-class>com.microsoft.sqlserver.jdbc.SQLServerXADataSource</xa-datasource-class>
<driver>sqljdbc</driver>
<xa-pool>
<is-same-rm-override>false</is-same-rm-override>
</xa-pool>
<security>
<user-name>some_user</user-name>
<password>some_password</password>
</security>
<validation>
<valid-connection-checker class-name="org.jboss.jca.adapters.jdbc.extensions.mssql.MSSQLValidConnectionChecker"/>
</validation>
</xa-datasource>
<drivers>
<driver name="h2" module="com.h2database.h2">
<xa-datasource-class>org.h2.jdbcx.JdbcDataSource</xa-datasource-class>
</driver>
<driver name="sqljdbc" module="com.microsoft.sqlserver.jdbc">
<driver-class>com.microsoft.sqlserver.jdbc.SQLServerDriver</driver-class>
</driver>
<driver name="postgresql" module="org.postgresql">
<xa-datasource-class>org.postgresql.xa.PGXADataSource</xa-datasource-class>
</driver>
</drivers>
</datasources>

然后我设置我的 entityManager bean,它使用我的 AbstractRoutingDataSource 实现作为它的数据源。这是不使用 persistence.xml 文件的 Spring 支持的 JPA 设置;据我所知,这是使用 JBoss 7 时获得实体包自动扫描的唯一方法。

springJpaConfig.xml

<!-- Use @PersistenceContext annotations for injecting entity managers -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

<!-- Set up JTA transaction manager -->
<tx:jta-transaction-manager />

<bean id="entityManagerFactoryMyDB" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="MyDB" />
<property name="dataSource" ref="dataSourceMyDB" />
<property name="packagesToScan" value="my.package.with.jpa.entities" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true" />
</bean>
</property>
<property name="jpaPropertyMap">
<map>
<entry key="javax.persistence.transactionType" value="jta" />

<entry key="hibernate.transaction.jta.platform" value="org.hibernate.service.jta.platform.internal.JBossAppServerJtaPlatform" />
<entry key="jboss.entity.manager.factory.jndi.name" value="java:app/MyDBEntityManagerFactory" />

<entry key="hibernate.dialect" value="org.hibernate.dialect.SQLServer2008Dialect" />
</map>
</property>
</bean>

<bean id="dataSourceMyDB" class="some.package.AbstractRoutingDataSourceMyDB">
<property name="lenientFallback" value="false" />
<property name="defaultTargetDataSource" value="java:jboss/datasources/ExampleDS" />
<property name="targetDataSources">
<map key-type="String">
<!-- This is a placeholder that will be filled in by BeanFactoryPostProcessor -->
</map>
</property>
</bean>

<!-- This allows us to modify Spring configuration load the list of datasources -->
<bean class="some.package.DatasourceRegisteringBeanFactoryPostProcessor" />

我在 AbstractRoutingDataSourceMyDB 中使用 ExampleDS 作为默认值,因为您必须提供一个 defaultTargetDataSource,但我总是想手动选择一个有效的数据库,因此如果有人在没有先手动选择连接的情况下尝试访问该数据库,他们将尝试执行他们的查询不存在的 ExampleDS 数据库,这将引发异常(非常 hacky,但它完成了工作)。

在 BeanFactoryPostProcessor 中,我现在需要填写我的数据源列表:

DatasourceRegisteringBeanFactoryPostProcessor.java

package some.package
class DatasourceRegisteringBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
HashMap<String, String> connectionsListMyDB = new HashMap<>();

// Load your connection list from wherever you need to, you can
// enumerate them directly from JNDI or some configuration location
connectionsListMyDB.put("db1", "java:jboss/datasources/MYDB_ONE");
connectionsListMyDB.put("db2", "java:jboss/datasources/MYDB_TWO");

if (connectionsList.isEmpty())
throw new RuntimeException("No JPA connections defined");

// Configure the dataSource bean properties
BeanDefinitionRegistry factory = (BeanDefinitionRegistry) beanFactory;
MutablePropertyValues mpv = factory.getBeanDefinition("dataSourceMyDB").getPropertyValues();

ManagedMap<String, String> mm = (ManagedMap<String, String>) mpv.getPropertyValue(
"targetDataSources").getValue();
mm.clear();
for (Entry<String, String> e : connectionsListMyDB.entrySet()) {
mm.put(e.getKey(), e.getValue());
}
}
}

这是我对 AbstractRoutingDataSource 的实现,它允许我在运行时切换连接:

AbstractRoutingDataSourceMyDB.java

public class AbstractRoutingDataSourceMyDB extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return getDbConnectionMyDB();
}

// ThreadLocal variable so that the connection gets set for the current thread
// using spring's request scope on the class instead of ThreadLocal would also work here.
private final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

public void setDbConnectionMyDB(String myKey) {
Assert.notNull(myKey, "myKey cannot be null");

contextHolder.set(myKey);

String k = contextHolder.get();
}

public String getDbConnectionMyDB() {
return (String) contextHolder.get();
}

public void clearDbConnectionMyDB() {
contextHolder.remove();
}
}

请注意,在从 DAO 类中更改当前连接之前,您必须调用 entitymanager.flush() 和 clear(),否则该事务范围内的所有未决操作将在事务提交时在新连接上执行。这是因为 Hibernate session 不知道连接曾经发生过更改,据它所知 - 它始终是同一个数据库。


所以在您的 DAO 中,您现在可以这样做:

SomeTableDAO.java

@PersistenceContext(unitName = "MyDB")
private EntityManager em;

@Autowired
private AbstractRoutingDataSourceMyDB routingSource;

public void someMethod(int id) {
em.flush();
em.clear();
routingSource.setDbConnectionMyDB("db1");
em.remove(em.getReference(Something.class, id)); // delete something in db1

em.flush();
em.clear();
routingSource.setDbConnectionMyDB("db2");
em.remove(em.getReference(Something.class, id)); // delete something else with the same id in db2
}

就这样吧,虽然它不是很漂亮 - 它可以完成 :)

关于java - 在使用 AbstractRoutingDataSource 切换数据源时共享事务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12618307/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com