Spring
5.4 AOP管理事务
5.4.1事务的特性
- 一荣俱荣,一损俱损 这句话能够很好地体现事务的特性,事务的特性包括下面的4大特性:
- 原子性(Atomic):表示组成一个事务的多个数据库操作是一个不可分割的原子单元,只有所有的操作都执行成功,整个事务才提交,事务中任何一个数据库操作失败,已经执行的任何操作都必须撤销,让数据库返回到初始状态。
- 一致性(Consistency):事务操作成功后,数据库所处的状态和它的业务规则是一致的,即数据不会被破坏【例如:事务之前A,B两个账户的总和是10万(A: 4W, B: 6W), 现在A转账2W给B (A: 2W, B: 8W)A, B账户总和依旧是10W,如果不是10W的话,则事务前后对于账户总和这种资源是不一致的。】
- 隔离性(Isolation):在并发数据操作时,不同的事务拥有各自的数据空间,他们的操作不会对对方产生干扰。准备的说,并非要求做到完全无干扰,数据库规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高数据一致性越好,但是并发性越弱。
- 持久性(Duiabiliy):一旦事务提交成功后,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须保证能够通过某种机制恢复数据。
5.4.2事务的结束方式及引发问题
commit, rollback
两个事务并发执行会出现以下问题:
- 1.脏读
- 2.不可重复读
- 3.幻读
脏读: 指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,
另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据, 那么另外一
个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。
不可重复读: 指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。
那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据
可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
幻觉读: 指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及
到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,
以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。
5.4.3事务的隔离级别(重点)
事务的隔离级别:
- 读未提交(Read Uncommitted):这是事务最低的隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读
- 读提交(Read Committed): 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。允许不可重复读取,但不允许脏读取。
3、可重复读(Repeatable Read):这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免不可重复读。
4、序列化(Serializable):这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。 除了防止脏读,不可重复读外,还避免了幻读。
Spring中事务隔离级别:(重点必须记住)
Oracle中事务
- oracle数据库支持read committed 和 serializable这两种事务隔离级别,所以oracle不支持脏读。
- oracle默认使用的是read committed。
- 设置隔离级别使用:set transaction isolation level[隔离等级]
5.4.4 Spring事务传播特性(重点)
1和4用最多
1、PROPAGATION_REQUIRED 表示如果当前环境中没有新事务,将创建新事务,如果事务已经存在,则加入当前事务。(这是最常见的选择。)
2、PROPAGATION_SUPPORTS 表示如果当前环境中没有新事务,不会开启新事务,如果事务已经存在,则加入当前事务
3、PROPAGATION_REQUIRED_NEW 表示每次都开启一个新的事务
4、PROPAGATION_ NOT_SUPPORTED 表示不会开启或者加入事务
5、PROPAGATION_ MANDOTORY 使用当前的事务,如果没有事务,则抛出异常
6、PROPAGATION_ NEVER 以非事务方式执行,如果当前存在事务,抛出异常。
7. PROPAGATION_NESTED:支持当前事务,新增Savepoint点,与当前事务同步提交或回滚
<!-- 配置事务管理 -->
<bean id="transactionManager" class=“
org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置事务Aop-->
<aop:config>
<aop:pointcut expression="execution(* com.r863.spring.service..*.*(..))" id=“pointTrans"/>
<aop:advisor advice-ref=" txAdvsor " pointcut-ref=" pointTrans "/>
</aop:config>
<!– 配置事务的传播特性-->
<tx:advice id="txAdvsor" transaction-manager="transactionManager">
<tx:attributes>
<!-- 不支持事务 -->
<tx:method name="get*" read-only="true" propagation="NOT_SUPPORTED"/>
<tx:method name="*" propagation="REQUIRED"/> <!-- 支持事务 -->
</tx:attributes>
</tx:advice>
案例:第一种方式(xml配置)
<!-- 步骤1:配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--步骤2:配置事务Aop切点 -->
<aop:config>
<aop:pointcut expression="execution(* cn.com.soft863.aop.transaction.service..*.*(..))" id="transPoint"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="transPoint"/>
</aop:config>
<!-- 步骤3:配置事务传播特性 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="query*" read-only="true" isolation="DEFAULT" propagation="NOT_SUPPORTED"/>
<tx:method name="find*" read-only="true" isolation="DEFAULT" propagation="NOT_SUPPORTED"/>
<tx:method name="get*" read-only="true" isolation="DEFAULT" propagation="NOT_SUPPORTED"/>
<tx:method name="*" isolation="DEFAULT" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
</tx:attributes>
</tx:advice>
注意:
spring在配置事务的时候,经常会需要配置read-only=“true"这个属性,就表示你现在正在操作的是一个只读事务,加上这个属性只是会提示数据库驱动这是一个只读事务,数据库驱动程序就会对于该事务进行优化,减少数据库的压力,毕竟事务处理是会耗费内存资源的只读事务仅仅是一个性能优化的推荐配置而已
第二种方式(注解)
<tx:annotation-driven transaction-manager="transactionManager"/>
Java代码:
@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED,rollbackFor=java.lang.Exception.class)
public int registUser(User user) {
System.out.println("开始");
int r1 = userDao.inserUser(user);
System.out.println("插入执行完毕"+r1);
System.out.println(2/0);
int r2 = userDao.updateUser();
System.out.println("更新执行完毕"+r2);
return 0;
}
@Transactional可以加到类上方
5.4.5 锁
- 乐观锁
所谓的乐观锁:就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。oracle默认使用乐观锁
- 悲观锁(重点)
顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次拿数据的时候都会上锁。这样别人拿数据的时候就要等待直到锁的释放。例如用法是select语句后面加上 for update,通过这种操作,使我们查询记录开始,这条记录被上锁,不允许其他对该记录有任何修改。
Select…For Update语句的语法与select语句相同,只是在select语句的后面加FOR UPDATE [NOWAIT]子句。
该语句用来锁定特定的行(如果有where子句,就是满足where条件的那些行)。当这些行被锁定后,其他会话可以选择这些行,但不能更改或删除这些行,直到该语句的事务被commit语句或rollback语句结束为止
Oracle的悲观锁需要利用一条现有的连接,分成两种方式,从SQL语句的区别来看,就是一种是for update,一种是for update nowait的形式。
- 执行select xxx for update操作时,数据会被锁定,只有执行commit或rollback才会释放
② 执行select xxx for update nowait操作时,数据也会被锁定,其他人访问时或返回ORA-00054错误,内容是资源正忙,需要采取相应的业务措施进行处理。
如果加入了for update, 则Oracle一旦发现(符合查询条件的)这批数据正在被修改,则不会发出该select语句查询,直到数据被修改结束(被commit),马上自动执行这个select语句。
同样,如果该查询语句发出后,有人需要修改这批数据(中的一条或几条),它也必须等到查询结束后(commit)后,才能修改
for update nowait和 for update 都会对所查询到得结果集进行加锁,所不同的是,如果另外一个线程正在修改结果集中的数据,for update nowait 不会进行资源等待,只要发现结果集中有些数据被加锁,立刻返回 “ORA-00054错误,内容是资源正忙, 但指定以 NOWAIT 方式获取资源”。