1)事务本身是在数据库中,在dao。但是如果是银行转账的号,就要银行和用户一起改,如果不改,那就用户的钱减少,而银行的钱增加,这是不正常的。 在 Spring 中通常可以通过以下三种方式来实现对事务的管理: (1)使用 Spring 的事务代理工厂管理事务 (2)使用 Spring 的事务注解管理事务 (3)使用 AspectJ 的 AOP 配置管理事务
spring事务管理api:
spring的事务管理,主要用到两个事务相关的接口
(1)事务管理器接口 事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。查看 SpringAPI 帮助文档:Spring 框架解压目录下的docs/javadoc-api/index.html。
(2)事务定义接口: 事务定义接口 TransactionDefinition 中定义了事务描述相关的三类常量:事务隔离级别、事务传播行为、事务默认超时时限,及对它们的操作。 A、定义了五个事务隔离级别常量: 这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。 DEFAULT:采用DB默认的事务隔离级别。MySql的默认为REPEATABLE_READ; Oracle默认为 READ_COMMITTED。 READ_UNCOMMITTED:读未提交。未解决任何并发问题。 READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。 REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读 SERIALIZABLE:串行化。不存在并发问题。
例子:
Spring事务代码详解: 要求:实现模拟购买股票。存在两个实体:银行账户 Account 与股票账户 Stock。当要购买股票时,需要从 Account 中扣除相应金额的存款,然后在 Stock 中增加相应的股票数量。而在这个过程中,可能会抛出一个用户自定义的异常。异常的抛出,将会使两个操作回滚。
Step1:创建数据库表 account、stock Step2:创建实体类 Account 与 Stock (略) Step3:定义 Dao 接口 IAccountDao 与 IStockDao
1 2 3 4 5 6 7 8 9 10 11 package com.tongji.dao;public interface IAccountDao { void insertAccount (String aname, double money) ; void updateAccount (String aname, double money, boolean isBuy) ; } package com.tongji.dao;public interface IStockDao { void insertStock (String sname, int amount) ; void updateStock (String sname, int amount, boolean isBuy) ; }
Step4:定义 Dao 实现类 AccountDaoImpl 与 StockDaoImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package com.tongji.dao;import org.springframework.jdbc.core.support.JdbcDaoSupport;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 = “update account set balance=balance+? where aname=?”; if (isBuy) { sql = “update account set balance=balance-? where aname=?”; } this .getJdbcTemplate().update(sql, money, aname); } } package com.tongji.dao;import org.springframework.jdbc.core.support.JdbcDaoSupport;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 amount, 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, amount, sname); } }
Step5:定义异常类 StockException
1 2 3 4 5 6 7 8 9 10 package com.tongji.beans;public class StockException extends Exception { private static final long serialVersionUID = 5377570098437361228L ; public StockException () { super (); } public StockException (String message) { super (message); } }
Step6:定义 Service 接口 IStockProcessService
1 2 3 4 5 6 package com.tongji.service;public interface IStockProcessService { void openAccount (String aname, double money) ; void openStock (String sname, int amount) ; void buyStock (String aname, double money, String sname, int amount) ; }
Step7:定义 service 的实现类 StockProcessServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package com.tongji.service;import org.springframework.transaction.annotation.Isolation;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;import com.tongji.beans.StockException;import com.tongji.dao.IAccountDao;import com.tongji.dao.IStockDao;public class StockProcessServiceImpl implements IStockProcessService { private IAccountDao accountDao; private IStockDao stockDao; public void setAccountDao (IAccountDao accountDao) { this .accountDao = accountDao; } public void setStockDao (IStockDao stockDao) { this .stockDao = stockDao; } @Override public void openAccount (String aname, double money) { accountDao.insertAccount(aname, money); } @Override public void openStock (String sname, int amount) { stockDao.insertStock(sname, amount); } @Override public void buyStock (String aname, double money, String sname, int amount) throws StockException { boolean isBuy = true ; accountDao.updateAccount(aname, money, isBuy); if (true ) { throw new StockException(“购买股票异常”); } stockDao.updateStock(sname, amount, isBuy); } }
Step8:定义Spring 配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <?xml version=”1.0 ″ encoding=”UTF-8 ″?> <beans xmlns=”http: xmlns:xsi=”http: xmlns:context=”http: http: http: http: http: <!– 注册数据源:C3P0数据源 –> <bean id=”myDataSource” class =”com.mchange.v2.c3p0.ComboPooledDataSource”> <property name=”driverClass” value=”${jdbc.driverClass}” /> <property name=”jdbcUrl” value=”${jdbc.url}” /> <property name=”user” value=”${jdbc.user}” /> <property name=”password” value=”${jdbc.password}” /> </bean> <!– 注册JDBC属性文件 –> <context:property-placeholder location=”classpath:jdbc.properties”/> <!– 注册Dao –> <bean id=”accountDao” class =”com.tongji.dao.AccountDaoImpl”> <property name=”dataSource” ref=”myDataSource”/> </bean> <bean id=”stockDao” class =”com.tongji.dao.StockDaoImpl”> <property name=”dataSource” ref=”myDataSource”/> </bean> <!– 注册Service –> <bean id=”stockService” class =”com.tongji.service.StockProcessServiceImpl”> <property name=”accountDao” ref=”accountDao”/> <property name=”stockDao” ref=”stockDao”/> </bean> </beans>
Step9:测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.tongji.test;import org.junit.Before;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import com.tongji.service.IStockProcessService;public class MyTest { private IStockProcessService service; @Before public void before () { @SuppressWarnings (“resource”) ApplicationContext ac = new ClassPathXmlApplicationContext(“applicationContext.xml”); service = (IStockProcessService) ac.getBean(“stockService”); } @Test public void testOpen () { service.openAccount(“张三”, 10000 ); service.openStock(“华为”, 5 ); } @Test public void testBuyStock () { service.buyStock(“张三”, 2000 , “华为”, 5 ); } }
此配置文件没有采用事务管理,所以购买股票的时候,出现了数据库中账户金额减少了,但是股票数目没有增加的不一致情况。
使用 Spring 的事务代理工厂管理事务: 该方式是,需要为目标类,即 Service 的实现类创建事务代理。事务代理使用的类是TransactionProxyFactoryBean,该类需要初始化如下一些属性: (1)transactionManager:事务管理器 (2)target:目标对象,即 Service 实现类对象 (3)transactionAttributes:事务属性设置 对于 XML 配置代理方式实现事务管理时,受查异常的回滚方式,程序员可以通过以下方式进行设置:通过“-异常”方式,可使发生指定的异常时事务回滚;通过“+异常”方式,可使发生指定的异常时事务提交。 修改Spring配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <?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”; 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=”myDataSource” class=”com.mchange.v2.c3p0.ComboPooledDataSource”> <property name=”driverClass” value=”${jdbc.driverClass}” /> <property name=”jdbcUrl” value=”${jdbc.url}” /> <property name=”user” value=”${jdbc.user}” /> <property name=”password” value=”${jdbc.password}” /> </bean> <!– 注册JDBC属性文件 –> <context:property-placeholder location=”classpath:jdbc.properties”/> <!– 注册Dao –> <bean id=”accountDao” class=”com.tongji.dao.AccountDaoImpl”> <property name=”dataSource” ref=”myDataSource”/> </bean> <bean id=”stockDao” class=”com.tongji.dao.StockDaoImpl”> <property name=”dataSource” ref=”myDataSource”/> </bean> <!– 注册Service –> <bean id=”stockService” class=”com.tongji.service.StockProcessServiceImpl”> <property name=”accountDao” ref=”accountDao”/> <property name=”stockDao” ref=”stockDao”/> </bean> <!– 事务 –> <!– 注册事务管理器 –> <bean id=”myTxManager” class=”org.springframework.jdbc.datasource.DataSourceTransactionManager”> <property name=”dataSource” ref=”myDataSource”/> </bean>
事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX。 REQUIRED:指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事务,则创建一个新事务。这种传播行为是最常见的选择,也是Spring 默认的事务传播行为。 常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,及不支持事务超时时限设置的 none 值。
1 2 3 4 5 6 7 8 9 10 11 12 <!– 生成事务代理 –> <bean id=”stockServiceProxy” class=”org.springframework.transaction.interceptor.TransactionProxyFactoryBean”> <property name=”transactionManager” ref=”myTxManager”/> <property name=”target” ref=”stockService”/> <property name=”transactionAttributes”> <props> <prop key=”open*”>ISOLATION_DEFAULT,PROPAGATION_REQUIRED</prop> <prop key=”buyStock”>ISOLATION_DEFAULT,PROPAGATION_REQUIRED,-StockException</prop> </props> </property> </bean> </beans>
由于本项目使用的是 JDBC 进行持久化,所以使用 DataSourceTransactionManager 类作为事务管理器。 修改测试类:
1 2 3 4 5 6 public void before () { @SuppressWarnings (“resource”) ApplicationContext ac = new ClassPathXmlApplicationContext(“applicationContext.xml”); service = (IStockProcessService) ac.getBean(“stockServiceProxy“); }
使用 Spring 的事务注解管理事务: 通过@Transactional 注解方式,也可将事务织入到相应方法中。而使用注解方式,只需在配置文件中加入一个 tx 标签,以告诉 spring 使用注解来完成事务的织入。该标签只需指定一个属性,事务管理器。 修改Spring配置文件: 通过@Transactional 注解方式,也可将事务织入到相应方法中。而使用注解方式,只需在配置文件中加入一个 tx 标签,以告诉 spring 使用注解来完成事务的织入。该标签只需指定一个属性,事务管理器。 修改Spring配置文件:
1 2 3 4 5 6 7 8 <!– 事务 –> <!– 注册事务管理器 –> <bean id=”myTxManager” class=”org.springframework.jdbc.datasource.DataSourceTransactionManager”> <property name=”dataSource” ref=”myDataSource”/> </bean> <!– 开启注解驱动 –> <tx:annotation-driven transaction-manager=”myTxManager”/> </beans>
修改 service 的实现类 StockProcessServiceImpl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package com.tongji.service;import org.springframework.transaction.annotation.Isolation;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;import com.tongji.beans.StockException;import com.tongji.dao.IAccountDao;import com.tongji.dao.IStockDao;public class StockProcessServiceImpl implements IStockProcessService { private IAccountDao accountDao; private IStockDao stockDao; public void setAccountDao (IAccountDao accountDao) { this .accountDao = accountDao; } public void setStockDao (IStockDao stockDao) { this .stockDao = stockDao; } @Override @Transactional (isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRED) public void openAccount (String aname, double money) { accountDao.insertAccount(aname, money); } @Override @Transactional (isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRED) public void openStock (String sname, int amount) { stockDao.insertStock(sname, amount); } @Override @Transactional (isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRED, rollbackFor=StockException.class ) public void buyStock (String aname , double money , String sname , int amount ) throws StockException { boolean isBuy = true ; accountDao.updateAccount(aname, money, isBuy); if (true ) { throw new StockException(“购买股票异常”); } stockDao.updateStock(sname, amount, isBuy); } }
@Transactional 的所有可选属性如下所示: propagation:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为Propagation.REQUIRED。 isolation:用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为Isolation.DEFAULT。 readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值为 false。 timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为-1,即没有时限。 rollbackFor:指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。 rollbackForClassName:指定需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。 noRollbackFor:指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。 noRollbackForClassName:指定不需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。 需要注意的是,@Transactional 若用在方法上,只能用于 public 方法上。对于其他非public 方法,如果加上了注解@Transactional,虽然 Spring 不会报错,但不会将指定事务织入到该方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction 注解。 若@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。 使用 AspectJ 的 AOP 配置管理事务(重点): 使用 XML 配置事务代理的方式的不足是,每个目标类都需要配置事务代理。当目标类较多,配置文件会变得非常臃肿。使用 XML 配置顾问方式可以自动为每个符合切入点表达式的类生成事务代理。 修改spring的配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <!– 事务 –> <!– 注册事务管理器 –> <bean id=”myTxManager” class=”org.springframework.jdbc.datasource.DataSourceTransactionManager”> <property name=”dataSource” ref=”myDataSource”/> </bean> <!– 注册事务通知 –> <tx:advice id=”txAdvice” transaction-manager=”myTxManager”> <tx:attributes> <!– 指定在连接点方法上应用的事务属性 –> <tx:method name=”open*” isolation=”DEFAULT” propagation=”REQUIRED”/> <tx:method name=”buyStock” isolation=”DEFAULT” propagation=”REQUIRED” rollback-for=”StockException”/> </tx:attributes> </tx:advice> <!– AOP配置 –> <aop:config> <!– 指定切入点 –> <aop:pointcut expression=”execution(* *..service.*.*(..))” id=”stockPointCut”/> <aop:advisor advice-ref=”txAdvice” pointcut-ref=”stockPointCut”/> </aop:config>