2012-09-27 98 views
3

如何共享數據源之間的交易使用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> 

彈簧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:

新增JBossTransactionManagerLookup到persistence.xml中,現在我得到了「添加多個資源最後不允許」切換到事務中的第二個數據源時。

編輯4:

試過setting JBOSS,所以我讓過去的錯誤 - 數據庫切換與預期的警告,現在的工作:「多最後的資源已被添加到當前事務這是事務不安全的,不應該依賴在「。 要去嘗試配置MSSQL XA驅動程序在JBOSS未來。

編輯5:

configuring MSSQL XA

後,一切都會按計劃,將發佈與設置此步驟需要一個答案。

+0

你可以給我們Spring和JTA配置嗎? –

+0

你能分享你的解決方案嗎?謝謝。 – narduk

+0

張貼它,希望它可以幫助你 - 祝你好運。 – Vedran

回答

3

這是我不推薦,除非你有沒有其他選擇的解決方案。二級高速緩存就不能使用可能像這樣的解決方案的工作,但它是我被迫使用,直到底層遺留數據庫合併爲一個購買時間(穩定)的解決方案。

首先在JBoss中standalone.xml建立數據庫連接的XA數據源。如果使用MS SQL服務器,請按照如何正確設置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> 

然後設置我的EntityManager豆使用我的實施AbstractRoutingDataSource作爲其DataSource 。 這是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數據庫上執行他們的查詢,這會拋出一個異常(非常黑客,但它完成了工作)。

在實現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()和清晰()將獲得在交易中的新的連接上執行承諾。這是因爲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 
} 

所以你去,雖然它並不漂亮 - 這是可以做到:)

相關問題