事务概念及属性
- 事务:一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用
- 事务的四个关键属性:
原子性:事务是一个原子操作, 由一系列动作组成. 事务的原子性确保动作要么全部完成要么完全不起作用.
一致性: 一旦所有事务动作完成, 事务就被提交. 数据和资源就处于一种满足业务规则的一致性状态中.
隔离性: 可能有许多事务会同时处理相同的数据, 因此每个事物都应该与其他事务隔离开来, 防止数据损坏.
持久性: 一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响. 通常情况下, 事务的结果被写到持久化存储器中
Spring 的事务管理
- Spring 既支持编程式事务管理, 也支持声明式的事务管理
- 编程式事务管理: 将事务管理代码嵌入到业务方法中来控制事务的提交和回滚.
- 声明式事务管理: 大多数情况下比编程式事务管理更好用. 它将事务管理代码从业务方法中分离出来, 以声明的方式来实现事务管理.
- Spring 通过 Spring AOP 框架支持声明式事务管理.
- 无论使用 Spring 的哪种事务管理策略(编程式或声明式), 事务管理器都是必须的.
- 事务管理器的不同实现:
- DataSourceTransactionManager: 再应用程序中只处理一个数据源,而且通过 JDBC 存取
- JtaTransactionManager: 在JavaEE应用服务器上用 JTA(Java Transaction API) 进行事务管理
- HibernateTransactionManager:用 Hibernate 框架存取数据库 注意:事务管理器以 Bean 形式声明在 Spring IOC 容器中
- 声明式事务管理
使用 @Transactional 注解标注事务注意:
- 为了将方法定义为支持事务处理的, 可以为方法添加 @Transactional 注解. 根据 Spring AOP 基于代理机制, 只能标注公有方法.
- 可以在方法或者类级别上添加 @Transactional 注解. 当把这个注解应用到类上时, 这个类中的所有公共方法都会被定义成支持事务处理的.
- 在 Bean 配置文件中只需要启用 <tx:annotation-driven> 元素, 并为之指定事务管理器就可以了.
- 如果事务处理器的名称是 transactionManager, 就可以在<tx:annotation-driven> 元素中省略 transaction-manager 属性. 这个元素会自动检测该名称的事务处理器.
<context:component-scan base-package="com.axon.spring.tx"></context:component-scan> <!-- 导入资源文件 --> <context:property-placeholder location="classpath:db.properties" /> <!-- 配置 C3P0 数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property> <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <tx:annotation-driven transaction-manager="transactionManager"/>
@Service("BookShopService") public class BookShopServiceImpl implements BookShopService{ @Autowired BookShopDao bookShopDao; //添加事务属性 @Transactional @Override public void purchase(String isbn, String username) { //获取书的单价 int price = bookShopDao.findBookPriceByIsbn(isbn); //更新书的库存 bookShopDao.updateBookStock(isbn); //更新用户余额 bookShopDao.updateUserAccount(username, price); } }
事务的传播行为
传播属性 描述 REQUIRED 如果有事物运行,当前的方法就在这个事务内运行,否则就启用新的事务,并在自己的事务内运行 REQUIRES_NEW 当前的方法必须启动新事物,并在自己的是事务内运行,如果有事务运行,应该将它挂起 SUPPORT 如果有事务运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中 NOT_SUPPORT 当前的方法不应该运行在事务中,如果有运行的事务将它挂起 MANDATORY 当前的方法必须运行在事务内部,如果没有正在运行是事务,就抛出异常 NEVER 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常 NESTED 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则就启动一个新事物,并在它自己的事务内运行
- REQUIRED 传播行为示例
当 bookService 的 purchase() 方法被另一个事务方法 checkout() 调用时, 它默认会在现有的事务内运行. 这个默认的传播行为就是 REQUIRED. 因此在 checkout() 方法的开始和终止边界内只有一个事务. 这个事务只在 checkout() 方法结束的时候被提交, 结果用户一本书都买不了
事务传播属性可以在 @Transactional 注解的 propagation 属性中定义
@Service("BookShopService") public class BookShopServiceImpl implements BookShopService{ @Autowired BookShopDao bookShopDao; //添加事务注解 @Transactional(propagation=Propagation.REQUIRED) @Override public void purchase(String isbn, String username) { //获取书的单价 int price = bookShopDao.findBookPriceByIsbn(isbn); //更新书的库存 bookShopDao.updateBookStock(isbn); //更新用户余额 bookShopDao.updateUserAccount(username, price); } } @Service("cashier") public class CashierImpl implements Cashier{ @Autowired BookShopService bookShopService; @Transactional @Override public void checkout(String username, List<String> isbns) { for(String isbn : isbns){ bookShopService.purchase(isbn, username); } } }
REQUIRES_NEW 传播行为示例
另一种常见的传播行为是 REQUIRES_NEW. 它表示该方法必须启动一个新事务, 并在自己的事务内运行. 如果有事务在运行, 就应该先挂起它.
@Transactional(propagation=Propagation.REQUIRES_NEW) public void purchase(String isbn, String username) {}
事务的隔离级别
Spring支持的四种事务隔离级别
隔离级别 描述 DEFAULT 使用底层数据库的默认隔离级别,对于大多数数据库来说,默认隔离级别都是READ_COMMITED READ_UNCOMMITTED 允许事务读取未被其他事务提交的变更,脏读、不可重复读和幻读的问题都会出现 READ_COMMITED 只允许事务读取已经被其他事务提交的变更,可以避免脏读,但不可重复读和幻读仍然会出现 REPEATABLE_READ 确保事务可以多次从一个字段中读相同的值,在这事务持续时间,禁止其他事务对这个字段进行更新,可以避免脏读和不可重复读,但幻读仍然存在 SERIAIZABLE 确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,所有并发问题都可避免,但性能低下 事务的隔离级别要得到底层数据库引擎的支持, 而不是应用程序或者框架的支持. (Oracle 支持的 2 种事务隔离级别:READ_COMMITED , SERIALIZABLE ,Mysql 支持 4 中事务隔离级别.)
设置事务隔离属性
@Transactional(propagation=Propagation.REQUIRES_NEW,isolation=Isolation.READ_COMMITTED) @Override public void purchase(String isbn, String username) {}
设置回滚事务属性
- 默认情况下只有未检查异常(RuntimeException和Error类型的异常)会导致事务回滚. 而受检查异常不会
- 事务的回滚规则可以通过 @Transactional 注解的 rollbackFor 和 noRollbackFor 属性来定义. 这两个属性被声明为 Class[] 类型的, 因此可以为这两个属性指定多个异常类.
- rollbackFor: 遇到时必须进行回滚
- noRollbackFor: 一组异常类,遇到时必须不回滚
@Transactional(propagation=Propagation.REQUIRES_NEW,isolation=Isolation.READ_COMMITTED,rollbackFor={IOException.class,SQLException.class},noRollbackFor=ArithmeticException.class) public void purchase(String isbn, String username) {}
设置超时和只读属性
- 超时事务属性: 事务在强制回滚之前可以保持多久. 这样可以防止长期运行的事务占用资源.
- 只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务.
- 超时事务属性用timeout设置,只读事务属性用readOnly设置
@Transactional(propagation=Propagation.REQUIRES_NEW,isolation=Isolation.READ_COMMITTED,rollbackFor={IOException.class,SQLException.class},noRollbackFor=ArithmeticException.class,readOnly=true,timeout=30) @Override public void purchase(String isbn, String username) {
基于XML方式配置事务管理(编程式事务管理)
<!-- 导入资源文件 --> <context:property-placeholder location="classpath:db.properties" /> <!-- 配置 C3P0 数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property> <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="bookShopDao" class="com.axon.spring.tx.xml.BookShopDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean> <bean id="bookShopService" class="com.axon.spring.tx.xml.BookShopServiceImpl"> <property name="bookShopDao" ref="bookShopDao"></property> </bean> <bean id="cashier" class="com.axon.spring.tx.xml.CashierImpl"> <property name="bookShopService" ref="bookShopService"></property> </bean> <!-- 1.配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 2.配置事务属性 --> <!-- 可以在 <tx:method> 元素中设定传播事务属性、指定隔离级别、超时和只读属性--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="purchase" propagation="REQUIRED"/> <tx:method name="*"/> </tx:attributes> </tx:advice> <!-- 3.配置事务切入点,以及事务切入点和事务属性关联起来 --> <aop:config> <aop:pointcut expression="execution(* com.axon.spring.tx.xml.BookShopService.*(..))" id="txPointCut"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/> </aop:config>