Spring事务学习

837 阅读5分钟

一 事务的概念

事务: 就是把一组业务当成一个业务来处理,要么都成功,要么都失败,保证业务操作的完整性的一种数据库机制. 例如: 张三给李四转账100元,那么张三的余额减少100的同时李四余额需要增加100.

二 事务的分类

1. 编程式事务

在数据库管理系统中(比如mysql),默认情况下一条SQL就是一个单独的事务,事务默认自动提交的,只有手动把自动提交改成false,事务才不会自动提交.我们可以把一组SQL放在一个事务中,只有全部执行成功后再commit,失败了就rollback回滚

下列是伪代码,是编程式事务的一种表达

connection.autoCommit(false)

sql语句1;

sql语句2;

sql语句3;

connection.commit()

catch{
    connection.rollback();
}
2.声明式事务

2.1 声明式事务本质

声明式事务管理是建立在AOP之上的,其本质是对方法前后进行拦截,然后在目标方法开始之前加入一个事务,在执行完目标方法后,根据执行情况选择提交或者回滚事务,声明式事务的有点事可以基于@Transactional来管理,也可以基于AOP配置

2.2 声明式事务四大特性(ACID)

原子性(Atomicity): 一组业务在操作下要么都成功,要么都失败

一致性(Consistency): 事务前后的数据要保证数据的一致性

隔离性(Isolation): 一个事务在执行过程中不能受到其他事务的干扰.即一个事务内部操作的数据对其他事务是隔离的

持久性(Durability): 一个事务一旦提交,它对数据库中的数据改变是永久性的.

三 声明式事务的配置

在applicationContext.xml文件中加入这样的配置

image.png

<!--声明式事务-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<!--基于驱动-->
<tx:annotation-driven transaction-manager="transactionManager"/>

image.png

声明式事务中的property标签的name可以随便取值,但是ref的值必须是Druid的Bean的id

基于驱动的transaction-manager的值必须是声明式事务Bean的id值

四 关于applicationContext和springmvc父子容器的关系

在ssm整合框架中,我们都会有两个配置文件,一个是applicationContext.xml(关于spring),一个是springmvc.xml(关于springmvc)的,但是我们在这里需要有一个清楚的认识,spring是springmvc的"父亲",所以在配置的时候需要做以下配置:

applicationContext.xml中需要加载所有的包,除了Controller, springmvc.xml中加载所有的包,除了Service. 理由如下:

spring是springmvc的"父亲",他们是一种继承关系,也就是说儿子可以使用父亲的,但是父亲不可以使用儿子的.

springmvc核心组件就是dispatcherServlet,管理路由分发的,也就是对应@Controller那一层的,如果把所有的包都加载在applicationContext.xml(spring),包括@Controller,那么遇到的所有路由都会直接去spring中寻找,可以由于spring不能用到"儿子"springmvc的dispatcherServlet,从而导致整体报404错误.

如果把所有的包都加载在springmvc.xml中,那么spring容器本应该加载@Service被子容器加载了,就不会再加载,spring容器会在加载@Service的同时进行事务处理(简单理解就是是它具有处理事务的能力),但是这个已经被springmvc加载完成了,导致@Service失去了事务处理能力,就不能处理事务了

综上所述: 在applicationContext.xml加载所有包,除了Controller,在springmvc.xml中加载所有包,除了Service

applicationContext.xml

image.png

springmvc.xml

image.png

五 事务配置的属性(理论)

1.isolation: 设置事务的隔离级别

READ_UNCOMMITTED 读未提交

READ_COMMITTED 读已提交

REPEATABLE_READ 可重复读

SERIALIZABLE 串行化

并发: 同一时间,多个请求同时对数据库进行读写操作,会产生并发问题(脏读,不可重复读,幻读)

(1)脏读

脏读: 事务一读取了事务二未提交的数据,我们简称这种数据为"脏数据"

解决方案: 设置隔离级别为读已提交(READ_COMMITTED)

(2)不可重复读

不可重复读: 一个事务中,多次读取相同的数据,但是结果不一样,会在本事务中产生数据不一致的问题

解决方案: 设置隔离级别为不可重复读(REPEATABLE_READ)

REPEATABLE_READ本质: 在多次读取一个字段中的数据时,禁止其他事务在此期间对这个字段进行更新(加行锁)

(3)幻读

幻读: 在一个事务中,多次对数据进行整合的时候,结果不一致,会在本事务中产生数据不一致的问题

解决方案: 设置隔离级别为串行化(SERIALIZABLE)

SERIALIZABLE本质: 在事务1多次整合同一个表中的数据时,禁止其他事务对该表进行增删改查,但是性能十分低下(加表锁)

2.propagation: 事务传播行为(偏理论)

事务传播行为类型外部不存在事务外部存在事务
REQUIRED开启新的事务融合到外部事务中
SUPPORTS不开启新的事务融合到外部事务中
REQUIRES_NEW开启新的事务不用外部事务,创建新的事务

融合到外部事务中: 就是把自己和外部作为一个事务

#Transactional
public int trans(){
    log();
}

@Transacgtional
public int log(){
    
}

比如说trans事务有两条select语句,而log事务有两条insert语句,如果融合在一起就是四条sql"同生共死",要么一起成功,要么一起回滚.

3.readOnly: 设置事务为只读事务

这个一般用于查询,告诉数据库当前操作仅限于查询,数据库会做相应优化

4.timeout: 事务超出指定时长自动终止并且回滚

事务1访问某一些数据的时候,可能该数据正在被其他事务访问,处于禁止状态,我们需要设置一下时长,否则给用户造成体验感差

六 基于xml的事务配置

image.png

<!--用于声明事务切入的所有方法-->
<aop:config>
    <aop:pointcut id="transactionCut" expression="execution(* com.cctv.service.impl.*.*(..))"/>
    <aop:advisor advice-ref="advice" pointcut-ref="transactionCut"/>
</aop:config>

<!--用来明确切点匹配到的方法哪些方法需要使用事务-->
<tx:advice id="advice" transaction-manager="transactionManager">
    <tx:attributes>
        <!--可以使用通配符-->
        <tx:method name="add*"/>
        <tx:method name="update*"/>
        <tx:method name="delete*"/>
        <tx:method name="get*" read-only="true" propagation="SUPPORTS"/>
        <tx:method name="query*" read-only="true"/>
    </tx:attributes>
</tx:advice>

aop:config里面的expression就是aop表达式,前面已经讲过

可以通过tx:advice来设置事务传播行为方式,隔离级别等等.