本文数据库环境为mysql,orm环境为ibatis
数据库脚本:
create table account(id varchar(20) primary key,name varchar(20));
create table history(id varchar(20) primary key,name varchar(20),accountid varchar(20),
constraint foreign key(accountid) references account(id));
我们的事务控制,就是想让account表和history同步,也就是account和history的数据必须同时更新,同时插入
DAO接口:
package ch12.transaction.declare;

import java.util.List;


public interface IDAO ...{
public void insertAccount(Account account);
public void insertHistory(History history,int count) throws MyException ;
public List getAllAccount();
public List getAllHistory();
}

Service接口:
package ch12.transaction.declare;

import java.util.List;


public interface AccountManager ...{
public void createAccount(Account account,History history,int count) throws MyException;
public List getAllAccount();
public List getAllHistory();
}

Domain:
package ch12.transaction.declare;


public class Account ...{
private String id;
private String name;

public Account()...{
}

public String getId() ...{
return id;
}

public void setId(String id) ...{
this.id = id;
}

public Account(String id, String name) ...{
super();
this.id = id;
this.name = name;
}

public String getName() ...{
return name;
}

public void setName(String name) ...{
this.name = name;
}
}


package ch12.transaction.declare;


public class History ...{
private String id;
private String name;
private Account account;

public History()...{
}

public History(String id, String name, Account account) ...{
super();
this.id = id;
this.name = name;
this.account = account;
}

public String getId() ...{
return id;
}

public void setId(String id) ...{
this.id = id;
}

public String getName() ...{
return name;
}

public void setName(String name) ...{
this.name = name;
}

public Account getAccount() ...{
return account;
}

public void setAccount(Account account) ...{
this.account = account;
}
}

ibatis配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMapConfig
PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-config-2.dtd">
<sqlMapConfig>

<sqlMap resource="ch12/transaction/declare/Account.xml" />
<sqlMap resource="ch12/transaction/declare/History.xml" />
</sqlMapConfig>
ibatis sql map:
history.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="ch12.transaction.declare.History" alias="history"/>
<resultMap id="historyTest" class="history" >
<result column="id" property="id" jdbcType="VARCHAR" />
<result column="name" property="name" jdbcType="VARCHAR" />
</resultMap>

<insert id="insertHistory" parameterClass="history">
insert into history (id,name,accountid) values (#id#,#name#,#account.id#)
</insert>
<select id="getAllHistory" resultMap="historyTest">
select * from history
</select>
</sqlMap>




account.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="ch12.transaction.declare.Account" alias="account"/>
<resultMap id="accountTest" class="account" >
<result column="id" property="id" jdbcType="VARCHAR" />
<result column="name" property="name" jdbcType="VARCHAR" />
</resultMap>

<select id="getAllAccount" resultMap="accountTest">
select * from account
</select>

<insert id="insertAccount" parameterClass="account">
insert into account (id,name) values (#id#,#name#)
</insert>
</sqlMap>

自定义异常:
package ch12.transaction.declare;


public class MyException extends Exception ...{

public MyException(String msg)...{
super(msg);
}
}

DAO实现:
package ch12.transaction.declare;

import java.sql.SQLException;
import java.util.List;

import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport;


public class TestDAO extends SqlMapClientDaoSupport implements IDAO ...{


public List getAllHistory() ...{
return this.getSqlMapClientTemplate().queryForList("getAllHistory");
}

public void insertAccount(Account account) ...{
this.getSqlMapClientTemplate().insert("insertAccount",account);
}

public void insertHistory(History history,int count) throws MyException ...{

if(count%3==0)...{
//当count能被三整除时,模拟错误,促使事务回滚
throw new MyException("divide by 3");
//如果使用RuntimeException,则可以直接抛出,并不需要再配置文件中配置"-MyException"参数
//因为RuntimeException Spring会自动进行回滚,而非RuntimeException出现,如果不配置参数,spring则会提交

}else...{
this.getSqlMapClientTemplate().insert("insertHistory",history);
}
}

public List getAllAccount() ...{
return this.getSqlMapClientTemplate().queryForList("getAllAccount");
}
}

service实现:
package ch12.transaction.declare;

import java.util.List;


public class AccountManagerImpl implements AccountManager ...{

public List getAllHistory() ...{
return testDAO.getAllHistory();
}
private TestDAO testDAO;

public List getAllAccount() ...{

return testDAO.getAllAccount();
}

public TestDAO getTestDAO() ...{
return testDAO;
}

public void setTestDAO(TestDAO testDAO) ...{
this.testDAO = testDAO;
}

public void createAccount(Account account,History history,int count) throws MyException...{
testDAO.insertAccount(account);
testDAO.insertHistory(history,count);
}

}

spring配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">


<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName">
<value>com.mysql.jdbc.Driver</value>
</property>
<property name="username">
<value>root</value>
</property>
<property name="password">
<value>1234</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost:3306/spring</value>
</property>
</bean>

<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<!-- 此处应注入ibatis配置文件,而非sqlMap文件,否则会出现“there is no statement.....异常” -->
<property name="configLocation">
<value>ch12/transaction/declare/sqlMapConfig.xml</value>
</property>

</bean>
<bean id="testDAO" class="ch12.transaction.declare.TestDAO">
<property name="dataSource">
<ref bean="dataSource"/>
</property>
<property name="sqlMapClient">
<ref bean="sqlMapClient"/>
</property>
</bean>

<bean id="managerTarget" class="ch12.transaction.declare.AccountManagerImpl">
<property name="testDAO">
<ref local="testDAO"/>
</property>
</bean>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource">
<ref bean="dataSource"/>
</property>
</bean>

<bean id="manager" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager">
<ref bean="transactionManager"/>
</property>
<property name="target">
<ref bean="managerTarget"/>
</property>
<property name="transactionAttributes">
<props>
<prop key="create*">PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED,-MyException</prop>
</props>
</property>
</bean>

</beans>
特别说明:我们在指定目标方法中允许抛出异常,同时我们也定义当异常发生时捕获代理应该进行何种操作,默认情况下,如果RuntimeException抛出,事务管理器进行回滚,如果checked exception抛出,则提交事务,但我们可以通过声明改变这以腥味,如果在异常前追加“-”则代理铺货异常时会自动回滚,如果前缀时"+"则代理将提交事务,如本文中的-MyException,默认情况下MyException会促使事务提交,我们加了前缀“-”,则代理捕获此异常时会进行回滚
测试代码:
package ch12.transaction.declare;



import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.sql.SQLException;


public class Test ...{


/** *//**
* @param args
*/

public static void main(String[] args) ...{
ApplicationContext context=new ClassPathXmlApplicationContext("ch12/transaction/declare/applicationContext.xml");
AccountManager manager=(AccountManager)context.getBean("manager");
int count=0;

for (int i = 1; i <= 10; i++) ...{
count=i;
Account account=new Account(String.valueOf(count),"name1");
History history=new History(String.valueOf(count),"name1",account);

try ...{
manager.createAccount(account, history,count);

} catch (MyException e) ...{
System.out.println(e.getMessage());
}
}
System.out.println("account data:");
List result=manager.getAllAccount();

for (Object acc : result) ...{
System.out.println(((Account)acc).getId());
}
System.out.println("history data:");
List result1=manager.getAllHistory();

for (Object acc1 : result1) ...{
System.out.println(((History)acc1).getId());
}
}
}

结果:
divide by 3
divide by 3
divide by 3
account data:
1
10
2
4
5
7
8
history data:
1
10
2
4
5
7
8
可以看到,能被三整除的操作,account虽然正确,但由于history失败,所以,两边数据保持了一致性