之前試過使用Spring動態切換資料庫,通過繼承AbstractRoutingDataSource重寫determineCurrentLookupKey()方法,來決定使用那個資料庫。在開啟事務之前,通過改變lookupKey來達到切換資料來源目的。但是這種方法多個資料庫之前沒辦法做事務管理,或許對於主從(讀寫)資料庫會好用一些,而對於需要一個操作中更新多個資料庫的情況,使用Atomikos或許會更好一些。
本文采用spring4+hibernate4+Atomikos進行事務管理。
依賴dependency
Atomikos要使用的庫使用maven匯入,spring和hibernate的依賴就不一一列出,需要額外新增的dependency如下:
<dependency> <groupId>com.atomikos</groupId> <artifactId>atomikos-util</artifactId> <version>3.9.3</version> </dependency> <dependency> <groupId>com.atomikos</groupId> <artifactId>transactions-jdbc</artifactId> <version>3.9.3</version> </dependency> <dependency> <groupId>com.atomikos</groupId> <artifactId>transactions-jta</artifactId> <version>3.9.3</version> </dependency> <dependency> <groupId>com.atomikos</groupId> <artifactId>transactions</artifactId> <version>3.9.3</version> </dependency> <dependency> <groupId>com.atomikos</groupId> <artifactId>transactions-api</artifactId> <version>3.9.3</version> </dependency> <dependency> <groupId>javax.transaction</groupId> <artifactId>jta</artifactId> <version>1.1</version> </dependency>
使用的資料庫驅動依賴如下:
<!-- 加入mysql驅動依賴包 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.34</version> </dependency> <!-- sql server資料庫驅動 --> <dependency> <groupId>net.sourceforge.jtds</groupId> <artifactId>jtds</artifactId> <version>1.3.1</version> </dependency> <!-- 加入druid資料來源依賴包 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.26</version> </dependency>
有點需要注意的是,druid的版本應該跟上mysql的版本,不然可能找不到MySQL的XADataSource。
配置configuration
配置資料來源xml程式碼:
<bean id="mysql" class="com.alibaba.druid.pool.xa.DruidXADataSource" init-method="init" destroy-method="close"> <property name="url" value="${jdbc_url}" /> <property name="username" value="${jdbc_username}" /> <property name="password" value="${jdbc_password}" /> <!-- 初始化連線大小 --> <property name="initialSize" value="0" /> <!-- 連線池最大使用連線數量 --> <property name="maxActive" value="20" /> <!-- 連線池最小空閒 --> <property name="minIdle" value="0" /> <!-- 獲取連線最大等待時間 --> <property name="maxWait" value="60000" /> <!-- <property name="poolPreparedStatements" value="true" /> <property name="maxPoolPreparedStatementPerConnectionSize" value="33" /> --> <property name="validationQuery" value="SELECT 1" /> <property name="testOnBorrow" value="false" /> <property name="testOnReturn" value="false" /> <property name="testWhileIdle" value="true" /> <!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="60000" /> <!-- 配置一個連線在池中最小生存的時間,單位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="25200000" /> <!-- 開啟removeAbandoned功能 --> <property name="removeAbandoned" value="true" /> <!-- 1800秒,也就是30分鐘 --> <property name="removeAbandonedTimeout" value="1800" /> <!-- 關閉abanded連線時輸出錯誤日誌 --> <property name="logAbandoned" value="true" /> <!-- 監控資料庫 --> <!-- <property name="filters" value="mergeStat" /> --> <!-- <property name="filters" value="stat" /> --> <!--<property name="filters" value="config" /> <property name="connectionProperties" value="config.decrypt=true" />--> </bean> <bean id="mysqlDataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close"> <!-- Set unique name for this DataSource --> <property name="uniqueResourceName"> <value>mysql</value> </property> <!-- Set XADatasource class name--> <property name="xaDataSource" ref="mysql"/> <!-- set properties for datasource connection pool --> <property name="poolSize" value="3" /> <!-- 管理 Connection 被佔用的時間 --> <!-- 如果不設定這個值,Atomikos使用預設的300秒(即5分鐘),那麼在處理大批量資料讀取的時候,一旦超過5分鐘,就會丟擲類似 Resultset is close 的錯誤 --> <property name="reapTimeout"><value>20000</value></property> </bean>
配置SessionFactory的xml程式碼:
<bean id="noticesSessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> <property name="dataSource" ref="mysqlDataSource" /> <property name="jtaTransactionManager" ref="springTransactionManager"></property> <property name="packagesToScan"> <list> <value>com.test.model</value> </list> </property> <property name="hibernateProperties"> <props> <!-- 慎重(推薦禁止) 固定為update --> <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop> <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop> <prop key="hibernate.autoReconnect">true</prop> <prop key="hibernate.show_sql">${hibernate.show_sql}</prop> <prop key="hibernate.format_sql">${hibernate.format_sql}</prop> <prop key="hibernate.use_sql_comments">${hibernate.use_sql_comments}</prop> <prop key="hibernate.jdbc.batch_size">${hibernate.batch_size}</prop> </props> </property> </bean>
如果要使用getCurrentSession()的話,要注意加上:
<property name="jtaTransactionManager" ref="springTransactionManager"></property>
新增多個資料庫的話,只需要把上面的xml再寫一遍。
下面配置atomikos的事務管理器xml程式碼:
<!-- atomikos事務管理器 --> <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close"> <property name="forceShutdown"> <value>true</value> </property> </bean> <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp"> <property name="transactionTimeout" value="300" /> </bean>
spring的事務管理器xml程式碼:
<!-- spring 事務管理器 --> <bean id="springTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="transactionManager" ref="atomikosTransactionManager"/> <property name="userTransaction" ref="atomikosUserTransaction" /> <property name="allowCustomIsolationLevels" value="true"/> </bean> <!-- 註解方式配置事物--> <tx:annotation-driven transaction-manager="springTransactionManager" />
新增transactions.properties檔案:
# SAMPLE PROPERTIES FILE FOR THE TRANSACTION SERVICE
# THIS FILE ILLUSTRATES THE DIFFERENT SETTINGS FOR THE TRANSACTION MANAGER
# UNCOMMENT THE ASSIGNMENTS TO OVERRIDE DEFAULT VALUES;
# Required: factory implementation class of the transaction core.
# NOTE: there is no default for this, so it MUST be specified!
#
com.atomikos.icatch.service=com.atomikos.icatch.standalone.UserTransactionServiceFactory
# Set base name of file where messages are output
# (also known as the 'console file').
#
# com.atomikos.icatch.console_file_name = tm.out
# Size limit (in bytes) for the console file;
# negative means unlimited.
#
# com.atomikos.icatch.console_file_limit=-1
# For size-limited console files, this option
# specifies a number of rotating files to
# maintain.
#
# com.atomikos.icatch.console_file_count=1
# Set the number of log writes between checkpoints
#
# com.atomikos.icatch.checkpoint_interval=500
# Set output directory where console file and other files are to be put
# make sure this directory exists!
#
# com.atomikos.icatch.output_dir = ./
# Set directory of log files; make sure this directory exists!
#
# com.atomikos.icatch.log_base_dir = ./
# Set base name of log file
# this name will be used as the first part of
# the system-generated log file name
#
# com.atomikos.icatch.log_base_name = tmlog
# Set the max number of active local transactions
# or -1 for unlimited.
#
# com.atomikos.icatch.max_actives = 50
# Set the default timeout (in milliseconds) for local transactions
#
# com.atomikos.icatch.default_jta_timeout = 10000
# Set the max timeout (in milliseconds) for local transactions
#
# com.atomikos.icatch.max_timeout = 300000
# The globally unique name of this transaction manager process
# override this value with a globally unique name
#
# com.atomikos.icatch.tm_unique_name = tm
# Do we want to use parallel subtransactions? JTA's default
# is NO for J2EE compatibility
#
#com.atomikos.icatch.serial_jta_transactions=false
# If you want to do explicit resource registration then
# you need to set this value to false.
#
# com.atomikos.icatch.automatic_resource_registration=true
# Set this to WARN, INFO or DEBUG to control the granularity
# of output to the console file.
#
# com.atomikos.icatch.console_log_level=WARN
# Do you want transaction logging to be enabled or not?
# If set to false, then no logging overhead will be done
# at the risk of losing data after restart or crash.
#
# com.atomikos.icatch.enable_logging=true
# Should two-phase commit be done in (multi-)threaded mode or not?
# Set this to false if you want commits to be ordered according
# to the order in which resources are added to the transaction.
#
# NOTE: threads are reused on JDK 1.5 or higher.
# For JDK 1.4, thread reuse is enabled as soon as the
# concurrent backport is in the classpath - see
# http://mirrors.ibiblio.org/pub/mirrors/maven2/backport-util-concurrent/backport-util-concurrent/
#
# com.atomikos.icatch.threaded_2pc=false
# Should shutdown of the VM trigger shutdown of the transaction core too?
#
# com.atomikos.icatch.force_shutdown_on_vm_exit=false
好了,Atomikos算是配置完成,我們可以寫程式碼測試一下。測試的程式碼這裡就不寫出了。
效能優化
儘管這個軟體有著很大的優勢,但是想要更好的發揮其作用,可以按以下的方法優化:
l 更高的記憶體,意味著更高的吞吐量(每秒的事務數目)。
l 使連線池儘可能的大。
l 一旦你不需要的連線請馬上關閉它們。不要把你的應用程式放在快取裡,讓內部連線池為你做這些,這將促使更高效的連線使用。
l 不要讓活動的事務閒置:終止所有情況下的事務,尤其是在異常報錯情況下的事務。這將減少資料庫的鎖定時間,並且最大效率的處理啟用的使用。
總結
這麼長的配置檔案要小心點寫,我的粗心結果就是datasource亂套了,導致花了不少時間排錯。當然還有各種亂七八糟的糟心問題,這些問題會在後面的文章中列出來,大家可以注意避免。最後用上Atomikos的分散式事務,沒有了各種的髒資料,舒心了不少。