Spring从入门到精通(四)
1. Spring的事务管理
1.1. 事务是什么?
在操作数据库时(增删改),如果同时操作多次数据,我们从业务希望,要不全部成功,要不全部失败。这种情况称为事务处理。
A转账给B。
第一步,扣除A君账号要转的金额
第二步,增加B君账号的金额
事务:指单个逻辑操作单元的集合
1.2. Spring事务控制我们要明确的
1.JavaEE体系进行分层开发,事务处理位于业务层,所以,一般情况下我们使用事务代理,一般放在分层设计业务层。
2.spring框架为我们提供了一组事务控制的应用程序接口(API)。
3.spring的事务控制都是基于AOP的,它既可以使用编程的方式实现,也可以使用配置的方式实现。我们学习的重点是使用配置的方式实现。
1.3. 案例引出问题
需求:从ID为10086账户给ID为10010账户转账1000元钱。
数据准备:account表(账户): ------------------------- ID BALANCE ------------------------- 10010 0 10086 1000 |
设计一个 Account表 |
CREATE TABLE `t_account` ( `id` int(11) NOT NULL AUTO_INCREMENT, `balance` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=10087 DEFAULT CHARSET=utf8; |
编写转账案例 |
1.4. 转账案例分析
结论: 根据上述案例分析, 事务管理应该是在Service层处理
1.5. 数据库并发问题
什么是数据库并发问题?
并发: 多个客户端同时同时访问数据库中某一条数据(秒杀)
数据库可以拥有多个访问客户端,若多个客户端并发地访问数据库中相同的资源,如果没有采取必要的隔离措施,则会导致各种并发问题,破坏数据的完整性。
这些问题归结为5类
包括3类数据读问题(脏读,不可重复读,幻读)
和2类数据更新问题(第一类丢失更新,第二类丢失更新)。 看图
1.5.1. 第一类更新丢失
两个事务更新相同数据,如果一个事务提交,另一个事务回滚,第一个事务的更新会被回滚
1.5.2. 第二类丢失更新
多个事务同时读取相同数据,并完成各自的事务提交,导致最后一个事务提交会覆盖前面所有事务对数据的改变
1.5.3. 脏读
第二个事务查询到第一个事务未提交的更新数据,第二个事务根据该数据执行,但第一个事务回滚,第二个事务操作脏数据
1.5.4. 幻读
一个事务查询到了另一个事务已经提交的新数据,导致多次查询数据不一致
1.5.5. 不可重复读
一个事务查询到另一个事务已经修改的数据,导致多次查询数据不一致
1.6. 数据库事务的隔离级别
问题:上述问题理论上如果出现了应该如何解决?
答:一般情况,数据库都会处理一些事务并发的问题,数据库提供了不同的事务隔离级别来处理不同的事务并发问题,事务隔离级别定义如下:
隔离级别 |
说明 |
READ_UNCOMMITED |
允许你读取还未提交的改变了的数据。可能导致脏、幻、不可重复读(相当于没有做任何事务隔离) |
READ_COMMITTED |
允许在并发事务已经提交后读取。可防止脏读,但幻读和 不可重复读仍可发生(ORACLE默认级别) |
REPEATABLE_READ |
对相同字段的多次读取是一致的,除非数据被事务本身改变。可防止脏、不可重复读,但幻读仍可能发生。(MYSQL默认级别) |
SERIALIZABLE |
完全服从ACID的隔离级别,确保不发生脏、幻、不可重复读。这在所有的隔离级别中是最慢的,它是典型的通过完全锁定在事务中涉及的数据表来完成的。(ORACLE支持)
|
针对不同隔离级别可以解决的的如下五类问题
1.6.1. 解决丢失更新的方案
数据库表数据加锁
1,悲观锁
(1) 在操作当前数据的事务开启事务就使用for update 锁住当前数据
(2) Hibernate和MyBatis都有悲观锁对应的解决方案
2,乐观锁
(1) 为表添加一个version字段。当前事务操作的时候都会比对当前事务情况的多次操作的版本号是否一致,如果不一致认为数据已经被更新
(2) Hibernate和MyBatis都有乐观锁对应的解决方案
1.7. Spring对事务的支持
- 为什么需要使用Spring是事务
答:使用Spring是事务代理,可以配置一次,不用重复编写事务处理代码!!
Spring框架针对事务处理提供专门的解决方案
Spring的事务管理主要包括3个接口
Spring事务处理接口 |
描述 |
TransactionDefinition |
封装事务的隔离级别超时时间,是否为只读事务和事务的隔离级别和传播规则等事务属性,可通过XML配置具体信息 |
PlatformTransactionManager |
根据TransactionDefinition提供的事务属性配置信息,创建事务。事物管理器 |
TransactionStatus |
封装了事务的具体运行状态。比如,是否是新开启事务,是否已经提交事务,设置当前事务为rollback-only等 |
1.7.1. TransactionDefinition
该接口主要定义了:事务的传播行为(规则),事务的隔离级别,获得事务信息的方法。所以在配置事务的传播行为,事务的隔离级别已经需要获得事务信息时,可以通过查阅该类的代码获得相关信息。
TransactionDefinition源码
public interface TransactionDefinition {
//事务的传播行为 int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4; int PROPAGATION_NEVER = 5; int PROPAGATION_NESTED = 6; //事务的隔离级别 int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE; //事务超时管理 int TIMEOUT_DEFAULT = -1;
//获得事务信息 int getPropagationBehavior();
int getIsolationLevel();
int getTimeout();
boolean isReadOnly();
String getName();
}
|
1.7.1.1. 事务传播规则
Spring在TransactionDefinition接口中定义了七种事务传播规则,规定了事务方法和事务方法发生嵌套调用时事务该如何进行传播
事务传播规则类型 |
描述 |
PROPAGATION_REQUIRED |
如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务(最常用操作)-Spring默认使用的就是此规则 |
PROPAGATION_REQUIRES_NEW |
创建一个新的事务,如果当前存在事务,则把当前事务挂起 |
PROPAGATION_SUPPORTS |
如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行 |
PROPAGATION_NOT_SUPPORTED |
以非事务方式运行,如果当前存在事务,则把当前事务挂起 |
PROPAGATION_NEVER |
以非事务方式运行,如果当前存在事务,则抛出异常 |
PROPAGATION_MANDATORY |
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常 |
PROPAGATION_NESTED |
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED |
1.7.2. PlatformTransactionManager事务管理器
Spring的事务管理:
1,PlatformTransactionManager:接口统一抽象处理事务操作相关的方法;
1):TransactionStatus getTransaction(TransactionDefinition definition):
根据事务定义信息从事务环境中返回一个已存在的事务,或者创建一个新的事务,并用TransactionStatus描述该事务的状态。
2):void commit(TransactionStatus status):
根据事务的状态提交事务,如果事务状态已经标识为rollback-only,该方法执行回滚事务的操作。
3):void rollback(TransactionStatus status):
将事务回滚,当commit方法抛出异常时,rollback会被隐式调用
2,在使用spring管理事务的时候,首先得告诉spring使用哪一个事务管理器,使用不同的框架(JdbcTemplate,MyBatis,Hibernate/JPA )使用事务管理器都不同
1.7.2.1. PlatformTransactionManager事物管理器的继承体系图
|
3,常用的事务管理器:
DataSourceTransactionManager:使用JDBC,MyBatis的事务管理器;
1.8. Spring事务的配置
Spring支持编程式事务管理和声明式事务管理。
- 编程式事务管理:事务和业务代码耦合度太高。
- 声明式事务管理:侵入性小,把事务从业务代码中抽离出来,使用AOP配置到配置文件中,提高维护性。
1.8.1. 声明式事务管理-xml方式配置
1.8.1.1. 准备配置文件
在配置文件中引入新的命名空间 tx
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd "> |
1.8.1.2. 配置事物管理器-DataSourceTransactionManager
Spring对象事务的支持,必须先配置事务管理器,事务管理器已经封装了事务的具体的处理
DataSourceTransactionManager
使用JDBC,MyBatis的事务管理器;(当前案例使用的Spring的JDBC操作,所以配置这个)
<!-- 1,配置事务管理器(应根据情况使用合适的事务管理器) --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入dataSource --> <property name="dataSource" ref="dataSource"/> </bean> <!-- 1、切入点(Pointcut):在哪些类,哪些方法上切入(where); 2、增强(Advice):早期翻译为通知,在方法执行的什么时机(when:方法前/方法后/方法前后)做什么(what:增强的功能); 3、切面(Aspect):切面=切入点+通知,通俗点就是:什么时机,什么地点,做什么! 4、织入(Weaving):把切面加入到对象,并创建出代理对象的过程。(该过程由Spring来完成)。 WWW 原则 : where : 在什么地方切事务 what : 干什么(切事务) when : 时机(在方法前或者后) -->
<!-- 事务相关的配置 : 使用AOP面向切面编程(切事务) : 使用 tx: 标签 --> <!-- tx:advice : 配置事物的标签 id : 唯一标识 transaction-manager : 需要用到的事物管理器 --> <!-- 2,配置管理事务的“增强” :环绕通知--> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <!-- 配置要切的方法 : trans (默认使用的是环绕通知) --> <tx:method name="trans"/> </tx:attributes> </tx:advice>
<!-- 3,面向切面 : --> <aop:config > <!-- 切入点 : where --> <aop:pointcut expression="execution ( * cn.zj.spring.service..*.*(..))" id="pt"/> <!-- 配置切面 切面 = 切入点 + 通知 advice-ref 通知的引用 pointcut-ref 切入点的引入 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt" /> <!-- 织入(Weaving):把切面加入到对象,并创建出代理对象的过程。(该过程由Spring来完成) --> </aop:config> |
1.8.1.3. 事物方法的属性细节配置
事务方法属性:
事务方法属性 |
描述 |
name |
匹配到的方法模式 |
read-only |
如果为true,开启一个只读事务,只读事务的性能较高,但是不能再只读事务中,使用DML |
isolation |
代表数据库事务隔离级别(就使用默认) DEFAULT:让spring使用数据库默认的事务隔离级别; |
no-rollback-for |
如果遇到的异常是匹配的异常类型,就不回滚事务 |
rollback-for |
如果遇到的异常是指定匹配的异常类型,才回滚事务;spring默认情况下,spring只会去回滚RuntimeException及其子类,不会回滚Exception和Thowable |
propagation |
事务的传播方式(当一个方法已经在一个开启的事务当中了,应该怎么处理自身的事务) 1,REQUIRED(默认的传播属性):如果当前方法运行在一个没有事务环境的情况下,则开启一个新的事务,如果当前方法运行在一个已经开启了的事务里面,把自己加入到开启的那个事务中 2,REQUIRES_NEW:不管当前方法是否运行在一个事务空间之内,都要开启自己的事务
|
timeout |
事务处理的超时时间,默认事物管理自动处理 ,可以手动配置
|
<tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes>
<!-- <tx:method> name : 需要切面的方法 isolation : 事务的隔离级别 DEFAULT 使用当前数据库默认的隔离级别,不同数据库隔离级别不同(可以不配置) propagation : 事物的传播规则 ,默认使用 REQUIRED read-only : 是否是只读事务, DQL配置即可 --> <!-- 一般DML操作才需要事务,DQL查询操作是不需要事物 * 通配符 --> <!-- 所有以前缀开都的方法都认为是 查询方法,就不切入事物 --> <tx:method name="get*" read-only="true"/> <tx:method name="select*" read-only="true" /> <tx:method name="find*" read-only="true"/> <tx:method name="query*" read-only="true"/> <tx:method name="list*" read-only="true"/>
<!-- 非查询(DQL)方法: DML操作,需要事务管理 --> <tx:method name="*" /> </tx:attributes> </tx:advice> |
1.8.2. 声明式事务管理-基于注解配置
Spring的声明式事务也支持 注解
1.8.2.1. applicationContext.xml配置文件
<!-- 1,配置事务管理器(应根据情况使用合适的事务管理器) --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入dataSource --> <property name="dataSource" ref="dataSource"/> </bean>
<!-- 开启注解驱动配置事务 编写此标签:Spring底层就支持注解配置事物,并且已经配置好事物管理器 --> <tx:annotation-driven transaction-manager="txManager"/> |
1.8.2.2. AccountServiceImpl 业务层代码
@Service /* @Transactional * 贴上此当前类已经被Spring事务管理 * 注意: @Transactional 只能对当前贴的Service类有效 * 常用属性 : * isolation=Isolation.REPEATABLE_READ, 隔离级别 propagation=Propagation.REQUIRED,传播规则 readOnly=true 是否只读事务 * */@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED) public class AccountServiceImpl implements AccountService{ @Autowired private AccountDao dao;
public void trans(Integer transOutId, Integer transInId, Integer money) { dao.tranOut(transOutId, money); System.out.println(1 / 0);// 模拟断电 dao.tranIn(transInId, money); } //单独为某一个方法配置具体的事物细节:如查询方法,事物是只读的 @Transactional(readOnly=true) public Object getUser() { //查询操作 return null; } @Transactional(readOnly=true) public List<Object> getUsers() { //查询操作 return null; } } |
1.8.3. 事物配置的注解和XML配置的选择
Xml配置 : 代码清晰,配置量少,容易维护
注解配置 : 侵入代码,每个Service层类都得配置,针对不同的方法还得配置事务细节:如查询方法配置只读操作,不容易维护
建议选择xml配置
2. 小结
Spring框架出现原因?
早期企业级开发组件(框架) EJB (重量级)不好用
音乐博士,计算机大牛
觉得ejb 不好,写了一本书各种吐槽抨击 ejb
顺便写了一个轻量级 一站式 企业框架 spring
2.1. Spring 核心作用
解耦 (降低程序代码与代码的直接依赖关系)
2.2. 核心功能
Spring容器-下面所有操作都在Spring 容器中完成,Spring就是项目对象的管家
1. IOC :控制反转(将对象的创建权交给Spring管理)
(1) Xml 配置
① <bean id/name=’xxx’ class = “类的全限定名” scope=’作用范围’ init-mehtod=’初始化方法’destory-mehtod=’销毁方法’>
② 从Spring容器中读取bean对象
(2) 注解配置 -贴在类上即可
① @Component 通用组件扫描创建
② @Controller 表现层(SpringMVC 专用)
③ @Service 业务层专用
④ @Repository 持久层/DAO/Mapper 专用
⑤ @Scope 作用范围
⑥ @PostConstrcut 初始化方法
⑦ @PreDestory 销毁方法
2. DI : 依赖注入(将对象的属性赋值,对象依赖关系维护交给Spring)
(1) XML配置
① <property name=’’ value/ref=’’> 属性注入
② <constructor-arg name=’参数名’ value/ref=’’ >
③ P 命名空间
1) <bean id =’xx’ class =”Xxxx” p:属性名=’值’ p:属性名-ref=’引用类型’>
(2) 注解配置
① Spring框架制定
1) @Autowired 默认按照类型注入
- a. 可以贴在 字段(成员编写),set方法,构造函数
2) @Qualifier 从多个相同类型中通过id指定唯一的那个bean
② JavaEE 官方指定
1) @Resource(name=’bean的id’)
3. AOP :面向切面编程
(1) AOP 底层原理-Java动态代理
① JDK动态代理 : 只能有接口的类型进行代理
② CGLIB代理 :即可代理有接口类,有可以代理没有接口的
(2) 专业术语
① JoinPoint 连接点(方法)
② PointCut 切入点 (某一类方法,规则)
③ Advice 通知/增强
④ Aspect 切面 = 切入点+通知
⑤ Weaving 织入(Spring自动完成)
4. Spring事务管理
(1) 配置事务管理器
① JDBC/MyBatis ---->DataSourceTransactionManager
(2) 事务的隔离级别 4个
(3) 事务的传播规则(行为) 7个
(4) 是否是只读事务
(5) 使用AOP 将事务切入到 业务层
5. Spring-Jdbc (了解)
(1) jdbcTemplate 模板类
① Update 更新方法(update,insert,delete)
② queryForObject : 单行查询
③ Query : 多行查询
6. Spring-test
7. SpringMVC