使用平台: 1. Mysql 5.0.8 2. ibatis 2.3.4 3. spring 2.5 4. eclipse 3.3 完成账户的转账功能,在出现异常时回滚。由spring管理各个类之间的关系和事务处理。 - 首先建立两个数据库,一个在本机上,一个在远端机上,脚本分别如下
1DROP DATABASE IF EXISTS testdb_a_zhanglong; 2 3CREATE DATABASE testdb_a_zhanglong; 4 5USE testdb_a_zhanglong; 6 7CREATE TABLE Account( 8 aid INT AUTO_INCREMENT, 9 owner VARCHAR(20) NOT NULL, 10 balance Float(10,3) DEFAULT 0.0, 11 PRIMARY KEY(aid) 12); 13 14INSERT INTO Account (owner,balance) VALUES ('zhanglong',10000.00); 15 1DROP DATABASE IF EXISTS testdb_b_zhanglong; 2 3CREATE DATABASE testdb_b_zhanglong; 4 5USE testdb_b_zhanglong; 6 7CREATE TABLE Account( 8 aid INT AUTO_INCREMENT, 9 owner VARCHAR(20) NOT NULL, 10 balance Float(10,3) DEFAULT 0.0, 11 PRIMARY KEY(aid) 12); 13 14INSERT INTO Account (owner,balance) VALUES ('zhengtao',10000.00); 15 在这里两个数据库是完全一样的,是为了方便起见。 - 建立Account的JavaBean类
1package com.aaron.atomikos.domain; 2 3public class Account { 4 5 private int accountId; 6 private String owner; 7 private float balance; 8} 9 - 建立业务层接口AccountService
1package com.aaron.atomikos.service; 2import com.aaron.atomikos.domain.Account; 3public interface AccountService { 4 void addAccount(Account account); 5 6 /** *//** 7 * 转账 8 * @param ida 9 * @param idb 10 * @param amount 11 * @return 12 */ 13 boolean transfer(int ida,int idb,float amount); 14 15 Account getAccountById(int id); 16} 17
- 建立业务层接口实现类AccountServiceImpl
1package com.aaron.atomikos.service.impl; 2import com.aaron.atomikos.dao.AccountDao; 3import com.aaron.atomikos.domain.Account; 4import com.aaron.atomikos.service.AccountService; 5public class AccountServiceImpl implements AccountService { 6 7 // 数据库a的Dao类 8 private AccountDao accountaDao; 9 // 数据库b的Dao类 10 private AccountDao accountbDao; 11 public AccountDao getAccountbDao() { 12 return accountbDao; 13 } 14 public void setAccountbDao(AccountDao accountbDao) { 15 this.accountbDao = accountbDao; 16 } 17 public AccountDao getAccountDao() { 18 return accountaDao; 19 } 20 public void setAccountaDao(AccountDao accountaDao) { 21 this.accountaDao = accountaDao; 22 } 23 @Override 24 public void addAccount(Account account) { 25 accountaDao.insertAccount(account); 26 } 27 28 @Override 29 public boolean transfer(int ida, int idb, float amount) { 30 boolean isSuccess = false; 31 32 Account accounta = new Account(); 33 Account accountb = new Account(); 34 35 //检查账户是否存在 36 if (!accountaDao.isExists(ida) || !accountbDao.isExists(idb)) { 37 throw new RuntimeException("账户" + ida + "或" + idb + "不存在"); 38 } 39 40 //得到账户余额 41 float balancea = accountaDao.getBalance(ida); 42 float balanceb = accountbDao.getBalance(idb); 43 if (balancea < amount) { 44 throw new RuntimeException("账户" + ida + "的余额不足!"); 45 } 46 47 //转账 48 accounta.setAccountId(ida); 49 accountb.setAccountId(idb); 50 accounta.setBalance(balancea - amount); 51 accountb.setBalance(balanceb + amount); 52 53 accountaDao.updateAccount(accounta); 54 55 //两次更新之间如果跑出异常,则会回滚操作 56 // if(true){ 57 // throw new RuntimeException(); 58 // } 59 60 accountbDao.updateAccount(accountb); 61 isSuccess = true; 62 return isSuccess; 63 } 64 65 @Override 66 public Account getAccountById(int id) { 67 return accountaDao.selectAccountById(id); 68 } 69} 70 建立DAO层AccountDao,继承自spring的SqlMapClientDaoSupport,这里为了简单起见,没有使用接口,直接写了具体实现类
1package com.aaron.atomikos.dao; 2 3import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport; 4 5import com.aaron.atomikos.domain.Account; 6 7public class AccountDao extends SqlMapClientDaoSupport { 8 9 /** *//** 10 * 插入账户 11 * 12 * @param account 13 */ 14 public void insertAccount(Account account) { 15 getSqlMapClientTemplate().insert("Account.insertAccount", account); 16 } 17 18 /** *//** 19 * 根据id删除账户 20 * 21 * @param id 22 */ 23 public void deleteAccountById(int id) { 24 getSqlMapClientTemplate().delete("Account.deleteAccountById", id); 25 } 26 27 /** *//** 28 * 更新账户余额 29 * 30 * @param account 31 */ 32 public void updateAccount(Account account) { 33 getSqlMapClientTemplate().update("Account.updateBalance", account); 34 } 35 36 /** *//** 37 * 根据id查找账户 38 * 39 * @param id 40 * @return 41 */ 42 public Account selectAccountById(int id) { 43 return (Account) getSqlMapClientTemplate().queryForObject( 44 "Account.selectAccountById", id); 45 } 46 47 /** *//** 48 * 根据账户编号得到余额 49 * 50 * @param idb 51 * @return 52 */ 53 public float getBalance(int idb) { 54 return Float.parseFloat(getSqlMapClientTemplate().queryForObject( 55 "Account.selectBalance", idb).toString()); 56 } 57 58 /** *//** 59 * 判断账户是否存在 60 * 61 * @param ida 62 * @return 63 */ 64 public boolean isExists(int ida) { 65 return (Integer) getSqlMapClientTemplate().queryForObject( 66 "Account.selectCount", ida) > 0 ? true : false; 67 } 68} 编写iBATIS的sql映射文件Account.xml
1<?xml version="1.0" encoding="UTF-8"?> 2<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd" > 3<sqlMap namespace="Account"> 4 <typeAlias alias="Account" type="com.aaron.atomikos.domain.Account" /> 5 <select id="selectAccountById" parameterClass="int" resultClass="Account"> 6 select * from Account where aid = #value# 7 </select> 8 9 <select id="selectBalance" parameterClass="int" resultClass="float"> 10 select balance from Account where aid = #value# 11 </select> 12 13 <select id="selectCount" parameterClass="int" resultClass="int"> 14 select count(*) from Account where aid = #value# 15 </select> 16 17 <update id="updateBalance" parameterClass="Account"> 18 update Account set balance = #balance# where aid = #accountId# 19 </update> 20 21 <insert id="insertAccount" parameterClass="Account"> 22 insert into Account (owner,balance) values (#owner#,#balance#); 23 </insert> 24</sqlMap> 还要建立两个ibatis的配置文件sql-map-config_A.xml和sql-map-config_B.xml,由于有两个数据库,所以要建两个。具体不列出。 编写spring的applicationContext.xml
1<?xml version="1.0" encoding="UTF-8"?> 2<beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:jee="http://www.springframework.org/schema/jee" 5 xmlns:aop="http://www.springframework.org/schema/aop" 6 xmlns:tx="http://www.springframework.org/schema/tx" 7 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd 8 http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.0.xsd 9 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd 10 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"> 11 12 <!-- 读取数据库连接配置文件 --> 13 <bean id="propertyConfig" 14 class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> 15 <property name="locations"> 16 <list> 17 <value>classpath:jdbc.properties</value> 18 </list> 19 </property> 20 </bean> 21 22 <!-- 数据源A(使用com.atomikos.jdbc.AtomikosDataSourceBean作为数据源) --> 23 <bean id="datasourceA" 24 class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" 25 destroy-method="close"> 26 <property name="uniqueResourceName"> 27 <value>mysql/db_a</value> 28 </property> 29 <property name="xaDataSourceClassName"> 30 <value>${jdbc.driverClassName}</value> 31 </property> 32 <property name="poolSize"> 33 <value>3</value> 34 </property> 35 <property name="xaProperties"> 36 <props> 37 <prop key="url">${jdbc.url}</prop> 38 <prop key="user">${jdbc.username}</prop> 39 <prop key="password">${jdbc.password}</prop> 40 </props> 41 </property> 42 </bean> 43 44 <!-- 数据源B(使用com.atomikos.jdbc.SimpleDataSourceBean作为数据源) --> 45 <bean id="datasourceB" 46 class="com.atomikos.jdbc.SimpleDataSourceBean" init-method="init" 47 destroy-method="close"> 48 <property name="uniqueResourceName"> 49 <value>mysql/db_b</value> 50 </property> 51 <property name="xaDataSourceClassName"> 52 <value>${jdbc2.driverClassName}</value> 53 </property> 54 <property name="xaDataSourceProperties"> 55 <value>user=${jdbc2.username};password=${jdbc2.password};url=${jdbc2.url};encoding=${jdbc2.encoding}</value> 56 </property> 57 <property name="exclusiveConnectionMode"> 58 <value>true</value> 59 </property> 60 <property name="validatingQuery"> 61 <value>SELECT 1</value> 62 </property> 63 <property name="connectionPoolSize"> 64 <value>3</value> 65 </property> 66 </bean> 67 68 <!-- 配置atomikos的事务管理器 --> 69 <bean id="atomikosTransactionManager" 70 class="com.atomikos.icatch.jta.UserTransactionManager" 71 init-method="init" destroy-method="close"> 72 <property name="forceShutdown" value="true" /> 73 </bean> 74 75 <bean id="atomikosUserTransaction" 76 class="com.atomikos.icatch.jta.UserTransactionImp"> 77 <property name="transactionTimeout" value="300" /> 78 </bean> 79 80 <!-- spring的JTA事务管理器 --> 81 <bean id="springTransactionManager" 82 class="org.springframework.transaction.jta.JtaTransactionManager"> 83 <property name="transactionManager" 84 ref="atomikosTransactionManager" /> 85 <property name="userTransaction" ref="atomikosUserTransaction" /> 86 </bean> 87 88 <!-- 通知配置 --> 89 <tx:advice id="txAdvice" 90 transaction-manager="springTransactionManager"> 91 <tx:attributes> 92 <tx:method name="delete*" rollback-for="Exception" /> 93 <tx:method name="save*" rollback-for="Exception" /> 94 <tx:method name="update*" rollback-for="Exception" /> 95 <tx:method name="*" read-only="true" 96 rollback-for="Exception" /> 97 </tx:attributes> 98 </tx:advice> 99 100 <!-- 事务切面配置 --> 101 <aop:config> 102 <aop:pointcut id="serviceOperation" 103 expression="execution(* *..service*..*(..))" /> 104 <aop:advisor advice-ref="txAdvice" 105 pointcut-ref="serviceOperation" /> 106 </aop:config> 107 108 <!-- 根据dataSourceA和sql-map-config_A.xml创建一个SqlMapClientA --> 109 <bean id="sqlMapClientA" 110 class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> 111 <property name="dataSource"> 112 <ref local="datasourceA" /> 113 </property> 114 <property name="configLocation"> 115 <value>classpath:/sql-map-config_A.xml</value> 116 </property> 117 </bean> 118 119 <!-- 根据dataSourceB和sql-map-config_B.xml创建一个SqlMapClientB --> 120 <bean id="sqlMapClientB" 121 class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> 122 <property name="dataSource"> 123 <ref local="datasourceB" /> 124 </property> 125 <property name="configLocation"> 126 <value>classpath:/sql-map-config_B.xml</value> 127 </property> 128 </bean> 129 130 <!--根据sqlMapClientA创建一个SqlMapClientTemplate的模版类实例sqlMapClientTemplateA--> 131 <bean id="sqlMapClientTemplateA" 132 class="org.springframework.orm.ibatis.SqlMapClientTemplate"> 133 <property name="sqlMapClient" ref="sqlMapClientA" /> 134 </bean> 135 136 <!--根据sqlMapClientB创建一个SqlMapClientTemplate的模版类实例sqlMapClientTemplateB--> 137 <bean id="sqlMapClientTemplateB" 138 class="org.springframework.orm.ibatis.SqlMapClientTemplate"> 139 <property name="sqlMapClient" ref="sqlMapClientB" /> 140 </bean> 141 142 <!-- 配置DAO --> 143 <bean id="accountaDao" class="com.aaron.atomikos.dao.AccountDao"> 144 <property name="sqlMapClientTemplate" 145 ref="sqlMapClientTemplateA" /> 146 </bean> 147 148 <bean id="accountbDao" class="com.aaron.atomikos.dao.AccountDao"> 149 <property name="sqlMapClientTemplate" 150 ref="sqlMapClientTemplateB" /> 151 </bean> 152 153 <!-- 配置service --> 154 <bean id="accountService" 155 class="com.aaron.atomikos.service.impl.AccountServiceImpl"> 156 <property name="accountaDao" ref="accountaDao" /> 157 <property name="accountbDao" ref="accountbDao" /> 158 </bean> 159</beans> 在这里,我使用了两个不同类的数据源,只是为了测试,具体配置如上。两者设置属性时有点差别,数据源com.atomikos.jdbc.AtomikosDataSourceBean中设置url,user,password属性是用xaProperties这个属性,它是个Properties类型,所以要用<props>标签分开设置,数据源com.atomikos.jdbc.SimpleDataSourceBean中设置url,user,password属性使用xaDataSourceProperties这个属性,它是String类型,查看源代码可以知道,这个类会把xaDataSourceProperties按“;”进行分割,得到user=root,password=root等几个字符串,再将“=”两边的字符串分别作为属性名和属性值放入一个properties变量中。有一点要注意,
1<value>user=${jdbc2.username};password=${jdbc2.password};url=${jdbc2.url};encoding=${jdbc2.encoding}</value> 中的<value>和user=${jdbc2.username}之间不能换行,因为该类不会忽略换行,会把换行加上user当成一个字符串,之后会报错的。
编写测试类AccountServiceTest
1package com.aaron.atomikos.test; 2 3import org.springframework.context.ApplicationContext; 4import org.springframework.context.support.ClassPathXmlApplicationContext; 5import com.aaron.atomikos.service.AccountService; 6import junit.framework.Assert; 7import junit.framework.TestCase; 8 9public class AccountServiceTest extends TestCase { 10 11 protected static ApplicationContext applicationContext; 12 protected static AccountService accountService; 13 static { 14 applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); 15 accountService = (AccountService) applicationContext.getBean("accountService"); 16 } 17 18 public void testTransfer(){ 19 int ida = 1; 20 int idb = 1; 21 Assert.assertNotNull(accountService.transfer(ida, idb, 666)); 22 } 23} 执行,查看数据库,成功!若将AccountserviceImpl中的抛出异常注释去掉,则会在两个数据库中回滚。
|