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

前端之家收集整理的这篇文章主要介绍了java-使用AbstractRoutingDataSource切换数据源时共享事务 前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

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

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

有任何想法吗?

  1. @Transactional
  2. public void crossDbTransactionTest() {
  3. // Selects a datasource from my pool of AbstractRoutingDataSources
  4. DbConnectionContextHolder.setDbConnectionByYear(2012);
  5. // execute something in the first database
  6. this.executeSomeJpaQuery("xyz");
  7. // switch to the second database
  8. DbConnectionContextHolder.setDbConnectionByYear(2011);
  9. // execute something in the second database
  10. this.executeSomeJpaQuery("xyz"); // on any errors rollback changes in both databases
  11. }

EDIT1(添加配置文件):

persistence.xml:

  1. <persistence-unit name="primarnaKonekcija" transaction-type="RESOURCE_LOCAL">
  2. <provider>org.hibernate.ejb.HibernatePersistence</provider>
  3. <properties>
  4. <property name="hibernate.dialect" value="org.hibernate.dialect.sqlServerDialect" />
  5. <property name="hibernate.max_fetch_depth" value="1" />
  6. <property name="hibernate.transaction.manager_lookup_class"
  7. value="org.hibernate.transaction.JBossTransactionManagerLookup" />
  8. </properties>
  9. </persistence-unit>

spring-jpa.xml:

  1. <!-- Shared DB credentials -->
  2. <context:property-placeholder location="classpath:config.properties" />
  3. <!-- DB connections by year -->
  4. <bean id="parentDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" abstract="true">
  5. <property name="driverClassName" value="${db.driver}" />
  6. <property name="username" value="${db.user}" />
  7. <property name="password" value="${db.password}" />
  8. </bean>
  9. <bean id="dataSource" class="myPackage.DbConnectionRoutingDataSource">
  10. <!-- Placeholder that is replaced in beanfactoryPostProcessor -->
  11. <property name="targetDataSources">
  12. <map key-type="int">
  13. <entry key="0" value-ref="placeholderDs" />
  14. </map>
  15. </property>
  16. <property name="defaultTargetDataSource" ref="placeholderDs" />
  17. </bean>
  18. <!-- EntityManager configuration -->
  19. <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerfactorybean">
  20. <property name="persistenceUnitName" value="primarnaKonekcija" />
  21. <property name="dataSource" ref="dataSource" />
  22. <property name="jpaVendorAdapter">
  23. <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
  24. <property name="databasePlatform" value="org.hibernate.dialect.sqlServerDialect" />
  25. <property name="showsql" value="true" />
  26. </bean>
  27. </property>
  28. </bean>
  29. <tx:annotation-driven />
  30. <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

  1. <datasources>
  2. <datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true">
  3. <connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1</connection-url>
  4. <driver>h2</driver>
  5. <security>
  6. <user-name>sa</user-name>
  7. <password>sa</password>
  8. </security>
  9. </datasource>
  10. <xa-datasource jta="true" jndi-name="java:jboss/datasources/MYDB_ONE" pool-name="MYDB_ONE" enabled="true" use-java-context="true" use-ccm="true">
  11. <xa-datasource-property name="ServerName">
  12. localhost
  13. </xa-datasource-property>
  14. <xa-datasource-property name="DatabaseName">
  15. MYDB_ONE
  16. </xa-datasource-property>
  17. <xa-datasource-property name="SelectMethod">
  18. cursor
  19. </xa-datasource-property>
  20. <xa-datasource-class>com.microsoft.sqlserver.jdbc.sqlServerXADataSource</xa-datasource-class>
  21. <driver>sqljdbc</driver>
  22. <xa-pool>
  23. <is-same-rm-override>false</is-same-rm-override>
  24. </xa-pool>
  25. <security>
  26. <user-name>some_user</user-name>
  27. <password>some_password</password>
  28. </security>
  29. <validation>
  30. <valid-connection-checker class-name="org.jboss.jca.adapters.jdbc.extensions.mssql.MSsqlValidConnectionChecker"/>
  31. </validation>
  32. </xa-datasource>
  33. <xa-datasource jta="true" jndi-name="java:jboss/datasources/MYDB_TWO" pool-name="MYDB_TWO" enabled="true" use-java-context="true" use-ccm="true">
  34. <xa-datasource-property name="ServerName">
  35. localhost
  36. </xa-datasource-property>
  37. <xa-datasource-property name="DatabaseName">
  38. MYDB_TWO
  39. </xa-datasource-property>
  40. <xa-datasource-property name="SelectMethod">
  41. cursor
  42. </xa-datasource-property>
  43. <xa-datasource-class>com.microsoft.sqlserver.jdbc.sqlServerXADataSource</xa-datasource-class>
  44. <driver>sqljdbc</driver>
  45. <xa-pool>
  46. <is-same-rm-override>false</is-same-rm-override>
  47. </xa-pool>
  48. <security>
  49. <user-name>some_user</user-name>
  50. <password>some_password</password>
  51. </security>
  52. <validation>
  53. <valid-connection-checker class-name="org.jboss.jca.adapters.jdbc.extensions.mssql.MSsqlValidConnectionChecker"/>
  54. </validation>
  55. </xa-datasource>
  56. <drivers>
  57. <driver name="h2" module="com.h2database.h2">
  58. <xa-datasource-class>org.h2.jdbcx.JdbcDataSource</xa-datasource-class>
  59. </driver>
  60. <driver name="sqljdbc" module="com.microsoft.sqlserver.jdbc">
  61. <driver-class>com.microsoft.sqlserver.jdbc.sqlServerDriver</driver-class>
  62. </driver>
  63. <driver name="postgresql" module="org.postgresql">
  64. <xa-datasource-class>org.postgresql.xa.PGXADataSource</xa-datasource-class>
  65. </driver>
  66. </drivers>
  67. </datasources>

然后,我设置了使用我的AbstractRoutingDataSource实现作为其DataSource的entityManager bean.
这是Spring支持的JPA设置,不使用persistence.xml文件;据我所知,这是使用JBoss 7时对实体进行自动程序包扫描的唯一方法.

springJpaConfig.xml

  1. <!-- Use @PersistenceContext annotations for injecting entity managers -->
  2. <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
  3. <!-- Set up JTA transaction manager -->
  4. <tx:jta-transaction-manager />
  5. <bean id="entityManagerFactoryMyDB" class="org.springframework.orm.jpa.LocalContainerEntityManagerfactorybean">
  6. <property name="persistenceUnitName" value="MyDB" />
  7. <property name="dataSource" ref="dataSourceMyDB" />
  8. <property name="packagesToScan" value="my.package.with.jpa.entities" />
  9. <property name="jpaVendorAdapter">
  10. <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
  11. <property name="showsql" value="true" />
  12. </bean>
  13. </property>
  14. <property name="jpaPropertyMap">
  15. <map>
  16. <entry key="javax.persistence.transactionType" value="jta" />
  17. <entry key="hibernate.transaction.jta.platform" value="org.hibernate.service.jta.platform.internal.JBossAppServerJtaPlatform" />
  18. <entry key="jboss.entity.manager.factory.jndi.name" value="java:app/MyDBEntityManagerFactory" />
  19. <entry key="hibernate.dialect" value="org.hibernate.dialect.sqlServer2008Dialect" />
  20. </map>
  21. </property>
  22. </bean>
  23. <bean id="dataSourceMyDB" class="some.package.AbstractRoutingDataSourceMyDB">
  24. <property name="lenientFallback" value="false" />
  25. <property name="defaultTargetDataSource" value="java:jboss/datasources/ExampleDS" />
  26. <property name="targetDataSources">
  27. <map key-type="String">
  28. <!-- This is a placeholder that will be filled in by beanfactoryPostProcessor -->
  29. </map>
  30. </property>
  31. </bean>
  32. <!-- This allows us to modify Spring configuration load the list of datasources -->
  33. <bean class="some.package.DatasourceRegisteringbeanfactoryPostProcessor" />

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

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

DatasourceRegisteringbeanfactoryPostProcessor.java

  1. package some.package
  2. class DatasourceRegisteringbeanfactoryPostProcessor implements beanfactoryPostProcessor {
  3. public void postProcessbeanfactory(ConfigurableListablebeanfactory beanfactory) {
  4. HashMap<String,String> connectionsListMyDB = new HashMap<>();
  5. // Load your connection list from wherever you need to,you can
  6. // enumerate them directly from JNDI or some configuration location
  7. connectionsListMyDB.put("db1","java:jboss/datasources/MYDB_ONE");
  8. connectionsListMyDB.put("db2","java:jboss/datasources/MYDB_TWO");
  9. if (connectionsList.isEmpty())
  10. throw new RuntimeException("No JPA connections defined");
  11. // Configure the dataSource bean properties
  12. BeanDefinitionRegistry factory = (BeanDefinitionRegistry) beanfactory;
  13. MutablePropertyValues mpv = factory.getBeanDefinition("dataSourceMyDB").getPropertyValues();
  14. ManagedMap<String,String> mm = (ManagedMap<String,String>) mpv.getPropertyValue(
  15. "targetDataSources").getValue();
  16. mm.clear();
  17. for (Entry<String,String> e : connectionsListMyDB.entrySet()) {
  18. mm.put(e.getKey(),e.getValue());
  19. }
  20. }
  21. }

这是我实现AbstractRoutingDataSource的实现,它使我可以在运行时切换连接:

AbstractRoutingDataSourceMyDB.java

  1. public class AbstractRoutingDataSourceMyDB extends AbstractRoutingDataSource {
  2. @Override
  3. protected Object determineCurrentLookupKey() {
  4. return getDbConnectionMyDB();
  5. }
  6. // ThreadLocal variable so that the connection gets set for the current thread
  7. // using spring's request scope on the class instead of ThreadLocal would also work here.
  8. private final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
  9. public void setDbConnectionMyDB(String myKey) {
  10. Assert.notNull(myKey,"myKey cannot be null");
  11. contextHolder.set(myKey);
  12. String k = contextHolder.get();
  13. }
  14. public String getDbConnectionMyDB() {
  15. return (String) contextHolder.get();
  16. }
  17. public void clearDbConnectionMyDB() {
  18. contextHolder.remove();
  19. }
  20. }

请注意,在从DAO类中更改当前连接之前,必须先调用entitymanager.flush()和clear(),否则在事务提交时,将在新连接上执行该事务范围内的所有挂起操作.这是因为,就其所知,Hibernate会话忽略了连接曾经发生过的更改-它始终是同一数据库.

因此,在您的DAO中,您现在可以执行以下操作:

SoMetableDAO.java

  1. @PersistenceContext(unitName = "MyDB")
  2. private EntityManager em;
  3. @Autowired
  4. private AbstractRoutingDataSourceMyDB routingSource;
  5. public void someMethod(int id) {
  6. em.flush();
  7. em.clear();
  8. routingSource.setDbConnectionMyDB("db1");
  9. em.remove(em.getReference(Something.class,id)); // delete something in db1
  10. em.flush();
  11. em.clear();
  12. routingSource.setDbConnectionMyDB("db2");
  13. em.remove(em.getReference(Something.class,id)); // delete something else with the same id in db2
  14. }

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

猜你在找的Spring相关文章