笔记大纲
-
事务简介(ACID)
-
Spring事务管理
- 编程式事务管理
- 声明式事务管理
- Spring提供的事务管理器
- 事务管理器的主要实现
-
声明事务解决常见的库存减了、余额不足的问题
-
事务的传播特性
-
事务的隔离级别
- 数据库事务并发问题
- 隔离级别
-
触发事务回滚的异常
- 默认情况
- 设置途经
-
事务的超时和只读属性
1.事务简介(ACID)
在JavaWeb企业级开发的应用中,为了保证数据的完整性和一致性,引入了数据库事务概念。
事务是一组工作单元的多个数据库操作,这些操作要么执行,要么都不执行!
事务四大特性(ACID)
(1)原子性(atomicity):不可再分,可认为事务中的操作要么都执行,要么都不执行。
(2)一致性(consistency):数据的一致性,例如转账的场景,小林账户有5000,小刘账户有1000,两人账户共6000。这时小林跟小刘转账成功2000,此时小林账户应该减去2000,剩余3000;而小刘账户应加上2000,现有3000。两人账户总额是不变的!
(3)隔离性(isolation):多个事务在并发执行过程中不会互相干扰。
(4)持久性(durability):事务执行完后,就会永久的保存在数据库中。
2.Spring事务管理
事务管理一般有两种,编程式务管理与声明式事务管理,在实际开发中,声明式事务管理是必备技能!
2.1.编程式事务管理
使用原生的JDBC API进行事务管理,是所有事务管理方式的基石,编程式事务管理需将事务管理代码嵌入到业务方法中来控制事务的提交和回滚,在使用编程的方式管理事务时,必须在每个事务操作中包含额外的事务管理代码。相对核心业务来说,事务管理的代码显然属于非核心业务,如多个模块都使用同样模式的代码进行事务管理,显然会造成较大程度的代码冗余!
应用操作步骤(6步):
①获取数据库连接Connection对象
②取消事务的自动提交
③执行操作
④正常完成操作时手动提交事务
⑤执行失败时回滚事务
⑥关闭相关资源
2.2.声明式事务管理
大多情况,声明式事务比编程式事务管理更好,它将事务管理代码从业务方法分离出来,以声明的方式来实现事务管理。事务管理代码的固定模式作为一种横切关注点,可通过AOP方法模块化,借助Spring AOP框架实现声明式事务管理!
Spring支持编程式事务管理,也支持声明式的事务管理。
注意:之前事务控制在DAO(数据访问层),实际项目事务应控制在Service(逻辑处理层)。
2.3.Spring提供的事务管理器
Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。
Spring的核心事务管理抽象是它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。
事务管理器可以以普通的bean的形式声明在Spring IOC容器中!
2.4.事务管理器的主要实现
3.声明事务解决常见的库存减了、余额不足的问题
(1)在xml文件中配置事务管理器,开启事务相关注解支持
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"></property>
</bean>
<!-- 开启事务注解支持
注意:transaction-manager默认值为transactionManager,
如果事务管理器的名字为transactionManager,那么该属性可以省略!
-->
<tx:annotation-driven transaction-manager="transactionManager"/>
(2)在需要进行事务控制的方法上加注解@Transcational
(3)@Transcational可标注的位置:
①类上,对类的所有的方法起作用;
②方法上:只对当前的方法起作用;
③如果类上和方法上都加了注解,那么方法上的优先!!!
4.事务的传播特性
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
比如:方法可能继承在现有的事务中运行,也可能开启了一个新的事务,并在自己的事务中运行。
事务的传播行为可以由传播属性指定,Spring定义了7种类传播行为。
事务传播属性可以再
@Transactional
注解的propagation
属性中定义!
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void testMethod() {}
传播属性(行为) | 描述 |
---|---|
required (默认值) | 如果有事务在运行,当前的方法在整个事务内运行,否则,就启动一个新的 事务,并在自己的事务内运行。 |
required_new | 当前的方法必须启动新事务,并在它自己的事务内运行,如果事务在 正运行,应该将它挂起。 |
supports | 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中。 |
not_supporte | 当前的方法不应该运行在事务中,如果有运行的事务,将它挂起。 |
mandatory | 当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常。 |
never | 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常。 |
nested | 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行。 |
5.事务的隔离级别
5.1.数据库事务并发问题
应用场景:现在有两个事务:Transactonal_A和Transactonal_B并发执行。
(1)脏读<更新后读取,则无效值!>
Transactonal_A将数据库中某记录中的age值从10变成了20;
Transactonal_B从数据库中读取了Transactonal_A更新后的值20;
Transactonal_A进行回滚操作,age修改成了原值10;
Transactonal_B读取到的20就是一个无效的值!
(2)不可重复读<第一次读取后,第二次再读取修改值,不一致!>
Transactonal_A读取了age值为10;
Transactonal_B将age值修改成了20;
Transactonal_A再次读取age值为20,和第一次读取不一致!
(3)幻读<第一次读取后,增加新的行,再次读>
Transactonal_A读取了db_table表中的部分数据;
Transactonal_B向db_table表中插入新的行;
Transactonal_A读取了db_table表时,多出了一些行。
5.2.隔离级别
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。
一个事务与其他事务隔离的程度称为隔离级别。
SQL标准中规定了多种事务隔离级别,不同的隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但是并发性越弱!
Mysql的默隔级别是可重复读(repeatable read)、Orcale的默隔级别是读已提交(read commited)!
隔离级别 | 说明 |
---|---|
读未提交(read uncommited) | 允许Transactonal_A与Transactonal_B未提交的事务 |
读已提交(read commited) | 要求Transactonal_A只能读ransactonal_B已提交的 |
可重复读(repeatable read) | 确保Transactonal_A可多次从一个字段中读取相同的值 即Transactonal_A执行期间禁止其它事务对个这个字段进行修改。 |
串行化(serializable) | 确保Transactonal_A可多次从一个表中读取到相同的行, 即Transactonal_A执行期间,禁止其它事务对这个表进行增删改操作。 |
以上隔离级别解决并发问题的能力如下:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
read uncommited | 有 | 有 | 有 |
read commited | 无 | 有 | 有 |
repeatable read | 无 | 无 | 有 |
serializable | 无 | 无 | 无 |
MySQL与Oracle数据库对事务隔离级别的支持程度如下:
隔离级别 | Oracle | MySQL |
---|---|---|
read uncommited | × | √ |
read commited | √(默认) | √ |
repeatable read | × | √(默认) |
serializable | √ | √ |
6.触发事务回滚的异常
6.1.默认情况
捕获到RuntimeException或Error时回滚,而捕获到编译时异常不回滚!
6.2.设置途经
(1)注解@Transactional 注解
① rollbackFor属性:指定遇到时必须进行回滚的异常类型,可以为多个;
② noRollbackFor属性:指定遇到时不回滚的异常类型,可以为多个。
(2)XML中配置
在Spring 2.x事务通知中,可以在tx:method
元素中指定回滚规则。如果有不止一种异常则用逗号分隔。
7.事务的超时和只读属性
事务可在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响。如果一个事务只读取数据但不做修改,数据库引擎可以对这个事务进行优化。
超时事务属性:事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源。
timeout=30
(注30是30s)。只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。
readOnly=true
(启动数据本身的一个优化策略,一般在查询时开启,增删改不开启!)。
@Transactional(propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
rollbackFor = java.lang.Exception.class,
noRollbackFor = java.lang.NullPointerException.class,
timeout = 30,
readOnly = false)
public void tranMethodTest(String a, String b) {}
XML中也设置,在Spring 2.x事务通知中,可以在tx:method
元素中进行指定!
<?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.codinglin.spring.tx.xml"></context:component-scan>
<!-- 读取外部配置文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置数据源,连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:advice id="myAdvice" transaction-manager="dataSourceTransactionManager">
<!-- 事务属性的配置 -->
<tx:attributes>
<tx:method name="insert*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.Exception" timeout="30" read-only="false"/>
<tx:method name="delete*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.Exception" timeout="30" read-only="false"/>
<tx:method name="update*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.Exception" timeout="30" read-only="false"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="select*" read-only="true"/>
<!-- 以上都没匹配的情况下进行匹配 -->
<tx:method name="*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.Exception" timeout="30" read-only="false"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!-- 将通知作用在哪,指定切入点 -->
<aop:advisor advice-ref="myAdvice" pointcut="execution(* com.codinglin.spring.tx.xml.service.*.*(..))"/>
</aop:config>
</beans>
☝上述分享来源个人总结,如果分享对您有帮忙,希望您积极转载;如果您有不同的见解,希望您积极留言,让我们一起探讨,您的鼓励将是我前进道路上一份助力,非常感谢!我会不定时更新相关技术动态,同时我也会不断完善自己,提升技术,希望与君同成长同进步!
☞本人博客:https://coding0110lin.blog.csdn.net/ 欢迎转载,一起技术交流吧!