事务概念
事务:数据库操作最基本单元,要么都成功,如果有一个失败,就是都失败
典型场景:银行转账,A给B转账,A钱变少,B钱变多,如果A转账过程出错,则B钱不会变多,A不会变少
四大特性:ACID
原子性:就是要么都成功,有一个失败,就全部失败,不做操作
一致性:一个状态到另一个状态必须一致的,如A没给B转成,2人前都不动,或者转成A少的数目和B增加数目一致,而不能是A失败不变,B增加或A减少,B不变
隔离性:数据库并发操作,事务间不能相互影响
持久性:修改操作一旦提交,数据库数据就发生对应变化(而不是没反应)
事务环境搭建(代码搭建)
![](https://img.haomeiwen.com/i16668075/0a9f082083b575a1.png)
JavaEE常见3层,web页面层,service业务逻辑层,dao数据库操作层,如上图,我们为了实现事务环境搭建,关心的是后2个层,其中Dao不考虑业务逻辑,只关心和数据库有关的操作,如转账,修改金额,而service层则是具体实现A少钱,B多钱的过程
![](https://img.haomeiwen.com/i16668075/098a8b04536e2d26.png)
我们使用可视化工具给user_db下创建表t_account,设置3个字段id,username,money
![](https://img.haomeiwen.com/i16668075/56433b9cfbc499a4.png)
表里创建2条数据,这里我用的是MySQL workbench,记得修改要点apply应用提交,
![](https://img.haomeiwen.com/i16668075/bef888e899fa7bf4.png)
可以查到数据确实创建了
然后就是创建Service类,UserDao,service注入UserDao,UserDao注入JdbcTemplate对象,JdbcTemplate注入DataSource,这个我们上节课也做过类似的
![](https://img.haomeiwen.com/i16668075/90156c52acfcfacd.png)
之前的xml就是创建数据库连接池和配置jdbcTemplate对象,这个xml文件不用动,我们删掉之前的测试类,service,dao先关
![](https://img.haomeiwen.com/i16668075/c8636f1d010baadd.png)
UserService类,注解创建对象,并自动装配Dao对象
![](https://img.haomeiwen.com/i16668075/47e9d920c01f4c33.png)
![](https://img.haomeiwen.com/i16668075/3f9b6ed9c40c3d92.png)
UserDao和其实现类,注解创建对象,并自动装配jdbcTemplate对象
![](https://img.haomeiwen.com/i16668075/a299561c4128c6dd.png)
我们定义方法,修改money
![](https://img.haomeiwen.com/i16668075/e51699e4122019d6.png)
实现类如上,我们使用update方法,不记得怎么用看上节课,
![](https://img.haomeiwen.com/i16668075/6fe8e159642a47bb.png)
Service类里定义转账方法,底层是调用dao的方法
![](https://img.haomeiwen.com/i16668075/8f9069ea4fa59ad9.png)
如上,实现了转账100,当然这是正常情况下,
![](https://img.haomeiwen.com/i16668075/20505808e3b860a2.png)
如上,比如银行操作时,发生断电了,我们使用/0实现故障,操作前将账户设为1000,1000
![](https://img.haomeiwen.com/i16668075/1ac08c9bc4721d24.png)
结果就变成报错外,数据lucy少了100,而Mary没变,总共丢了100,肯定是不对的
这就需要使用事务来进行处理,先说下大致逻辑
如果代码编程:我们使用trycatch结构捕获异常,try里先开启事务,然后数据库操作,最后结尾没有异常就提交事务,catch捕获到异常,里面进行事务的回滚操作。但是spring5给我们提供了更方便的事务管理相关的封装
spring事务管理介绍
之前我们之前说过JavaEE的三个层,我们事务管理一般在业务逻辑层里
spring进行事务管理操作一般有2种方式:编程式和声明式(常用后者)
我开始说的try catch finally就是编程式事务操作
对于声明式事务管理,有2种实现方式,即熟悉的xml配置和注解配置(当然还是注解最常用),spring使用事务管理,其底层其实就是AOP
spring对事务有专门的接口,针对不同的框架,有专门的实现类
![](https://img.haomeiwen.com/i16668075/7e31a06e6d09e01a.png)
如上图,接口就是PlatformTransactionManager,看结构可以看到子接口和实现类,其中,使用JdbcTemplate和Mybatis都是使用DataSourceTransactionManager,如果是使用Hibernate,则使用HibernateTransactionManager
声明式事务管理(基于注解)
步骤:1 配置文件创建事务管理器实现类对象bean,这个没啥说的,如下图
![](https://img.haomeiwen.com/i16668075/f66762d9b55d89a4.png)
![](https://img.haomeiwen.com/i16668075/b14a8f5dbc423b19.png)
我们查看源码可以看到其有DataSource属性,我们需要给其注入数据源属性,xml里数据源已经制定好了
![](https://img.haomeiwen.com/i16668075/b3c3216ab812ce0b.png)
2.导入aop和tx名称空间,因为事务是依赖aop的,如下图,相信我们已经回来
![](https://img.haomeiwen.com/i16668075/dec33a3f9b7b7f52.png)
3 开启事务注解,此处还是在xml里
![](https://img.haomeiwen.com/i16668075/31f0749642e457b5.png)
使用tx空间的annotation-driven标签,指定事务管理为我们之前创建的对象
![](https://img.haomeiwen.com/i16668075/f1b0c1fd1e9af98d.png)
4给Service添加注解@Transactional(给类添加,则类所有方法都进行事务管理,也可以给方法添加,使指定方法事务管理),如上
![](https://img.haomeiwen.com/i16668075/fb50e5ce04303661.png)
最后运行测试类,我们先把金额改回1000,1000然后运行带/0的代码,可以看到虽然报错,但是账户并没有变化
基于注解配置事务的参数
刚才我们使用了@Transactional注解,其实是可以传入参数的
![](https://img.haomeiwen.com/i16668075/89f435440d15c146.png)
我们给注解加上括号,可以看到其可以传入很多参数,我们重点关辛苦其中红框的6个,分别是事务传播行为,事务隔离,超时,只读,回滚和不回滚。下面我们分别对6个属性逐一解释
propagation事务传播行为
事务传播官方解释:多事务方法进行调用,过程中如何进行事务管理
什么是事务呢,对数据库进行添加修改删除等操作(使事务发生变化,不算查询)就是事务
![](https://img.haomeiwen.com/i16668075/698517d217b2cd06.png)
上面一张图方便我们理解传播行为,我们定义2个方法,add和update,其中add内部调用了update,我们给add方法加了事务,而update没有事务,这是一种情况,还有一种update有事务,调用他的add没事务,另一种,还有2个都有事务,如何管理这些情况的事务就是事务传播行为
![](https://img.haomeiwen.com/i16668075/52e06bee8aa83cea.png)
spring对于事务传播,定义了7种行为,如上图
REQUIRED,图中解释比较官方,我们还是以add和update为例,比如我们add有事务,而update没有事务,则update操作依据与add的事务。如果add事务终止(视频上说没有事务,,觉得这不太可能),则启动一个事务执行(是spring里的默认设置)
REQUIRED_NEW,不管add是否有事务运行,都新建一个事务(之前有的事务会被挂起)
其他几个可能不是特别常用
![](https://img.haomeiwen.com/i16668075/1e38ad6a811fe625.png)
我们给加上propagation属性如上,idea等号右侧输入REQUIRED,自动补全
隔离级别
我们知道事务有隔离性,即多事务操作不能互相影响
多事务有3个读问题:脏读,不可重复读,虚读(也叫幻读)
脏读:一个未提交的事务读取到了另一个未提交事务的数据
![](https://img.haomeiwen.com/i16668075/58a1ecaf06fcb66e.png)
上图来说明脏读,比如2个人都访问1条记录,都开启了事务,如果没有隔离,A事务要给钱-100,B要给钱变成60000,如果B修改60000了,可能A读到60000再-100,但是B没有提交,B回滚了,A就出现问题了,注意2个都是未提交
不可重复读:一个未提交事务访问已提交修改的事务的数据
![](https://img.haomeiwen.com/i16668075/881268eae697a41b.png)
如上图,2个人分别开启事务A,B,A获得金额5000,B也是,但是B想修改把钱变成了900,并提交了事务,而A再进行事务读取时,发现已经变化了
当然脏读是质量问题,而不可重复度是一种现象
虚读:一个未提交事务访问另一个事务已提交添加的数据
![](https://img.haomeiwen.com/i16668075/37db83ceca5163fb.png)
spring配置的隔离级别属性如上,我们可以看到不同隔离级别的几种读现象出现
![](https://img.haomeiwen.com/i16668075/14bb15ff1fb163d3.png)
添加了隔离属性的注解如上
其他事务属性
1超时 timeout,如果事务一定时间内未提交,则进行回滚,默认-1,即不超时,设置指定值单位为秒
2 readOnly,设置是否只读,默认false,设置为true,则只能查询,不能修改添加删除
3 rollBackFor回滚,设置哪些异常进行回滚,等号右侧为异常类名.class
4 noRollBackFor不回滚,设置哪些异常不回滚,赋值同上
基于xml进行声明式事务管理
虽然注解是最常用的,这里仅体现xml实现方式,分3步:1配置事务管理器,2配置通知,3配置切入点和切面。2,3为aop部分,我们之前讲过,我们增强的部分叫做通知,切入点就是把事务加载到哪个方法,而切面就是把事务加载的过程进行体现
![](https://img.haomeiwen.com/i16668075/e0c9787a78a48688.png)
我们把之前的xml拷贝一份叫bean2.xml,并把<tx:annotation-driven transaction-manager="tm"></tx:annotation-driven>开启事务注解删掉,保留bean标签,因为这个标签就是创建事务管理器
![](https://img.haomeiwen.com/i16668075/050b685f78cc2833.png)
接下来配置通知,我们使用tx:advice标签,给其起个id,里面在tx:attributes标签里添加方法,我们可以给其传入方法名,也可以像下一行使用*,表示对所有user2开头的方法增强,我们还可以给标签其他参数,就可以看到我们事务的几个属性啦
![](https://img.haomeiwen.com/i16668075/e0cb28c1b3614432.png)
然后就是创建切入点,还是使用aop:config标签,然后内部使用aop:pointcut,expresssion传入切入点表达式
![](https://img.haomeiwen.com/i16668075/374a0b4d731a7fa1.png)
然后是配置切面,我们学aop是使用aop:aspect,这里使用aop:advisor,pointcut_ref就是传入我们的切入点id,然后advice-ref传入我们的通知id
![](https://img.haomeiwen.com/i16668075/070c9dc38387910d.png)
为了运行测试把service里注解给注释了
![](https://img.haomeiwen.com/i16668075/3f30ffbb5a3b949e.png)
然后修改测试类注意是bean2.xml,本以为你我运行会和视频一样OK的,但是我报错了,这里提示创建bean对象异常,我仔细排查了半天终于找到了结果,要给通知添加事务管理器关联
![](https://img.haomeiwen.com/i16668075/df1508a1a8c7bb6b.png)
修改后正常,只显示/0异常,我猜测可能默认是设置id为如下图,idea提示的,而我起名tm,还真他妈。。
![](https://img.haomeiwen.com/i16668075/da5ede15e8d58588.png)
![](https://img.haomeiwen.com/i16668075/327b30e2ceb56ca7.png)
运行结果正常
使用完全注解进行事务操作
我们之前使用注解还是用了xml文件,这次我们只通过注解实现事务操作
![](https://img.haomeiwen.com/i16668075/961b93542907eed8.png)
我们之前的xml文件是这样的
首先完全注解开发,需要给创建配置类替代xml文件
![](https://img.haomeiwen.com/i16668075/37ab5587c8556b2b.png)
如上图,我们创建config包,创建配置类TxConfig,然后使用几个注解,这里算是只是回顾了@Configuration声明此类为配置类,@ComponentScan开启组件扫描,我们在com.demo.spring5下面扫描即可,@EnableTransactionManagement 开启事务管理。接下来就是给配置类里注入属性了,像数据库连接池等等
![](https://img.haomeiwen.com/i16668075/8d74073e78634774.png)
我们xml里是使用bean标签,我们这里使用@Bean注解,然后对象设置返回数据源,类型参照xml创建对象,里面我们new这个类即可,其中几个属性通过对象的set方法,参照xml把属性设置过来,最后返回数据源,如上,接下来,参照之前的文件我们需要创建JdbcTemplate对象,还是如法炮制
![](https://img.haomeiwen.com/i16668075/b0b0e04ba3c6032b.png)
如上,其中模板对象需要注入数据源,我们可以通过上个方法的返回值创建,但是没必要,因为默认会调用方法,但是因为单例,IOC容器只创建一次,我们直接使传入数据源参数,让spring按类型自动装配即可
![](https://img.haomeiwen.com/i16668075/d9e2e0a857826725.png)
创建事务管理器,如上,没什么说的,至此,我们的配置类就把xml文件完全替代了
![](https://img.haomeiwen.com/i16668075/9a3dc6f092a8e645.png)
我们把Service里的注释取消,恢复@Transactional注解
![](https://img.haomeiwen.com/i16668075/2d739373485c1609.png)
测试类里变化的就是需要使用注解的上下文,参数传入配置类的class
![](https://img.haomeiwen.com/i16668075/b31bb24b3e90c55e.png)
就在我兴致冲冲的去运行,又TM报错啦,NND,提示是字符编码问题,setUrl里传入的字符串有问题,后来将&;改成&即可,因为xml里是特殊语法
![](https://img.haomeiwen.com/i16668075/0ff07a2cfc02281a.png)
然后还没完,改完了提示事务隔离界别不支持None,即设置REPEATABLE_READ不行,,
![](https://img.haomeiwen.com/i16668075/b3906d1887f7922b.png)
最后改成default,只有/0错误,,终于完事
![](https://img.haomeiwen.com/i16668075/683a47bd8f4a5a3c.png)
验证如下,实际使用中,完全注解是王道,xml不用最好
网友评论