JavaWeb 笔记之 Spring 的事务管理

事务概念及属性

  • 事务:一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用
  • 事务的四个关键属性:
  • 原子性:事务是一个原子操作, 由一系列动作组成. 事务的原子性确保动作要么全部完成要么完全不起作用.

  • 一致性: 一旦所有事务动作完成, 事务就被提交. 数据和资源就处于一种满足业务规则的一致性状态中.

  • 隔离性: 可能有许多事务会同时处理相同的数据, 因此每个事物都应该与其他事务隔离开来, 防止数据损坏.

  • 持久性: 一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响. 通常情况下, 事务的结果被写到持久化存储器中


Spring 的事务管理

  • Spring 既支持编程式事务管理, 也支持声明式的事务管理
  • 编程式事务管理: 将事务管理代码嵌入到业务方法中来控制事务的提交和回滚.
  • 声明式事务管理: 大多数情况下比编程式事务管理更好用. 它将事务管理代码从业务方法中分离出来, 以声明的方式来实现事务管理.
  • Spring 通过 Spring AOP 框架支持声明式事务管理.
  • 无论使用 Spring 的哪种事务管理策略(编程式或声明式), 事务管理器都是必须的.
 
  • 事务管理器的不同实现:
  • DataSourceTransactionManager: 再应用程序中只处理一个数据源,而且通过 JDBC 存取
  • JtaTransactionManager: 在JavaEE应用服务器上用 JTA(Java Transaction API) 进行事务管理
  • HibernateTransactionManager:用 Hibernate 框架存取数据库 注意:事务管理器以 Bean 形式声明在 Spring IOC 容器中
  1. 声明式事务管理

使用 @Transactional 注解标注事务注意:

  1. 为了将方法定义为支持事务处理的, 可以为方法添加 @Transactional 注解. 根据 Spring AOP 基于代理机制, 只能标注公有方法.
  2. 可以在方法或者类级别上添加 @Transactional 注解. 当把这个注解应用到类上时, 这个类中的所有公共方法都会被定义成支持事务处理的.
  3. 在 Bean 配置文件中只需要启用 <tx:annotation-driven> 元素, 并为之指定事务管理器就可以了.
  4. 如果事务处理器的名称是 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>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值