事务管理:
分global事务管理和local事务管理,global要跨越多个事务资源,而local一般是本地资源,如JDBC,比较好控制。
下面的例子展示怎么Spring的事务管理。声明式事务管理
其实个人感觉还是手工控制事务舒服一些,因为被Spring管理后,觉得真的很简单了,特感觉没有深度东西可以做了。。。个人感受。
此代码是ProSpring书上面,经过简单改造,用Mysql数据库,把不完整的代码补充完整。
数据库:
DROP TABLE IF EXISTS `accounts`;
CREATE TABLE `accounts` (
`AccountId` int(10) unsigned NOT NULL auto_increment,
`AccountNumber` varchar(20) NOT NULL,
`Balance` decimal(10,2) NOT NULL,
PRIMARY KEY (`AccountId`),
UNIQUE KEY `AccountId` (`AccountNumber`)
) ENGINE=InnoDB DEFAULT CHARSET=gb2312;
DROP TABLE IF EXISTS `history`;
CREATE TABLE `history` (
`HistoryId` bigint(20) unsigned NOT NULL auto_increment,
`Account` varchar(20) NOT NULL,
`Operation` varchar(50) NOT NULL,
`Amount` decimal(10,2) NOT NULL,
`TransactionDate` timestamp NOT NULL,
`TargetAccount` varchar(20) default NULL,
UNIQUE KEY `HistoryId` (`HistoryId`)
) ENGINE=InnoDB DEFAULT CHARSET=gb2312;
--
-- Dumping data for table `history`
--
接口操作方法:
package com.apress.prospring.ch12.business;
import java.math.BigDecimal;
import com.apress.prospring.ch12.domain.Account;
public interface AccountManager {
/**
* 插入账户
* @param account
*/
public void insert(Account account);
/**
* 往账户里面存钱
* @param accountId
* @param amount
*/
public void deposit(String accountId, BigDecimal amount);
/**
* 从sourceAccount往targetAccount转账
* @param sourceAccount
* @param targetAccount
* @param amount
*/
public void transfer(String sourceAccount, String targetAccount, BigDecimal amount);
/**
* 数据库中账户的数量
* @return
*/
public int count();
}
抽象类实现:
package com.apress.prospring.ch12.business;
import java.math.BigDecimal;
import java.util.Date;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.InitializingBean;
import com.apress.prospring.ch12.domain.Account;
import com.apress.prospring.ch12.domain.AccountDao;
import com.apress.prospring.ch12.domain.History;
import com.apress.prospring.ch12.domain.HistoryDao;
public abstract class AbstractAccountManager
implements InitializingBean, AccountManager {
private AccountDao accountDao;
private HistoryDao historyDao;
protected void doInsert(Account account) {
getAccountDao().insert(account);
History history = new History();
history.setAccount(account.getAccountNumber());
history.setAmount(account.getBalance());
history.setOperation("Initial deposit");
history.setTargetAccount(null);
history.setTransactionDate(new Date());
getHistoryDao().insert(history);
}
protected void doDeposit(String accountId, BigDecimal amount) {
History history = new History();
history.setAccount(accountId);
history.setAmount(amount);
history.setOperation("Deposit");
history.setTargetAccount(null);
history.setTransactionDate(new Date());
getAccountDao().updateBalance(accountId, amount);
getHistoryDao().insert(history);
}
protected void doTransfer(String sourceAccount,
String targetAccount, BigDecimal amount) {
Account source = getAccountDao().getAccountById(sourceAccount);
if (source.getBalance().compareTo(amount) > 0) {
// transfer allowed
getAccountDao().updateBalance(sourceAccount, amount.negate());
getAccountDao().updateBalance(targetAccount, amount);
History history = new History();
history.setAccount(sourceAccount);
history.setAmount(amount);
history.setOperation("Paid out");
history.setTargetAccount(targetAccount);
history.setTransactionDate(new Date());
getHistoryDao().insert(history);
history = new History();
history.setAccount(targetAccount);
history.setAmount(amount);
history.setOperation("Paid in");
history.setTargetAccount(sourceAccount);
history.setTransactionDate(new Date());
getHistoryDao().insert(history);
} else {
throw new RuntimeException("Not enough money");
}
}
protected int doCount() {
return getAccountDao().getCount();
}
public final void afterPropertiesSet() throws Exception {
if (accountDao == null) throw new
BeanCreationException("Must set accountDao");
if (historyDao == null) throw new
BeanCreationException("Must set historyDao");
initManager();
}
protected void initManager() {
}
protected AccountDao getAccountDao() {
return accountDao;
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
protected HistoryDao getHistoryDao() {
return historyDao;
}
public void setHistoryDao(HistoryDao historyDao) {
this.historyDao = historyDao;
}
}
默认实现:
package com.apress.prospring.ch12.business;
import java.math.BigDecimal;
import com.apress.prospring.ch12.domain.Account;
public class DefaultAccountManager extends AbstractAccountManager {
public void insert(Account account) {
doInsert(account);
}
public void deposit(String accountId, BigDecimal amount) {
doDeposit(accountId, amount);
}
public void transfer(String sourceAccount, String targetAccount, BigDecimal amount) {
doTransfer(sourceAccount, targetAccount, amount);
}
public int count() {
return 0;
}
}
数据库操作的两个接口类dao
package com.apress.prospring.ch12.domain;
import java.math.BigDecimal;
public interface AccountDao {
/**
* get account by account number
* @param accountId
* @return
*/
public Account getAccountById(String accountId);
/**
* @param sourceAccount
* @param amount
*/
public void updateBalance(String sourceAccount, BigDecimal amount);
/**
* @param account
*/
public void insert(Account account);
/**
* @return
*/
public int getCount();
}
package com.apress.prospring.ch12.domain;
// in HistoryDao.java:
import java.util.List;
public interface HistoryDao {
public List getByAccount(int account);
public History getById(int historyId);
public void insert(History history);
}
AccountDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap>
<typeAlias type="com.apress.prospring.ch12.domain.Account" alias="account"/>
<resultMap class="account" id="result">
<result property="accountNumber" column="AccountNumber"/>
<result property="balance" column="Balance"/>
</resultMap>
<insert id="insertAccout" parameterClass="account">
insert into accounts (AccountNumber, Balance) values (#accountNumber#,#balance#)
</insert>
<select id="getAccountById" resultMap="result" parameterClass="int" >
select * from accounts where AccountNumber=#value#
</select>
<update id="update" parameterClass="map">
update accounts set Balance=#balance# where AccountNumber=#accountNumber#
</update>
</sqlMap>
HistoryDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap>
<typeAlias type="com.apress.prospring.ch12.domain.History" alias="history"/>
<resultMap class="history" id="result">
<result property="account" column="Account"/>
<result property="operation" column="Operation"/>
<result property="amount" column="Amount"/>
<result property="transactionDate" column="TransactionDate"/>
<result property="targetAccount" column="TargetAccount"/>
</resultMap>
<insert id="insertHistory" parameterClass="history">
insert into history (Account,Operation,Amount,TransactionDate, TargetAccount)
values (#account#,#operation#,#amount#,#transactionDate#,#targetAccount#)
</insert>
</sqlMap>
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- Data source bean -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" >
<property name="driverClassName">
<value>com.mysql.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost/test</value></property>
<property name="username"><value>root</value></property>
<property name="password"><value>G@111111</value></property>
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource"><ref local="dataSource"/></property>
</bean>
<bean id="sqlMapClient"
class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation"><value>sqlMapConfig.xml</value></property>
</bean>
<bean id="accountDao"
class="com.apress.prospring.ch12.data.SqlMapClientAccountDao">
<property name="dataSource"><ref local="dataSource"/></property>
<property name="sqlMapClient"><ref local="sqlMapClient"/></property>
</bean>
<bean id="historyDao"
class="com.apress.prospring.ch12.data.UnreliableSqlMapClientHistoryDao">
<property name="dataSource"><ref local="dataSource"/></property>
<property name="sqlMapClient"><ref local="sqlMapClient"/></property>
</bean>
<bean id="accountManagerTarget"
class="com.apress.prospring.ch12.business.DefaultAccountManager">
<property name="accountDao"><ref local="accountDao"/></property>
<property name="historyDao"><ref local="historyDao"/></property>
</bean>
<bean id="accountManager"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager">
<ref bean="transactionManager"/></property>
<property name="target"><ref local="accountManagerTarget"/></property>
<property name="transactionAttributes">
<props>
<prop key="insert*">
PROPAGATION_REQUIRED, ISOLATION_READ_COMMITTED</prop>
<prop key="transfer*">
PROPAGATION_REQUIRED, ISOLATION_SERIALIZABLE</prop>
<prop key="deposit*">
PROPAGATION_REQUIRED, ISOLATION_READ_COMMITTED</prop>
</props>
</property>
</bean>
</beans>
package com.apress.prospring.ch12.business;
import java.math.BigDecimal;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.apress.prospring.ch12.domain.Account;
public class AccountManagerTest {
private ApplicationContext context;
private void run() {
System.out.println("Initializing application");
context = new ClassPathXmlApplicationContext(new String[] {
"applicationContext.xml" });
AccountManager manager = (AccountManager)context.getBean(
"accountManager");
int count = manager.count();
int failures = 0;
int attempts = 100;
for (int i = 0; i < attempts; i++) {
Account a = new Account();
a.setBalance(new BigDecimal(10));
a.setAccountNumber("123 " + i);
try {
manager.insert(a);
} catch (RuntimeException ex) {
System.out.println("Failed to insert account " + ex.getMessage());
failures++;
}
}
System.out.println("Attempts : " + attempts);
System.out.println("Failures : " + failures);
System.out.println("Prev count: " + count);
System.out.println("New count : " + manager.count());
System.out.println("Done");
}
public static void main(String[] args) {
new AccountManagerTest().run();
}
}