事务管理是AOP的应用。在Spring中使用三种方法,来实现对事务的管理:
- 使用事务代理工厂管理事务
- 使用事务注解管理事务
- 使用AspectJ的AOP配置管理事务
一、Spring事务管理API
Spring的事务管理,主要用到两个事务相关的接口。
(1)事务管理器接口
事务管理器是PlatformTransactionManager接口对象。其主要完成事物的提交、回滚,以及事务的状态信息。
看一下其官方API:
A:常用的两个实现类:
- DataSourceTransactionManager:使用JDBC或者是iBatis进行持久化数据时使用。
- HibernateTransactionManager:使用Hibernate进行持久化数据时候使用
B:Spring的回滚方式
Spring事物的默认回滚方式是:运行时异常发生回滚,受查时异常发生提交。受查时异常也可以手工设置回滚。
什么是运行时异常和受查时异常?
(2)事务管理接口:
事务定义接口TransactionDefination中定义了事物描述相关的三类常量:事务隔离级别、事务传播行为、事务默认超时时限。
A:事务隔离级别:
Spring定义了五个事务隔离界别:
- DEFAULT:采用DB默认的事务隔离界别。
- READ_UNCOMMITTED:读未提交。未解决任何并发问题。
- READ_COMMITTED:读已提交。解决脏读,存在不可重复读和幻读。
- REPEATABLE_READ:可重复读。解决脏读、不可重复读。存在幻读
- SERIALIZABLE:串行化。不存在并发问题。
B:事务传播行为常量
事务传播行为指的是:处于不同事物中的方法在相互调用时候,执行期间事务的维护情况。比如说A事务中的方法doSome调用B事务中的方法doOther,在调用执行期间,事务的维护情况,这就叫做事务传播行为。
这里有七个事务传播行为常量:
- REQUIRED:指定的方法必须在事务内执行
- SUPPORTS:指定的方法支持当前事务,但是若没有当前事务,也可以以非事务方式执行。
- MANDATORY:指定的方法必须在当前事务内执行,若没有当前事务,则直接抛出异常。
- REQUIRES_NEW:总是新建一个事务,若当前存在事务,则挂起,直到新事务执行完毕。
- NOT_SUPPORTS:指定的方法不能再事务环境中执行,若当前存在事务,就将当前事务挂起
- NEVER:指定的方法不能在事务环境中执行,若当前存在事务,就直接抛出异常
- NESTED:指定的方法2必须在事务内执行,若当前存在事务,则直接嵌套事务内执行:若没有当前事务,则创建一个新事务。
C:默认事务超时时限
上面的这些概念太抽象了,先知道,然后直接看例子。
现在有一个需求,那就是买股票的例子。现在存在两个实体Account(银行账户)和Stock(股票账户)。买完了股票,银行账户应该扣除相应的金额。股票账户应该增加相应的股票。
二、环境搭建、
1、创建数据库和表
2、实体类
首先是Account:
public class Account {
private Integer aid;
private String aname;
private double balance; // 余额
//有参构造方法和无惨构造方法
}
然后是股票类Stock
public class Stock {
private Integer sid;
private String sname; // 股票名称
private int count; // 股票数量
//get和set方法
//toString方法
//有参和无参构造器
}
3、定义接口
public interface IAccountDao {
void insertAccount(String aname, double money);
void updateAccount(String aname, double money, boolean isBuy);
}
还有股票接口
public interface IStockDao {
void insertStock(String sname, int amount);
void updateStock(String sname, int count, boolean isBuy);
}
4、定义接口实现类
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
@Override
public void insertAccount(String aname, double money) {
String sql="insert into account(aname,balance) values(?,?)";
this.getJdbcTemplate().update(sql, aname,money);
}
@Override
public void updateAccount(String aname, double money, boolean isBuy) {
String sql=null;
if(isBuy){
sql="update account set balance=balance-? where aname=? ";
}else{
sql="update account set balance=balance+? where aname=? ";
}
this.getJdbcTemplate().update(sql,money,aname);
}
}
另一个实现类:
public class StockDaoImpl extends JdbcDaoSupport implements IStockDao {
@Override
public void insertStock(String sname, int amount) {
String sql="insert into stock(sname,count) values(?,?)";
this.getJdbcTemplate().update(sql, sname,amount);
}
@Override
public void updateStock(String sname, int count, boolean isBuy) {
String sql="update stock set count=count-? where sname=?";
if(isBuy){
sql="update stock set count=count+? where sname=?";
}
this.getJdbcTemplate().update(sql,count,sname);
}
}
5、定义Service接口
public interface IBuyStockService {
void openAccount(String aname,double money);
void openStock(String sname,int amount);
void buyStock(String aname,double money,String sname,int count);
}
6、定义Service接口的实现类
public class BuyStockServiceImpl implements IBuyStockService {
private IAccountDao adao;
private IStockDao sdao;
public void setAdao(IAccountDao adao) {
this.adao = adao;
}
public void setSdao(IStockDao sdao) {
this.sdao = sdao;
}
@Override
public void openAccount(String aname, double money) {
adao.insertAccount(aname,money);
}
@Override
public void openStock(String sname, int amount) {
sdao.insertStock(sname,amount);
}
@Override
public void buyStock(String aname, double money, String sname, int count) {
boolean isBuy=true;
adao.updateAccount(aname,money,isBuy);
sdao.updateStock(sname,count,isBuy);
}
}
7、配置文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 注册数据源:C3P0 -->
<bean id="myDateSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 注册数据库属性文件:方式二 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 注册账户dao -->
<bean id="accountDao" class="com.fdd.dao.AccountDaoImpl" >
<property name="dataSource" ref="myDateSource"></property>
</bean>
<!-- 注册股票dao -->
<bean id="stockDao" class="com.fdd.dao.StockDaoImpl" >
<property name="dataSource" ref="myDateSource"></property>
</bean>
<!-- 注册service -->
<bean id="buyStockService" class="com.fdd.service.BuyStockServiceImpl">
<property name="adao" ref="accountDao"></property>
<property name="sdao" ref="stockDao"></property>
</bean>
</beans>
8、测试
public class MyTest {
private IBuyStockService service;
@Before
public void before(){
String resource = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(resource);
service =(IBuyStockService) ac.getBean("buyStockService");
}
@Test
public void test01(){
service.openAccount("张三", 10000);
service.openStock("西工大", 0);
}
@Test
public void test02(){
service.buyStock("张三", 2000, "西工大", 5);
}
}
看结果:
首先我们在test01中在张三的账户上存了1万,然后再西工大的开了户
然后再test02中张三花了2000,买了西工大5只股票。此时Account应该还有8000,然后Stock有5只股票。
三、使用Spring的事务代理工厂管理事务
在上面的例子中,我们使用的是Spring的事务管理机制。下面使用事务代理。事务代理使用的是TransactionProxyFactoryBean,这个类需要初始化一些属性:
- transactionManager:事务管理器
- target:目标对象,也就是service的实现类
- transactionAttributes:事务属性设置
在配置代理的时候,对于受查异常的回滚方式:
- -异常方式:但发生指定的异常时候事务回滚
- +异常方式:当发生指定的异常时事务提交
下面看看使用代理工厂如何实现事务的管理:
1、导入jar
这里需要导入两个jar
- com.springsource.org.aopaliance-1.0.0.jar
- spring-aop-4.2.1.RELEASE.jar
2、添加事务管理器DataSourceTransactionManager
3、添加事务代理TransactionProxyFactoryBean
4、测试
四、使用Spring的事务注解管理事务
通过@Transactional注解方式,也可以将事务织入到相应方法中。而事务注解方式,只需要添加一个tx标签,告诉Spring使用注解来完成事务的织入。
<tx:annotion-driven transaction-maneger="myTransactionManager"/>
这里只列出了一个属性,还有其他的一些属性:
- propagation:用于设置事务传播属性。该类型是枚举类型,比如Propagation.REQUIRED
- isolation:用于设置事务的隔离级别
- readOnly:用于设置该方法对数据库的操作是否是只读的。
- timeout:用于设置本操作与数据库连接的超时时限
- rollbackFor:指定需要回滚的异常类
- rollbackForClassName:指定需要回滚的异常类类名
- noRollbackForClassName:指定不需要回滚的异常类类名
1、在容器中添加事务管理器
2、在Service的实现类中添加注解:
3、修改配置文件
4、修改测试类
五、使用AspectJ的AOP配置管理事务(重要)
前面说了一大堆,发现,各种配置,代码的修改。不方便。并且每一个目标类都需要配置目标代理。在这里使用AspectJ来管理事务。直接上代码看吧
1、导入jar
在这里需要四个jar:
- 两个前面使用注解导入的jar
- com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
- spring-aspects-4.2.1.RELEASE
2、添加事务管理
3、配置事务通知
为事务通知设置相关属性,指定要将事务以什么方式植入给那些方法。比如在之前的买股票的例子,再买股票的时候如果发生了异常,这时候就需要事务回滚。在这里StockException是自定义的股票异常