Spring总结五:事务管理

事务管理

    事务管理是企业级应用程序开发中必不可少的技术, 用来确保数据的完整性和一致性.

    事务就是一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用.
事务的四个关键属性(ACID)

  • 原子性(atomicity): 事务是一个原子操作, 由一系列动作组成. 事务的原子性确保动作要么全部完成要么完全不起作用.
  • 一致性(consistency): 一旦所有事务动作完成, 事务就被提交. 数据和资源就处于一种满足业务规则的一致性状态中.
  • 隔离性(isolation): 可能有许多事务会同时处理相同的数据, 因此每个事物都应该与其他事务隔离开来, 防止数据损坏.
  • 持久性(durability): 一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响. 通常情况下, 事务的结果被写到持久化存储器中.

    作为企业级应用程序框架, Spring 在不同的事务管理 API 之上定义了一个抽象层. 而应用程序开发人员不必了解底层的事务管理 API, 就可以使用 Spring 的事务管理机制.

    Spring 既支持编程式事务管理, 也支持声明式的事务管理.

    编程式事务管理: 将事务管理代码嵌入到业务方法中来控制事务的提交和回滚. 在编程式管理事务时,必须在每个事务操作中包含额外的事务管理代码,一般编程时很少采用编程式的事务管理。

    声明式事务管理: 大多数情况下比编程式事务管理更好用. 它将事务管理代码从业务方法中分离出来, 以声明的方式来实现事务管理. 事务管理作为一种横切关注点, 可以通过 AOP 方法模块化. Spring 通过 Spring AOP 框架支持声明式事务管理.

    Spring 从不同的事务管理 API 中抽象了一整套的事务机制. 开发人员不必了解底层的事务 API, 就可以利用这些事务机制. 有了这些事务机制, 事务管理代码就能独立于特定的事务技术了.
    Spring 的核心事务管理抽象是在这里插入图片描述,它为事务管理封装了一组独立于技术的方法. 无论使用 Spring 的哪种事务管理策略(编程式或声明式), 事务管理器都是必须的,因为不论哪种方式,我们都需要事务管理器来帮我们完成一些操作。

    Spring内置了很多事务管理器,常见的有三种:

  • DataSourceTransactionManager:org.springframework.jdbc.datasource包下,数据源事务管理类,提供对单个javax.sql.DataSource数据源的事务管理,只要用于JDBC,Mybatis框架事务管理。
  • HibernateTransactionManager:org.springframework.orm.hibernate3包下,数据源事务管理类,提供对单个org.hibernate.SessionFactory事务支持,用于集成Hibernate框架时的事务管理;(Hibernate现在已经不是主流了,这个我没有深入了解,不做介绍)
  • JtaTransactionManager:位于org.springframework.transaction.jta包中,提供对分布式事务管理的支持,并将事务管理委托给Java EE应用服务器,或者自定义一个本地JTA事务管理器,嵌套到应用程序中。(我还没有学分布式事务,不做介绍,以后我会专门就事务做一个学习记录)

事务管理在Spring中有四种实现方式:

  • 编程式事务管理:我们需要在代码中调用commit()、rollback()等事务管理相关的方法。可以有三种方法去实现
    1、使用 JDBC 原生的事务管理方法
    2、使用 Spring 为的 TransactionTemplate类
    3、使用 Spring 的PlatTransactionTemplate类
    因为这种方式不常用,并且是侵入式的代码,就不写详细笔记了(其实是因为我当时没怎么学,直接就用声明式事务了)。
  • 基于 TransactionProxyFactoryBean的声明式事务管理
    这种方式需要在 xml 中配置 TransactionProxyFactoryBean,一些与事务有关的属性也会配置在xml中
	<bean id="serviceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
		<property name="transactionManager" ref="配置在xml中的事务管理器"></property>
		<property name="target" ref="需要进行事务代理的bean"></property>
		<!-- 要配置的事务的具体属性信息 -->
		<property name="transactionAttributes">
			<props>
				<!-- 
					key指定要代理的方法
					ISOLATION_DEFAULT  事务的隔离级别
					PROPAGATION_REQUIRED  传播行为
					Exception 表示发生指定异常回滚,+Exception 表示发生指定异常提交
				-->
				<prop key="add*">ISOLATION_DEFAULT,PROPAGATION_REQUIRED,-BuyStockException</prop>
			</props>
		</property>
	</bean>

  • 基于 @Transactional 的声明式事务管理
        为了将方法定义为支持事务处理的, 可以为方法添加 @Transactional 注解. 根据 Spring AOP 基于代理机制, 只能标注公有方法.
        可以在方法或者类上添加 @Transactional 注解. 当把这个注解应用到类上时, 这个类中的所有公共方法都会被定义成支持事务处理的.
    在 Bean 配置文件中只需要启用 <tx:annotation-driven> 元素, 并为之指定事务管理器就可以了.
        如果事务处理器的名称是 transactionManager, 就可以在<tx:annotation-driven> 元素中省略 transaction-manager 属性. 这个元素会自动检测该名称的事务处理器.
	<tx:annotation-driven transaction-manager="transactionManager"/>//开启事务注解
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
	@Autowired
	private BookShopDao bookShopDao;
	//添加事务注解
	//1、使用 propagation 指定事务的传播行为(什么是传播行为,在下文中介绍),即当前的事务方法被另外一个事务方法调用时
	//如何使用事务,默认取值为 QEQUIRED 即使用调用方法的事务
	//REQUIRES_NEW 使用自己的事务,调用的事务方法的事务被挂起
	//2、使用 isolation 指定事务的隔离级别,最常用的取值为 REAN_COMMITTED
	//3、默认情况下 Spring 的声明事务对所有的运行时异常进行回滚,也可以通过
	     对应的属性进行设置,通常情况下默认值即可
	//4、使用 readOnly 指定事务是否为只读。 表示这个事务只读取数据但不更新数据,
	     这样可以帮助数据库引擎优化事务。若真的是一个只读取数据库值的方法,应设置为 readOnly=true
	//5、使用 timeout 指定强制回滚之前事务可以占用的时间。
//	@Transactional(propagation=Propagation.REQUIRES_NEW,
//			isolation=Isolation.READ_COMMITTED,
//			noRollbackFor= {UserAccountException.class})
	
	@Transactional(propagation=Propagation.REQUIRES_NEW,
			isolation=Isolation.READ_COMMITTED,
			readOnly=false,
			timeout=3)
	@Override
	public void purchase(String username, String isbn) {
		
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//1、获取书的单价
		int price = bookShopDao.findBookPriceByIsbn(isbn);
		//2、更新书的库存
		bookShopDao.updateBookStock(isbn);
		//3、更新用户的余额
		bookShopDao.updateUserAccount(username, price);
	}

}
  • 基于Aspectj AOP配置事务
        事务管理是一种横切关注点,通过 tx Schema 中定义的 <tx:advice> 元素声明事务通知。
        声明了事务通知后, 就需要将它与切入点关联起来. 由于事务通知是在 <aop:config> 元素外部声明的, 所以它无法直接与切入点产生关联. 所以必须在 <aop:config> 元素中声明一个增强器通知与切入点关联起来.
        由于 Spring AOP 是基于代理的方法, 所以只能增强公共方法. 因此, 只有公有方法才能通过 Spring AOP 进行事务管理.
	<!-- 1、配置事务属性 -->
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<!-- 根据方法名指定事务的属性 -->
			<tx:method name="purchase" propagation="REQUIRES_NEW"/>
			<!-- 这里的 * 意为通配符 -->
			<tx:method name="get*" read-only="true"/>
			<tx:method name="find*" read-only="true"/>		
		</tx:attributes>
	</tx:advice>
	
	<!-- 2、配置事务切入点 ,以及把事务切入点和事务属性关联起来-->
	<aop:config>
		<!-- 切入点表达式 -->
		<aop:pointcut expression="execution(* com.qlgydx.spring.tx.xml.service.*.*(..))" id="txPointcut"/>
		<!--  增强器 ,将切入点与事务通知关联起来-->
		<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
	</aop:config>

事务的传播属性

    事务的传播属性是指当一个事务方法被另一个事务调用时,必须指定事务如何传播。
    意思就是如果一个事务中调用了另一个事务,那么后面调用的事务是重新开启一个事务,与调用它的事务无关(即就相当于事务中调用了一个可能会抛出异常的方法)呢,还是与调用它的事务共享一个事务呢。
    从被调用方看似乎没什么影响,无论什么情况下,只要被调用方出现了异常都会进行回滚,关键是在调用方,如果不是共用一个事务,就会出现如果被调用方出现了异常,被调用方回滚了,而调用方此时是不会进行回滚的,因为异常是在被调用方的事物中捕获的,而如果共用一个事务,只要出现了异常,整个事务就都会回滚。这里就出现问题了,到底要不要共用一个事务?所以对事务传播属性的设置是非常重要的。
Spring定义了7种传播行为(TransactionDefinition接口中定义的):
在这里插入图片描述

事务的隔离级别

    事务的隔离级别这个概念是由并发事务所引起的问题而提出的。

    并发事务:即当同一个应用程序或者不同应用程序中的多个事务在同一个数据集上并发执行时,可能会出现许多意外的问题。

  • 丢失更新:两个事务同时更新一行数据,最后一个事务的更新会覆盖掉第一个事务的更新,从而导致第一个事务更新的数据丢失
  • 脏读:一个事务读取到另外一个未提交事务的内容(一个事务如果更新了记录但并没有提交即没有结束事务,被另一个事务读取了数据,但是第一个事务又回滚了,导致后来的事务读取到了不该读取到的事务,我们说后来的事务是读到了脏的数据"脏数据的意思是不应该出现的数据”,即为脏读)。
  • 幻读:同样的事务操作过程中,不同时间段多次(不同事务)读取同一数据,读取到的内容不一致(一般是行数变多或变少)。
  • 不可重复读:同一事务中,多次读取内容不一致(一般行数不变,而内容变了)。

幻读与不可重复读的区别
    两者产生的结果都是前后数据不一致,主要是侧重点不同

  • 幻读侧重点在于插入与删除(行数的改变,指数据在事务未结束中的前后两次查询,发现行数不同了),即第二次查询会发现比第一次查询数据变少或者变多了,以至于给人一种幻象一样,所以叫幻读。
  • 而不可重复读侧重点在于修改,即第二次查询会发现查询结果比第一次查询结果不一致,即重复读的结果不同,所以叫不可重复读。

    区分它们,可以更好的选择优化策略,解决不可重复读,用行锁就可以解决,而要解决幻读,就要用表锁,但是表锁对性能的影响太大了,这里就又牵涉到了锁的种类与选择,即行锁、表锁、页锁、悲观锁、乐观锁等,本文是事务管理的学习笔记,锁就不详谈了。

Spring支持的隔离级别(在TransactionDefinition接口中定义的)默认值为isolation_default(底层数据库默认级别),其他四个隔离级别跟数据库隔离级别一致。:
在这里插入图片描述
DEFAULT:用底层数据库的默认隔离级别,数据库管理员设置什么就是什么
READ_UNCOMMITTED(未提交读)
READ_COMMITTED(提交读,也可以说读已提交)
REPEATABLE_READ(可重复读)
SERIALIZABLE(序列化)

    从理论上来说, 事务应该彼此完全隔离, 以避免并发事务所导致的问题. 然而, 那样会对性能产生极大的影响, 因为事务必须按顺序运行. 在实际开发中, 为了提升性能, 事务会以较低的隔离级别运行.事务的隔离级别可以通过隔离事务属性指定.

事务回滚

    默认情况下只有运行时异常(RuntimeException和Error类型的异常)会导致事务回滚. 而检查时异常不会.
    事务的回滚规则可以通过 @Transactional 注解的 rollbackFor 和 noRollbackFor 属性来定义(也可以通过<tx:method>指定). 这两个属性被声明为 Class[] 类型的, 因此可以为这两个属性指定多个异常类.

  • rollbackFor: 遇到时必须进行回滚
  • noRollbackFor: 一组异常类,遇到时必须不回滚

    由于事务可以在行和表上获得锁, 因此长事务会占用资源, 并对整体性能产生影响.,如果一个事物只读取数据但不做修改, 数据库引擎可以对这个事务进行优化(可以指定readOnly属性),为了优化操作,我们还有两个属性可以使用。

  • 超时事务属性: 事务在强制回滚之前可以保持多久. 这样可以防止长期运行的事务占用资源.
  • 只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务.

同样的这两个属性也可以在 @Transactional 注解和<tx:method>中指定。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值