声明式事务
一、复习三层架构模型
三层架构模型,分别是:控制层,业务层,持久层
控制层(control):传递参数、调用与跳转
业务层(service):系统的业务逻辑主要在这里完成
持久层(Dao):用于对数据库进行实际操作
为了更好的降低各层间的耦合度,在三层架构程序设计中,采用面向对象编程。 即上层对下层的调用,是通过接口实现的。 而下层对上层的真正服务提供者,是下层接口的实现类。 服务标准(接口)是相同的,服务提供者(实现类)可以更换。 这就实现了层间解耦合。
二、声明式事物的相关概念
①编程式事物:事务功能的相关操作全部通过自己编写代码来实现:
存在的问题:1.代码复用率不高 2.所有细节都要程序员自己手动完成,代码没有得到复用
②声明式事物:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性 能等各个方面的优化。
声明式:通过配置让框架实现功能。
三、如果没有声明式事物,会出现什么情况?
①准备工作
前面的加入依赖,创建jdbc.propertyties文件,配置spring的配置文件先不进行赘述了
②创建组件(这边用尚硅谷的图片进行描述)
控制层,创建service对象 ,传递参数,通过service对象调用方法
service层创建bookDao对象,这里完成业务逻辑,购买一本书,需要三步
①查询书的价格
②更新图书的库存
③更新用户的余额
dao中对数据库进行操作,创建JdbcTemplate对象,编写sql语句进行调试
③如果没有声明式事物会出现什么情况
如果没有声明式事物,编写测试类进行处理:
代码中第一行是配置
@RunWith是一个注解:
@RunWith(SpringJUnit4ClassRunner.class),让测试运行于Spring测试环境
用户购买图书,先查询图书的价格,再更新图书的库存和用户的余额 假设用户id为1的用户,购买id为1的图书 用户余额为50,而图书价格为80 购买图书之后,用户的余额为-30,数据库中余额字段设置了无符号,因此无法将-30插入到余额字段 此时执行sql语句会抛出SQLException
注意:如果字段设置了无符号,当余额小于0的时候会出现问题,这时用户余额为-30,因此会出现sql异常,因此余额不会减30,但是书的余额本数会减一。
四、使用了声明式事物后的结果
①添加事物配置
<!-- 加上注解后,进行扫描,对组件进行扫描--> <context:component-scan base-package="com.atguigu.spring"></context:component-scan> <!--引入配置文件--> <context:property-placeholder location="jdbc.properties"></context:property-placeholder> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!-- 用于操作数据库的--> <bean class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 要通过JDBCTemplate来操作数据库,必须先设置数据源--> <property name="dataSource" ref="dataSource"></property> </bean> <!--配置事物管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 开启事物的注解驱动--> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
配置事务管理器,开启事物驱动,transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就 是这个默认值,则可以省略这个属性
②添加事物注解
首先,使用的注解式@Transactional
注意,一定是在service层添加事物相关的注解的,因为service层表示业务逻辑层,一个方法表示一个完成的功能,因此处理事务一般在service层处理 在BookServiceImpl的buybook()。
③测试结果
测试后的结果,就是抛出异常后,整个事物都会进行回滚
@Transactional标识在方法上,只会影响该方法
@Transactional标识的类上,会影响类中所有的方法
五、事物的隔离级别(这是最重要的一个事物属性,我们先进行处理)
不同的隔离级别对应不同的干扰程度,分别有以下四种,从上到下干扰程度越低,一致性越好,但是相对应的并发性也就越差:
读未提交:READ UNCOMMITTED
允许Transaction01读取Transaction02未提交的修改,读未提交表示:如果一个事务已经开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读此行数据,该隔离级别可以通过“排他写锁”,但是不排斥读线程实现。这样就避免了更新丢失,却可能出现脏读,也就是说事务B读取到了事务A未提交的数据,也就是A线程进行写操作,但是好没有进行对提交操作,如果此时B线程对数据进行读取,那么会读取到A线程未提交的数据。
读已提交:READ COMMITTED
要求Transaction01只能读取Transaction02已提交的修改。
如果是一个读事务(线程),则允许其他事务读写,如果是写事务将会禁止其他事务访问该行数据,该隔离级别避免了脏读,但是可能出现不可重复读。事务A事先读取了数据,事务B紧接着更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变,也就是线程在进行写操作是时候,会加上同步互斥锁,让其他线程无法读取数据,这样就避免了读未提交,也就是我对我要操作的那一行用锁锁起来。
可重复读:REPEATABLE READ
确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它 事务对这个字段进行更新。
可重复读取是指在一个事务内,多次读同一个数据,在这个事务还没结束时,其他事务不能访问该数据(包括了读写),这样就可以在同一个事务内两次读到的数据是一样的,因此称为是可重复读隔离级别,读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务(包括了读写),这样避免了不可重复读和脏读,但是有时可能会出现幻读。
串行化:SERIALIZABLE
确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它 事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。
提供严格的事务隔离,它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行,如果仅仅通过“行级锁”是无法实现序列化的,必须通过其他机制保证新插入的数据不会被执行查询操作的事务访问到。序列化是最高的事务隔离级别,同时代价也是最高的,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻读
使用方式:
六、@Transactional的其他属性情况
①回滚策略
声明式事务默认只针对运行时异常回滚,编译时异常不回滚。
在上面的方法添加上那个注解后,表示发生算数异常时,不发生回滚策略
②超时
说白了就是事物执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常 程序可以执行。
具体实现代码:
表示如果过了三秒过后会对所有事物进行回滚,这个例子不是特别好,因为一开始就会出现线程休眠三秒钟。
③只读
对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这 样数据库就能够针对查询操作来进行优化。如果此时进行了写操作,那么会抛出异常。
④事物的传播行为
当事务方法被另外一个事务方法调用时,必须制定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事物,并在自己的事务中运行。
我们在A类的aMethod()方法中调用了B类的bMethod()方法。这个时候就涉及到业务层方法之间互相调用的事务问题。如果我们的bMethod()发生异常需要回滚,如何配置事务传播行为才能让aMethod也跟着回滚呢,这个时候就需要事务传播行为的知识了。
默认事物传播行为:
PROPAGATION_REQUIRED
1.如果外部方法没有开启事务的话,修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
2.如果外部方法开启事务并且被的话,所有
修饰的内部方法和外部方法均属于同一事务,只要一个方法回滚,整个事务均回滚。
PROPAGATION_REQUIRES_NEW
创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
如果我们的使用Propagation.REQUIRES_NEW事务传播行为修饰,
还是用
修饰的话。如果
发生异常回滚,
不会跟着回滚,因为
开启了独立的事务。但是如果
抛出了未被捕获的异常并且这个异常满足事务回滚规则的话,
同样也会回滚,因为这个异常被
的事务管理机制检测到了。
七、基于XML实现声明式事物