前段時間碰到一個需要訪問多個數據庫的例子,由於項目上是採用tomcat作爲項目開發和佈署,所以就沒有考慮採用特定廠商的JTA實現,而是通過一個開源的JTA實現來完成tomcat和多個數據庫之間的直接交互。
多數據庫訪問最直接的問題就是在一個service中,存在着多個數據庫dao對象,當前面的dao對象操作完成之後,如果後面的某一個dao訪問出錯,那麼這個service應該如何進行回滾呢。一般來說,回滾應該是整個service一起回滾,所以就需要對這個service中的所有dao所涉及的sessionFactory進行處理。而對於hibernate+spring來說,spring是使用hibernate的sessionFactory來進行事務的控制和回滾的,而hibernate又是將由自己的一個transactionFactory引用相關的transaction來進行事務的提交和回滾。所以,如果要用到多個數據庫,就需要一個支持多個數據庫的transaction。JOTM就提供了一個開源的多數據庫Transaction應用,它採用了支持XADatasource的Xpool來進行數據源管理。
1,首先需要在項目中引入以下包:
- <!--這個爲jotm的核心包 -->
- <dependency>
- <groupId>org.ow2.jotm</groupId>
- <artifactId>jotm-core</artifactId>
- <version>2.2.1</version>
- </dependency>
- <!-- 這個爲xappol即XaDatasource的一個開源實現 -->
- <dependency>
- <groupId>com.experlog</groupId>
- <artifactId>xapool</artifactId>
- <version>1.5.0</version>
- </dependency>
- <!-- jotm的數據操作類包 -->
- <dependency>
- <groupId>org.ow2.jotm</groupId>
- <artifactId>jotm-datasource</artifactId>
- <version>2.2.1</version>
- </dependency>
- <!-- cmi配置包,jotm初始化時需要相應的包 -->
- <dependency>
- <groupId>org.ow2.cmi</groupId>
- <artifactId>cmi-all</artifactId>
- <version>2.0-RC7</version>
- </dependency>
- <!-- j2ee的api,tomcat中沒有中 -->
- <dependency>
- <groupId>geronimo-spec</groupId>
- <artifactId>geronimo-spec-j2ee-connector</artifactId>
- <version>1.5-rc4</version>
- </dependency>
2,在配置文件的根目錄增加一個carol.properties文件(此文件是一個對命名空間以及jndi的配置支持)
- carol.protocols=jrmp
- carol.jvm.rmi.local.call=true
- carol.start.jndi=false
- carol.start.ns=false
- carol.jndi.java.naming.factory.url.pkgs=org.apache.naming
3,在項目中配置數據源連接,這裏就不能在spring或者hibernate的配置文件中配置數據源了(當然,如果配置成xadatabase可能會正確,沒嘗試過)。在web應用中,在webapp下新建立META-INF文件夾,並新建立context.xml文件。(有些文件說直接在tomcat的service.xml中創建,其實在每個項目中建立更加獨立)tomcat在加載項目時會自動加載此文件。
- <?xml version='1.0' encoding='utf-8'?>
- <Context reloadable="false">
- <Resource name="jdbc/gtip"
- auth="Container"
- type="javax.sql.DataSource"
- factory="org.objectweb.jotm.datasource.DataSourceFactory"
- driverClassName="com.mysql.jdbc.Driver"
- username="root" password=""
- url="jdbc:mysql://localhost/gtip"/>
- <Resource name="jdbc/gtipext"
- auth="Container"
- type="javax.sql.DataSource"
- factory="org.objectweb.jotm.datasource.DataSourceFactory"
- driverClassName="com.mysql.jdbc.Driver"
- username="root" password=""
- url="jdbc:mysql://localhost/gtipext"/>
- <Resource name="UserTransaction"
- auth="Container"
- type="javax.transaction.UserTransaction"/>
- <Transaction factory="org.objectweb.jotm.UserTransactionFactory"
- jotm.timeout="60"/>
這裏建立了兩個數據源,一個爲gtip,一個爲gtipext,且聲明瞭一個默認的事務UserTransaction,同時聲明瞭一個事務提供工廠,表示由jotm提供了一個事務實現。
4,在項目中引用這兩個數據源,即在web.xml中引用數據源。
- <resource-env-ref>
- <description>gtip</description>
- <resource-env-ref-name>jdbc/gtip</resource-env-ref-name>
- <resource-env-ref-type>javax.sql.DataSource</resource-env-ref-type>
- </resource-env-ref>
- <resource-env-ref>
- <description>gtip</description>
- <resource-env-ref-name>jdbc/gtipext</resource-env-ref-name>
- <resource-env-ref-type>javax.sql.DataSource</resource-env-ref-type>
- </resource-env-ref>
這裏即表示需要引用兩個數據源定義
5,在hibernate.cfg.xml中配置數據源,表示引用此數據源
- <property name="connection.datasource">java:comp/env/jdbc/gtipext</property>
這裏就會引用gtipext的數據源,表示hibernate將通過此來尋找相應的datasource實現
6,在spring.xml中配置事務,並通知hibernate引用相關jtaTransactionFactory
- <bean id="jotm" class="com.greejoy.develop.bean.JotmFactoryBean"/>
此是一個jotmBean定義,此bean在Spring3.x版本上已經沒有了,所以可以從spring2.5.6版本上直接將源碼copy過來即可。這裏就是一個工廠bean,去最終創建出一個jotm的current對象(或者不是創建,而是直接通過靜態引用Current.getCurrent()直接獲取)。在這裏,就需要一個手段來獲取到jotm對象。
- <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
- <property name="transactionManager" ref="jotm"/>
- </bean>
事務管理器聲明,表示定義了一個jta的事務管理器(其實它最沒有實現transactionManager接口),但是最終的事務管理將由具體的transactionManager去實現,即將實現transactionManager的工作委派給容器或者具體的實現去做。
- <bean id="sessionFactory"
- class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
- <property name="configLocations" value="classpath*:conf/**/hibernate.cfg.xml"/>
- <property name="jtaTransactionManager" ref="jotm"/>
- </bean>
Hibernate的sessionFactory定義聲明,其中需要一個jtaTransactionManager的屬性定義,當hibernate解析此屬性時,將會指定hibernate的transactionFactory實現爲JTATransactionFactory(默認它會指定爲JDBCTransactionFactory)。
至此,相應的配置工作即結束,在項目中可以使用spring跟以前一個的定義事務控制aop,並交由相關的transactionManager去進行控制。
ps:在spring的AnnotationSessionFactoryBean來說,在配置了jtaTransactionManager之後,spring會往hibernate中配置一個屬性hibernate.transaction.manager_lookup_class,並引用hibernate尋找到相應的transactionManager。但是hibernate在3.5.3版本實現中,並沒有使用lookupClass來尋找transactionManger,而是使用其去尋找userTransactionName,再自己根據userTransactionName通過initContext來取得transactionManager。在srping3.x的實現中,lookupClass(即LocalTransactionManagerLookup)的getUserTransactionName會返回null(雖然其getTransactionManager會返回相應的transactionManager)。hibernate沒有使用這個方法,而是使用了getTransactionUsername方法。兩者顯示沒有很好的對接,或者是其中