一、事务
概念:数据库特性;
在进行数据库操作时,为了保证数据的安全以及减少错误的发生,一般都会进行事务的管理,数据库在进行操作时,内部也进行了简单的事务管理,只不过在进行更复杂操作时,这种默认的事务不足以进行相应需求的实现,这个时候就需要我们手动的进行事务的管理
1.事务的ACID原则
事务的原子性( Atomicity):一组事务,要么成功;要么撤回。
一致性 (Consistency):事务执行后,数据库状态与其他业务规则保持一致。如转账业务,无论事务执行成功否,参与转账的两个账号余额之和应该是不变的。
隔离性(Isolation):事务独立运行。一个事务处理后的结果,影响了其他事务,那么其他事务会撤回。事务的100%隔离,需要牺牲速度。
持久性(Durability):软、硬件崩溃后,InnoDB数据表驱动会利用日志文件重构修改。可靠性和高速度不可兼得, innodb_flush_log_at_trx_commit 选项 决定什么时候吧事务保存到日志里。
2.事务处理
2.1mysql数据库中 使用sql语句进行事务处理
#在执行多条sql前手动开启事务
start transaction;
update student set studentname='Bill' where studentid='2010005';
update student set studentage=88 where studentid='2010005';
#在sql执行结束后手动提交事务
commit;
#mysql中如果事务在执行过程中发生异常导致事务执行出现问题,会自动进行回滚
#将已经执行的更改进行撤销
rollback;
2.2jdbc中进行事务处理
jdbc进行事务处理的语法
获取连接对象con
设置con.setAutoCommit(false);//关闭事务的自动提交-》开启事务
执行多条sql
对执行sql语句进行捕获如果出现异常则con.rollback();回滚事务
执行成功则 con.commit(); 提交事务
// mysql默认开启事务的自动提交
// 每执行1条SQL语句都是一个事务
Class.forName("com.mysql.jdbc.Driver");
Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/teacher", "root", "root");
try {
// 默认事务自动提交 每执行1条sql一个事务 其他事务不会影响该事务的执行
String sql = "update student set studentname='Bill' where studentid='2010005' ";
// 事务的开启
// 在执行sql之前设置当前连接
// 将自动提交关闭
con.setAutoCommit(false);
PreparedStatement ps = con.prepareStatement(sql);
ps.executeUpdate();
// 第二条sql语句报错没有执行成功 但是第一条数据已经执行成功存入数据库
String sql2 = "update student set studesntage=28 where studentid='2010005' ";
PreparedStatement ps2 = con.prepareStatement(sql2);
ps2.executeUpdate();
} catch (Exception e) {
// 对执行的sql语句进程try catch 如果出现异常回滚事务
con.rollback();
}
// 如果执行成功则提交事务将执行的结果保存
con.commit();
3.事务的隔离级别
3.1并发事务导致的问题
并发事务导致的问题大致有5类,其中两类是更新问题三类是读问题。
· 脏读(dirty read):读到另一个事务的未提交新数据,即读取到了脏数据;
· 不可重复读(unrepeatable):对同一记录的两次读取不一致,因为另一事务对该记录做了修改;
· 幻读(虚读)(phantom read):对同一张表的两次查询不一致,因为另一事务插入了一条记录。
3.2四大隔离级别
1、SERIALIZABLE(串行化)
所有事务串行依次执行,不会发生任何数据问题,但是效率最低
2、REPEATABLE READ(可重复读)(MySQL)
mysql默认的事务隔离级别,事务之间在进行操作时,对操作的数据进行限制,不允许其他事务对当前事务正在处理的数据(行)进行操作,可以避免脏读和不可重复读,效率比串行化高一点
3、READ COMMITTED(读已提交数据)(Oracle)
oracle数据库默认的事务隔离级别,多个事务进行执行,除了可以任意读取操作其他事务没有处理的数据外,只能读取其他事务已经提交了的数据,可以防止脏读,但是不能防止不可重复读与幻读,效率比可重复读高一点
4、READ UNCOMMITTED(读未提交数据)
不进行任何处理,不能解决任何隔离问题,但效率最高
3.3mysql设置隔离级别
MySQL的默认隔离级别为Repeatable read,可以通过下面语句查看:
SELECT @@TX_ISOLATION
;
也可以通过下面语句来设置当前连接的隔离级别:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ ;//[4选1]
3.4JDBC设置隔离级别
con.setTransactionIsolation(int level) :
//conn.setTransactionLeve (TRANSACTION_SERIALIZABLE);
参数可选值如下:
· Connection.TRANSACTION_READ_UNCOMMITTED;
· Connection.TRANSACTION_READ_COMMITTED;
· Connection.TRANSACTION_REPEATABLE_READ;
· Connection.TRANSACTION_READ_SERIALIZABLE。
3.5测试示例
例子使用InnoDB,开启两个客户端A,B,在A中修改事务隔离级别,在B中开启事务并修改数据,然后在A中的事务查看B的事务修改效果(两个客户端相当于是两个连接,在一个客户端中的修改参数变量的值是不会影响到另外的一个客户端的):
cmd窗口登录: MySQL -u root -p
1.READ-UNCOMMITTED(读取未提交内容)级别
1)A修改事务级别并开始事务,对user表做一次查询
show databases;
use mybaties;
show tables;
set session transaction isolation level read uncommitted;
set autocommit=off; 关闭自动提交
select * from account;
2)B更新一条记录
update account set money=899 where id=1;
3)此时B事务还未提交,A在事务内做一次查询,发现查询结果已经改变
select * from account;// 1 jack money为899;
4)B进行事务回滚
rollback;
5)A再做一次查询,查询结果又变回去了
select * from account;// 1 jack money为999;
6)A表对user表数据进行修改
update account set money=899 where id=1;
7)B表重新开始事务后,对user表记录进行修改,修改被挂起,直至超时,但是对另一条数据的修改成功,说明A的修改对user表的数据行加行共享锁(因为可以使用select)
set autocommit=on;
update account set money=799 where id=1;
可以看出READ-UNCOMMITTED隔离级别,当两个事务同时进行时,即使事务没有提交,所做的修改也会对事务内的查询做出影响,这种级别显然很不安全。但是在表对某行进行修改时,会对该行加上行共享锁
错误提示: ERROR 1205 (HY000) Lock wait timeout exceeded; try restarting transaction;
2. READ-COMMITTED(读取提交内容)
1)设置A的事务隔离级别,并进入事务做一次查询
set session transaction isolation level read committed;
set autocommit=off; 关闭自动提交
select * from account;// 1 jack money为999;
2)B开始事务,并对记录进行修改
set autocommit=on;
update account set money=899 where id=1;
select * from account;// 1 jack money为899;
3)A再对user表进行查询,发现记录没有受到影响
select * from account;// 1 jack money为999;
4)B提交事务
commit;
5)A再对user表查询,发现记录被修改
select * from account;// 1 jack money为899;
6)A对user表进行修改
update account set money=799 where id=1; //成功
7)B重新开始事务,并对user表同一条进行修改,发现修改被挂起,直到超时,但对另一条记录修改,却是成功,说明A的修改对user表加上了行共享锁(因为可以select)
update account set money=699 where id=1; //成功
错误提示: ERROR 1205 (HY000) Lock wait timeout exceeded; try restarting transaction;
READ-COMMITTED事务隔离级别,只有在事务提交后,才会对另一个事务产生影响,并且在对表进行修改时,会对表数据行加上行共享锁
3. REPEATABLE-READ(可重读)
1)A设置事务隔离级别,进入事务后查询一次
set session transaction isolation level repeatable read;
2)B开始事务,并对user表进行修改
update account set money=699 where id=1; //成功
3)A查看user表数据,数据未发生改变
select * from account where id=1;// 1 jack money为999
4)B提交事务
commit;
5)A再进行一次查询,结果还是没有变化
select * from account where id=1;// 1 jack money为999
6)A提交事务后,再查看结果,结果已经更新
commit
select * from account where id=1;// 1 jack money为699
7)A重新开始事务,并对user表进行修改
update account set money=699 where id=2; //成功
8)B表重新开始事务,并对user表进行修改,修改被挂起,直到超时,对另一条记录修改却成功,说明A对表进行修改时加了行共享锁(可以select)
update account set money=699 where id=2; //失败
错误提示: ERROR 1205 (HY000) Lock wait timeout exceeded; try restarting transaction;
REPEATABLE-READ事务隔离级别,当两个事务同时进行时,其中一个事务修改数据对另一个事务不会造成影响,即使修改的事务已经提交也不会对另一个事务造成影响。
在事务中对某条记录修改,会对记录加上行共享锁,直到事务结束才会释放。
4.SERIERLIZED(可串行化)
1)修改A的事务隔离级别,并作一次查询
2)B对表进行查询,正常得出结果,可知对user表的查询是可以进行的
3)B开始事务,并对记录做修改,因为A事务未提交,所以B的修改处于等待状态,等待A事务结束,最后超时,说明A在对user表做查询操作后,对表加上了共享锁
SERIALIZABLE事务隔离级别最严厉,在进行查询时就会对表或行加上共享锁,其他事务对该表将只能进行读操作,而不能进行写操作。
二、声明式事务控制
1. 编程式事务控制相关对象
1.1 PlatformTransactionManager 事务管理平台
PlatformTransactionManager 接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法。
注意:
PlatformTransactionManager 是接口类型,不同的 Dao 层技术则有不同的实现类;
例如:Dao 层技术是jdbc 或 mybatis 时:org.springframework.jdbc.datasource.DataSourceTransactionManager
Dao 层技术是hibernate时:org.springframework.orm.hibernate5.HibernateTransactionManager
1.2 TransactionDefinition 事务定义
TransactionDefinition 是事务的定义信息对象,里面有如下方法:
1. IsolationLevel 事务隔离级别
设置隔离级别,可以解决事务并发产生的问题,如脏读、不可重复读和虚读。
-
ISOLATION_DEFAULT 默认
-
ISOLATION_READ_UNCOMMITTED 读未提交
-
ISOLATION_READ_COMMITTED 读已提交
-
ISOLATION_REPEATABLE_READ 可重复读
-
ISOLATION_SERIALIZABLE 串行化
2. PropogationBehavior事务传播行为
-
REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
-
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
-
MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
-
REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
-
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
-
NEVER:以非事务方式运行,如果当前存在事务,抛出异常
-
NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作
-
超时时间:默认值是-1,没有超时限制。如果有,以秒为单位进行设置
-
是否只读:建议查询时设置为只读
1.3 TransactionStatus
TransactionStatus 接口提供的是事务具体的运行状态,方法介绍如下。
1.4 知识要点
编程式事务控制三大对象
-
PlatformTransactionManager 事务管理平台
-
TransactionDefinition 事务定义
-
TransactionStatus 事务状态
2 基于 XML 的声明式事务控制
2.1 什么是声明式事务控制
Spring 的声明式事务顾名思义就是采用声明的方式来处理事务。这里所说的声明,就是指在配置文件中声明,用在 Spring 配置文件中声明式的处理事务来代替代码式的处理事务。
声明式事务处理的作用
-
事务管理不侵入开发的组件。具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可
-
在不需要事务管理的时候,只要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译,这样维护起来极其方便
注意:Spring 声明式事务控制底层就是AOP。
2.2 声明式事务控制的实现
声明式事务控制明确事项:
-
谁是切点?
-
谁是通知?
-
配置切面?
①引入tx命名空间
<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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" 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 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
②配置事务增强
<!--平台事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!--事务增强配置--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*"/> </tx:attributes> </tx:advice>
③配置事务 AOP 织入
<!--事务的aop增强--> <aop:config> <aop:pointcut id="myPointcut" expression="execution(* com.itheima.service.impl.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"></aop:advisor> </aop:config>
④测试事务控制转账业务代码
@Override public void transfer(String outMan, String inMan, double money) { accountDao.out(outMan,money); int i = 1/0; accountDao.in(inMan,money); }
2.3 切点方法的事务参数的配置
<!--事务增强配置--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*"/> </tx:attributes> </tx:advice>
其中,tx:method 代表切点方法的事务参数的配置,例如:
<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="-1" read-only="false"/>
-
name:切点方法名称
-
isolation:事务的隔离级别
-
propogation:事务的传播行为
-
timeout:超时时间
-
read-only:是否只读
2.4 知识要点
声明式事务控制的配置要点
-
平台事务管理器配置
-
事务通知的配置
-
事务aop织入的配置
3 基于注解的声明式事务控制
3.1 使用注解配置声明式事务控制
- 编写 AccoutDao
@Repository("accountDao") public class AccountDaoImpl implements AccountDao { @Autowired private JdbcTemplate jdbcTemplate; public void out(String outMan, double money) { jdbcTemplate.update("update account set money=money-? where name=?",money,outMan); } public void in(String inMan, double money) { jdbcTemplate.update("update account set money=money+? where name=?",money,inMan); } }
- 编写 AccoutService
@Service("accountService") @Transactional public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED) public void transfer(String outMan, String inMan, double money) { accountDao.out(outMan,money); int i = 1/0; accountDao.in(inMan,money); } }
- 编写 applicationContext.xml 配置文件
<!—之前省略datsSource、jdbcTemplate、平台事务管理器的配置--> <!--组件扫描--> <context:component-scan base-package="com.itheima"/> <!--事务的注解驱动--> <tx:annotation-driven/>
3.2 注解配置声明式事务控制解析
①使用 @Transactional 在需要进行事务控制的类或是方法上修饰,注解可用的属性同 xml 配置方式,例如隔离级别、传播行为等。
②注解使用在类上,那么该类下的所有方法都使用同一套注解参数配置。
③使用在方法上,不同的方法可以采用不同的事务参数配置。
④Xml配置文件中要开启事务的注解驱动<tx:annotation-driven />
3.3 知识要点
注解声明式事务控制的配置要点
-
平台事务管理器配置(xml方式)
-
事务通知的配置(@Transactional注解配置)
-
事务注解驱动的配置 tx:annotation-driven/
-