大家所了解的事务Transaction,它是一些列严密操作动作,要么都操作完成,要么都回滚撤销。Spring事务管理基于底层数据库本身的事务处理机制。数据库事务的基础,是掌握Spring事务管理的基础。这篇总结下Spring事务。 事务具备ACID四种特性,ACID是Atomic(原子性)、Consistency(一致性)、Isolation(隔离性)和Durability(持久性)的英文缩写。
事务最基本的操作单元,要么全部成功,要么全部失败,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。
事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。
指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。
事务传播行为就是多个事务方法调用时,如何定义方法间事务的传播。Spring定义了7中传播行为:
编程式事务基本已经OUT了,所有就省略了,主要回顾 下声明式事务。 以用户购买股票为例 新建用户对象、股票对象、以及dao、service层
/** * 账户对象 * */ public class Account { private int accountid; private String name; private int balance; public int getAccountid() { return accountid; } public void setAccountid(int accountid) { this.accountid = accountid; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getBalance() { return balance; } public void setBalance(int balance) { this.balance = balance; } } 复制代码
/** * 股票对象 * */ public class Stock { private int stockid; private String name; private Integer count; public Stock() { super(); } public Stock(int stockid, String name, Integer count) { super(); this.stockid = stockid; this.name = name; this.count = count; } public int getStockid() { return stockid; } public void setStockid(int stockid) { this.stockid = stockid; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getCount() { return count; } public void setCount(Integer count) { this.count = count; } } 复制代码
DAO层
public interface AccountDao { void addAccount(String name,double money); void updateAccount(String name,double money,boolean isbuy); } 复制代码
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { @Override public void addAccount(String name, double money) { String sql = "insert account(name,balance) values(?,?);"; this.getJdbcTemplate().update(sql,name,money); } @Override public void updateAccount(String name, double money, boolean isbuy) { String sql = "update account set balance=balance+? where name=?"; if(isbuy) sql = "update account set balance=balance-? where name=?"; this.getJdbcTemplate().update(sql, money,name); } } 复制代码
public interface StockDao { void addStock(String sname,int count); void updateStock(String sname,int count,boolean isbuy); } public class StockDaoImpl extends JdbcDaoSupport implements StockDao { @Override public void addStock(String sname, int count) { String sql = "insert into stock(name,count) values(?,?)"; this.getJdbcTemplate().update(sql,sname,count); } @Override public void updateStock(String sname, int count, boolean isbuy) { String sql = "update stock set count = count-? where name = ?"; if(isbuy) sql = "update stock set count = count+? where name = ?"; this.getJdbcTemplate().update(sql, count,sname); } } 复制代码
Service
public interface BuyStockService { public void addAccount(String accountname, double money); public void addStock(String stockname, int amount); public void buyStock(String accountname, double money, String stockname, int amount) throws BuyStockException; } 复制代码
public class BuyStockServiceImpl implements BuyStockService{ private AccountDao accountDao; private StockDao stockDao; @Override public void addAccount(String accountname, double money) { accountDao.addAccount(accountname,money); } @Override public void addStock(String stockname, int amount) { stockDao.addStock(stockname,amount); } @Override public void buyStock(String accountname, double money, String stockname, int amount) throws BuyStockException { boolean isBuy = true; accountDao.updateAccount(accountname, money, isBuy); if(isBuy==true){ throw new BuyStockException("购买股票发生异常"); } stockDao.updateStock(stockname, amount, isBuy); } public AccountDao getAccountDao() { return accountDao; } public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } public StockDao getStockDao() { return stockDao; } public void setStockDao(StockDao stockDao) { this.stockDao = stockDao; } } 复制代码
自定义异常类
public class BuyStockException extends Exception { public BuyStockException() { super(); } public BuyStockException(String message) { super(message); } } 复制代码
<?xml version="1.0" encoding="UTF-8"?> <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" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-aop-4.2.xsd "> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 注册数据源 C3P0 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" > <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="jdbcUrl" value="${jdbc.url}"></property> <property name="user" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <bean id="accountDao" class="com.zwd.spring.transaction.daoImpl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="stockDao" class="com.zwd.spring.transaction.daoImpl.StockDaoImpl"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="buyStockService" class="com.zwd.spring.transaction.serviceImpl.BuyStockServiceImpl"> <property name="accountDao" ref="accountDao"></property> <property name="stockDao" ref="stockDao"></property> </bean> <!-- 事务管理器 --> <bean id="myTracnsactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 事务代理工厂 --> <!-- 生成事务代理对象 --> <bean id="serviceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager" ref="myTracnsactionManager"></property> <property name="target" ref="buyStockService"></property> <property name="transactionAttributes"> <props> <!-- 主要 key 是方法 ISOLATION_DEFAULT 事务的隔离级别 PROPAGATION_REQUIRED 传播行为 --> <prop key="add*">ISOLATION_DEFAULT,PROPAGATION_REQUIRED</prop> <!-- -Exception 表示发生指定异常回滚,+Exception 表示发生指定异常提交 --> <prop key="buyStock">ISOLATION_DEFAULT,PROPAGATION_REQUIRED,-BuyStockException</prop> </props> </property> </bean> </beans> 复制代码
通过结果和观察数据库数据变化,可以看出我们声明的异常回滚发生了效果。
其他类不做改变,只改变购买股票接口实现类和配置文件
public class BuyStockServiceImpl implements BuyStockService{ private AccountDao accountDao; private StockDao stockDao; @Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED) @Override public void addAccount(String accountname, double money) { accountDao.addAccount(accountname,money); } @Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED) @Override public void addStock(String stockname, int amount) { stockDao.addStock(stockname,amount); } public BuyStockServiceImpl() { // TODO Auto-generated constructor stub } @Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED,rollbackFor=BuyStockException.class) @Override public void buyStock(String accountname, double money, String stockname, int amount) throws BuyStockException { boolean isBuy = true; accountDao.updateAccount(accountname, money, isBuy); if(isBuy==true){ throw new BuyStockException("购买股票发生异常"); } stockDao.updateStock(stockname, amount, isBuy); } public AccountDao getAccountDao() { return accountDao; } public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } public StockDao getStockDao() { return stockDao; } public void setStockDao(StockDao stockDao) { this.stockDao = stockDao; } } 复制代码
<context:property-placeholder location="classpath:jdbc.properties"/> <!-- 注册数据源 C3P0 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" > <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="jdbcUrl" value="${jdbc.url}"></property> <property name="user" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <bean id="accountDao" class="com.zwd.spring.transaction.daoImpl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="stockDao" class="com.zwd.spring.transaction.daoImpl.StockDaoImpl"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="buyStockService" class="tcom.zwd.spring.transaction.serviceImpl.BuyStockServiceImpl"> <property name="accountDao" ref="accountDao"></property> <property name="stockDao" ref="stockDao"></property> </bean> <!-- 事务管理器 --> <bean id="myTracnsactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 启用事务注解 --> <tx:annotation-driven transaction-manager="myTracnsactionManager"/> 复制代码
可以看出,使用@Transactional注解的方式配置文件要简单的多,将事务交给事务注解驱动。它有个缺陷是他会把所有的连接点都作为切点将事务织入进去, 显然只需要在buyStock()方法织入事务即可。下面看看最后一种实现,它就可以精准的织入到指定的连接点
public class BuyStockServiceImpl implements BuyStockService{ private AccountDao accountDao; private StockDao stockDao; @Override public void addAccount(String accountname, double money) { accountDao.addAccount(accountname,money); } @Override public void addStock(String stockname, int amount) { stockDao.addStock(stockname,amount); } public BuyStockServiceImpl() { // TODO Auto-generated constructor stub } @Override public void buyStock(String accountname, double money, String stockname, int amount) throws BuyStockException { boolean isBuy = true; accountDao.updateAccount(accountname, money, isBuy); if(isBuy==true){ throw new BuyStockException("购买股票发生异常"); } stockDao.updateStock(stockname, amount, isBuy); } public AccountDao getAccountDao() { return accountDao; } public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } public StockDao getStockDao() { return stockDao; } public void setStockDao(StockDao stockDao) { this.stockDao = stockDao; } } 复制代码
<context:property-placeholder location="classpath:jdbc.properties"/> <!-- 注册数据源 C3P0 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" > <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="jdbcUrl" value="${jdbc.url}"></property> <property name="user" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <bean id="accountDao" class="com.zwd.spring.transaction.daoImpl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="stockDao" class="com.zwd.spring.transaction.daoImpl.StockDaoImpl"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="buyStockService" class="com.zwd.spring.transaction.serviceImpl.BuyStockServiceImpl"> <property name="accountDao" ref="accountDao"></property> <property name="stockDao" ref="stockDao"></property> </bean> <!-- 事务管理器 --> <bean id="myTracnsactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <tx:advice id="txAdvice" transaction-manager="myTracnsactionManager"> <tx:attributes> <!-- 为连接点指定事务属性 --> <tx:method name="add*" isolation="DEFAULT" propagation="REQUIRED"/> <tx:method name="buyStock" isolation="DEFAULT" propagation="REQUIRED" rollback-for="BuyStockException"/> </tx:attributes> </tx:advice> <aop:config> <!-- 切入点配置 --> <aop:pointcut expression="execution(* *..serviceImpl.*.*(..))" id="point"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="point"/> </aop:config> 复制代码