Spring事务一:事务的原子性控制以及事务传播特性:
我们学习的AOP,最重要的就是对事务的控制。
实际项目中,对数据库的单次操作,如一次insert,我们在jdbc Template中,使用的是jdbc的事务,会自动的为我们提交事务。但是在实际项目中,一次操作可能会涉及多次数据库的连接,这样是需要我们来保证事务的一致性的,要么同时成功,要么同时失败,使用jdbc实现起来是很麻烦的,使用AOP就可以来完成这些工作。如我单次操作,多次数据库的连接,AOP可以自动的来为我开启事务,关闭事务,提交事务,包括事务的传播特性,隔离级别,都可以使用AOP来做设置。
事务我们自己去写,是一件很麻烦的事情,事务的传播,隔离级别,自己去写的话是相当费事的,使用Spring的AOP可以简单很多。
1:事务的概述和特征:
1:什么是事务:
一荣俱荣,一损俱损,多个复杂的操作,可以将其看作是一个整体,要么同时成功,要么同时失败。
事务的四个特征(ACID):
这四个特征面试常问,工作中也是很重要的。
- 原子性(Atomic):表示组成一个事务的多个数据库的操作是不可分割的单元。只有所有的操作成功,才算成功,整个事务提交的时候,其中任何一个操作失败,都会导致所有操作失败,那么事务就会做回滚。
如我一次事务中,有十次对数据库的操作,这十次操作要么同时成功,要么同时失败。一个操作失败,即这十个操作全部失败。
- 一致性(Consistentcy):事务操作成功后,数据库所处的状态和业务规则保持一致。
如A转100元给B,就要保证A的账户减少100元,B的账户增加100元。转账前后,A和B账户的余额是不会有变化的。如果A减少了100元,但是B却没有增加100元,这样就是不一致的。
- 隔离性(islation):多个数据库的操作,操作相同的数据并发时,不同的事务有自己的数据空间,事务与事务之间不受干扰。
但是不是完全不受干扰,干扰程度由数据库或者操作事务的隔离级别来决定的,隔离级别越高,干扰就越低,并发性就越低,数据的一致性越好。
- 持久性(Druability):一旦事务提交成功,数据就持久化到数据库之中了,是不可以在回滚了。重启机器,重启服务器,数据都还是在的。
原子性:可以使用Spring的事务传播来控制,一致性和隔离性:使用数据库的隔离级别来控制
2:事务原子性剖析:
Spring中,原子性的控制主要体现在事务传播这一块(Spring对事物传播特性的控制)。
现在我来建立两张表:订单表和订单明细表,订单明细表中添加订单表的主键,但是不使用约束:
1:使用注解来处理事务传播特性:
说明:我们要保证事务的原子性,通过@Transational注解。使用这个注解之前,我必须要在Spring的配置文件中,开启事务注解的驱动。
第一步:配置事务管理器,开启注解的事务的启动
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
<!-- 从属性文件中读取数据源
那么我下面对数据源的配置,value中使用值的形式,就和el表达式得语法差不多
-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置dbcp的数据源
destroy-method:一旦数据源调用了close方法,会做资源的销毁,连接的释放
-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- 配置Mysql的驱动 com.mysql.jdbc.Driver-->
<property name="driverClassName" value="${driverClassName}"></property>
<!-- 配置url jdbc:mysql://localhost:3306/[database]-->
<property name="url" value="${url}"></property>
<!-- 配置username -->
<property name="username" value="${uname}"></property>
<!-- 配置password -->
<property name="password" value="${pword}"></property>
<!-- 数据源加载的时候,自动的为我创建多少个连接,之后我使用的时候,就不用临时创建了,直接使用就可以了 -->
<property name="initialSize" value="${initialSize}"></property>
<!-- 数据库连接池中最大创建的连接数的配置,这里是最大为我创建5个连接,我如果一次使用了5个连接,连接池就不会再来为我创建了,
就只能通过jdbc来创建
-->
<property name="maxActive" value="${maxActive}"></property>
<!-- 最大空闲连接数 ,如果有多余的空闲连接,这个连接是会被释放的,也就是销毁-->
<property name="maxIdle" value="${maxIdle}"></property>
<!-- 最小空闲连接数:我连接闲置的时候,也不能全部都释放,一定要有闲置的连接,以便于有请求过来的时候,可以使用 -->
<property name="minIdle" value="${minIdle}"></property>
<!-- 数据源配置完毕后,就可以来访问数据库了,
这里接着配置的话,可以使用配置文件的方式,也可以使用注解的方式,使用注解方式的话,就要配置注解扫描器,
使用配置文件方式的话,就将Bean全部配置在配置文件中,进行管理,现在使用配置文件的方式。
-->
</bean>
<!-- 为orderDao的实现类来创建Bean -->
<bean id="orderDao" class="cn.aa.spring.dao.impl.orderDaoImpl">
<!-- 为Bean注入数据源,由于我的数据源,是在Bean中定义了属性的,所以,我可以通过下面的方式来做 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 为detailDao的实现类来创建Bean -->
<bean id="detailDao" class="cn.aa.spring.dao.impl.DetailDaoImpl">
<!-- 为Bean注入数据源,由于我的数据源,是在Bean中定义了属性的,所以,我可以通过下面的方式来做 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 为UserServiceImpl来创建Bean -->
<bean id="orderService" class="cn.aa.spring.service.impl.OrderServiceImpl">
<property name="orderDao" ref="orderDao"></property>
<property name="detailDao" ref="detailDao"></property>
</bean>
<!-- 创建事务管理器,在Bean中创建
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
设置事务管理器,Spring是为我们提供了事务管理器的。这个事务管理器,就是用来管理事务的,可以让我的事务保持事务的四个特征,也就是ACID。-->
<bean id="lbj" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源,为什么要注入数据源:DataSourceTransactionManager是依赖于数据库的,它有很多的特性,都是依赖于数据库的,像数据库的隔离级别等等。-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 事务管理器配置好之后,首先使用注解的形式来对事务进行管理:开启注解的事务驱动,transaction-manager="transactionManager":关联事务管理器,事务管理器是我们的核心。使用annotation-driven只是使用这种注解,标注在目标的类,或者方法上,真正管理事务的还是事务管理器
-->
<tx:annotation-driven transaction-manager="lbj"/>
第三步:通过@Transational注解来标志要使用事务的类或接口或方法。
多数情况是标注在类上面,或者方法上面,其实我们也是建议标注在类或者方法上面。如果我需要控制的力度很细的话,就标注在方法上面,如果一个类都采用同一种方式,来做事务的传播,或者隔离级别,就标注在类上面。
接口上面为什么一般不标注:
标注在接口上,接口的使用一般是通过实现类的,注解是不会被实现类继承的。
标注在方法上面,可以让我们控制的更为细一些,@Transational注解 可以来设置我们的事务的传播。
在实际项目中,我们通常将事务开启在service层。
现在假设我没有使用注解的情况
2:原子性刨析:
以上的话,显然是有问题的,没有保证事务的一致性,这种情况显然是不允许的。在实际项目中,一定不能发生这种情况,这是非常严重的错误。现在我来进行解决:
实例代码:使用Transactional标注要使用事务的方法:
public class OrderServiceImpl implements OrderService{
private OrderDao orderDao;
private DetailDao detailDao;
public void setOrderDao(OrderDao orderDao) {
this.orderDao = orderDao;
}
public void setDetailDao(DetailDao detailDao) {
this.detailDao = detailDao;
}
/*
*
* 使用@Transactional注解,来保证事务的一致性
* rollbackFor=Exception.class:发生这个非运行期异常的时候,做回滚。
*/
@Transactional(rollbackFor=Exception.class)
@Override
public void saveOrderAntDetail(Order order, Detail detail){
/*我的detailDao其实引用了orderDao中的主键,我们其实应该将orderDao保存之后,将其主键返回回来,然后再设置给detailDao,这样才可以保证完整性
*/
//首先插入一条数据到订单表,然后获得我插入数据的id
Integer orderId = orderDao.saveOrder(order);
/*
* 现在我没有使用注解标注,假如发生了异常
*/
//将订单表的id设置给明细表
detail.setOrderId(orderId);
//插入一条数据到明细表
detailDao.saveDetail(detail);
//int i = 1/0;
/*
* 现在我在这里,要保证事物的原子性,这两个操作要么同时成功,要么同时失败。假如我订单保存成功了,但是明细没有保存成功,那么我的订单就是一个脏数据,这个订单就是一个废的。就没有意义。所以这两个操作要么同时成功,要么同时失败。
*/
//throw new Exception();
}
注意:在Service层开启事务的时候,默认情况下,发生运行是异常会做回滚,非运行时异常不会做回滚。
对于非运行期异常,默认情况下是不做回滚,但是我们可以对其进行设置,让其做回滚。
但是我们可以通过@Transaional中的属性进行设置,对那个异常做回滚,对那个异常不做回滚。但是在实际工作中,一般不会设置,实际工作中,一般也是对运行期异常来做回滚。
非运行期异常是需要我们来处理的。
关于事务默认的传播特性:
public class OrderServiceImpl implements OrderService{
private OrderDao orderDao;
private DetailDao detailDao;
public void setOrderDao(OrderDao orderDao) { this.orderDao = orderDao; }
public void setDetailDao(DetailDao detailDao) { this.detailDao = detailDao; } /* * 我通过Transational保证了事务的原子性 ,原子性的保证,是由事务的传播特性来控制的。Transational:的默认的事务传播特性是required。在实际项目中,百分之八十都是required。当下面这个方法执行的时候,Spring会开启事务,开启的是一个事务。下面两个操作,使用的是同一个事务。我们默认情况下,使用的是required,就是不管我这里有多少个操作,使用的都是同一个事务,也就是说,这个事务会按着操作的执行顺序,往下面传递。这样就使得我所有的操作,在一个事物之中,一荣俱荣,一损俱损。所有的操作要么同时成功,要么同时失败。@Transactional(propagation=):来进行传播特性的设置, */
@Transactional @Override public void saveOrderAntDetail(Order order, Detail detail){ Integer orderId = orderDao.saveOrder(order); detail.setOrderId(orderId); detailDao.saveDetail(detail); }
|