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

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

到目前为止,没有事务,查询将在两个数据库上正确执行,但是当我开始事务时,所有事情都在同一个数据库上执行(即,我无法再切换到第二个数据库).

有任何想法吗?

@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(添加的配置文件):

persistence.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为null.

编辑3:

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

编辑4:

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

编辑5:

configuring MSSQL XA之后,一切都会按预期进行,将发布答案以及进行此设置所需的步骤.

最佳答案
除非您别无选择,否则我不建议您使用此解决方案. 2级缓存可能无法使用这样的解决方案,但这是一个(稳定)的解决方案,我不得不用它来争取时间,直到基础遗留数据库合并为一个.

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

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>

然后,我设置了使用我的AbstractRoutingDataSource实现作为其DataSource的entityManager bean.
这是Spring支持的JPA设置,不使用persistence.xml文件;据我所知,这是使用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" />

我使用ExampleDS作为AbstractRoutingDataSourceMyDB中的默认值,因为您必须提供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会话忽略了连接曾经发生过的更改-它始终是同一数据库.

因此,在您的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
}

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


这篇文章主要介绍了spring的事务传播属性REQUIRED_NESTED的原理介绍,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。传统事务中回滚点的使...
今天小编给大家分享的是一文解析spring中事务的传播机制,相信很多人都不太了解,为了让大家更加了解,所以给大家总结了以下内容,一起往下看吧。一定会有所收获...
这篇文章主要介绍了SpringCloudAlibaba和SpringCloud有什么区别,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。Spring Cloud Netfli...
本篇文章和大家了解一下SpringCloud整合XXL-Job的几个步骤。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。第一步:整合pom文件,在S...
本篇文章和大家了解一下Spring延迟初始化会遇到什么问题。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。List 坑列表 = new ArrayList(2);...
这篇文章主要介绍了怎么使用Spring提供的不同缓存注解实现缓存的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇...
本篇内容主要讲解“Spring中的@Autowired和@Resource注解怎么使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学...
今天小编给大家分享一下SpringSecurity怎么定义多个过滤器链的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家
这篇文章主要介绍“Spring的@Conditional注解怎么使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Spring的@Con...
这篇文章主要介绍了SpringCloudGateway的熔断限流怎么配置的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇SpringCloud&nb...
今天小编给大家分享一下怎么使用Spring解决循环依赖问题的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考
这篇文章主要介绍“Spring事务及传播机制的原理及应用方法是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Sp...
这篇“SpringCloudAlibaba框架实例应用分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价
本篇内容主要讲解“SpringBoot中怎么使用SpringMVC”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习...
这篇文章主要介绍“SpringMVC适配器模式作用范围是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“SpringMVC
这篇“导入SpringCloud依赖失败如何解决”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家...
这篇文章主要讲解了“SpringMVC核心DispatcherServlet处理流程是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来
今天小编给大家分享一下SpringMVCHttpMessageConverter消息转换器怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以...
这篇文章主要介绍“Spring框架实现依赖注入的原理是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Spring框架...
本篇内容介绍了“Spring单元测试控制Bean注入的方法是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下