Spring框架自学之路(十)

(10Day)

    前面我们讲了spring JDBC的操作,自然既然有对数据库进行操作,那么就一定会要有事务来确保数据的完整性和一致性。

    那么什么是事务呢?相信大家都知道事物是怎么一回事吧。为了防止有些人忘记了,现在我在简要的说下什么是事务。

    事务其实就是一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用。比如说我们在淘宝买东西扣钱的时候,这时候我们余额要减少同时库存也要减少,这两个操作要么都完成,要么都不完成。如果一个完成一个不完成,那这样要么用户少了钱要么库存少了件,这就是事务。

 事务的四个关键属性(ACID)

原子性(atomicity): 事务是一个原子操作, 由一系列动作组成. 事务的原子性确保动作要么全部完成要么完全不起作用.

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

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

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

    那么在spring中是如何管理事物的呢?其实非常简单,用注解的方法只需要写几句话就OK了。那么我们来看一看把。

    数据库文件和一些Dao什么的我就不贴出来了,参照我下面的例子应该非常容易理解。首先我们现在spring配置文件中配置事务管理器和启用事物的注解(需要导入tx命名空间)

<!-- 配置事务管理器 -->
	<bean id = "transactionManager" 
	class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref = "dataSource"></property>
	</bean>
	<!-- 启用事物注解 -->
	<tx:annotation-driven transaction-manager="transactionManager"/>
    随后在Service层中启用事务注解
package com.SpringTransaction;

import org.junit.rules.Timeout;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service("BookShopService")
public class BookShopServiceImpl implements BookShopService {
	@Autowired
	private BookShopDao bookShopDao;
	/*
	 *添加事务注解(@Transactional)
	 *1.使用 propagation 指定事务的传播行为, 即当前的事务方法被另外一个事务方法调用时
	 *如何使用事务, 默认取值为 REQUIRED, 即使用调用方法的事务
	 *2.使用isolation来确定事务的隔离级别,最常用的取值为READ_COMMITTED
	 *3.noRollbackFor,通常情况下spring的声明式事物会对所有运行时的异常进行回滚,也可以通过noRollbackFor来
	 *设置不需要回滚的异常。通常情况下使用默认值就行了。
	 *	@Transactional(propagation = Propagation.REQUIRED,
	 *	isolation=Isolation.READ_COMMITTED,
	 *	RollbackFor={UserAccountException.class})
	 *4.readOnly:使用readOnly可以指定事务是否只读,若为true表示只读,
	 *这样可以帮助数据库引擎优化事务. 若真的事一个只读取数据库值的方法, 应设置 readOnly=true
	 *5.timeout指定强制回滚之前事务可以占用的时间. 防止一个访问长时间占用 
	 * */
	@Transactional(propagation = Propagation.REQUIRED,
			isolation=Isolation.READ_COMMITTED,
			readOnly = false,
			timeout=3)
	public void purchase(String username, int bookid) {
		//1.获取书的单价
		int price = bookShopDao.findBookPriceByBookId(bookid);
		//2.更新书的库存
		bookShopDao.updateBookStock(bookid);
		//3.更新用户余额
		bookShopDao.updateUserAccount(username, price);
	}
}

    现在我来说说注解中propagation,isolation这两个比较复杂的属性,其他的看看我上面的注释应该就可以明白。

   1.首先propagation是用来指定事务的传播行为的:其中比较常用的值为REQUIRED和REQUIRES_NEW。

    REQUIRED:如果有一个事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行。

    REQUIRES_NEW:当前方法必须启动新的事务,并在它自己的事务内运行,如果有事务在运行,就先将其挂起。

    用个例子来说明两者的区别:一个订单有两本书A和B,A书价格为100,B本为60.账户余额有120元,购买A书有一个 方法,购买B书有一个方法。都有添加事务,同时,为这个订单付款的C方法中包含了上述两种方法。C方法也添加了个事务。如果C方法事务的传播行为是REQUIRED的话,A,B两个方法就会执行C的事务。最终两本书一本书都不会买用户余额也不会减少。如果C方法事务的传播行为是REQUIRES_NEW的话,A,B两个方法会执行自己的事务然后在执行C的事务,这样用户为订单付款的时候会把A书买了,B书提示余额不足。用户的余额也会减少100.

    2.isolation是指事务的隔离级别,事务的隔离级别是用来避免在并发操作中出现脏读,不可重复读和幻读的问题。事务的隔离级别由低到高共有4种分别为Read uncommitted、Read committed、Repeatable read和 Serializable。下面我来说说这4种隔离级别代表者什么。

    2.1.Read uncommitted:字面上意思,就是一个事务可以读取另一个未提交事务的数据。比如你老板要给你发工资的时候不小心多发了2000元,但是事务还没提交。就在这时你查看了自己这个月的工资,会发现比以往多了2000元。但是当你老板发现不对的时候马上进行回滚并提交事务,最终你的工资并没有增加。这就是脏读。那么如何解决脏读问题呢?这就需要使用.Read committed这个更高的事务隔离级别来解决。

    2.2.Read committed:读提交,就是一个事务要等待另一个事务完成后才能读取数据。比如说你要用银行卡买一个东西是6000元,在你买单的时候收费系统首先检测你的卡里有1W元确实够买,但是如果在这个时候你女朋友用你的银行卡花了5000块买了套化妆品。这时当收费系统准备扣款的时候,再检测卡里的钱,发现没钱了(第二次检查自然是在你女朋友买化妆品的事务提交后才进行)。这时候你就很无语,难道刚刚看眼花了,刚刚卡里明明钱是够的。这就是脏读。当然事务的隔离级别设为Read committed的话就可以避免脏读的情况,但是这个事务隔离级别中,一个事务范围内两个相同的查询却返回不同的数据,这就是不可重复读,如要解决不可重复读的问题那就只能使用比Read committed更高的事务隔离级别Repeatable read。

    2.3.Repeatable read:重复读,就是在开始读取数据时(开启事务时),不在允许修改操作。例子还是上面的例子如果事务的隔离级别设为Repeatable read,那么当你要花6000元买个东西的时候,事务便会开启,不再允许其他的事务执行UPDATE修改操作。当你女朋友要化5000元买化妆品的时候就不能完成付款。这样你就可以顺利的买下你要买的东西了(是不是很爽)。这样就解决了不可重复读这个问题。写到这里不知道你们有没有发现,不可重复读对应的是修改,即UPDATE操作。但是还是有可能出现幻读的问题,因为幻读对应的是INSERT操作,而不是UPDATE操作。那么如何解决幻读的问题呢?相信大家已经猜到了,就是使用事务隔离级别中的最高级别Serializable。

    2.4.Serializable:序列化,是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读,不可重复读和幻读。说到这里,大家也许会想到既然Serializable可以解决所有问题,那为什么不把所有的隔离级别都设置为Serializable呢?其实Serializable是使用的比较少的隔离级别。为什么呢,正所谓物极必反。大家想想,既然Serializable解决了这么多问题。那么其必定是会消耗了大量的数据库性能的,所以一般是不会使用Serializable的。其实大多数数据库的默认事务隔离级别是Read committed的,比如Sql Server、Oracle、Mysql的默认隔离级别就是Read committed。

    那么怎么用spring配置文件的方式配置注解呢?很简单看下面代码立刻就会明白

<?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:tx="http://www.springframework.org/schema/tx"
	xmlns:aop="http://www.springframework.org/schema/aop"
	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/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
	
	<!-- 扫描路径 -->
	<context:component-scan base-package="com"></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.user}"></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>
	<!-- 配置JdbcTemplate -->
	<bean id="jdbcTemplate" 
	class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	<bean id = "bookShopDao" class="com.SpringTransaction.xml.BookShopDaoImpl">
		<property name="jdbcTemplate" ref="jdbcTemplate"></property>
	</bean>
	<bean id = "bookShopService" class="com.SpringTransaction.xml.service.impl.BookShopServiceImpl">
		<property name="bookShopDao" ref = "bookShopDao"></property>
	</bean>
	<bean id = "cashier" class="com.SpringTransaction.xml.service.impl.CashierImpl">
		<property name="bookShopService" ref="bookShopService"></property>
		
	<!-- 1.配置事务管理器 -->
	</bean>
	<bean id = "transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	<!-- 2.配置事务属性 -->
	<tx:advice id = "txAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<!-- 根据方法名指定事务的属性 -->
			<tx:method name="*"/>
		</tx:attributes>
	</tx:advice>
	<!-- 3.配置事务切入点,以及把切入点和事务属性关联起来 -->
	<aop:config>
		<aop:pointcut expression="execution(* com.SpringTransaction.xml.service.*.*(..))" 
		id="txPointCut"/>
		<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
	</aop:config>
</beans>

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页