一 事务的概念
事务: 就是把一组业务当成一个业务来处理,要么都成功,要么都失败,保证业务操作的完整性的一种数据库机制. 例如: 张三给李四转账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文件中加入这样的配置
<!--声明式事务-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--基于驱动-->
<tx:annotation-driven transaction-manager="transactionManager"/>
声明式事务中的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
springmvc.xml
五 事务配置的属性(理论)
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的事务配置
<!--用于声明事务切入的所有方法-->
<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来设置事务传播行为方式,隔离级别等等.