Spring事务一:事务的原子性控制以及事务传播特性(一):

本文探讨Spring中的事务管理,重点讲解事务的原子性及其在Spring中的实现,包括事务的四大特征(ACID)和如何通过@Transational注解控制事务的传播。举例说明了未正确处理事务可能导致的问题,以及如何使用@Transactional注解确保事务的一致性和回滚策略。

Spring事务一:事务的原子性控制以及事务传播特性:

我们学习的AOP,最重要的就是对事务的控制。

实际项目中,对数据库的单次操作,如一次insert,我们在jdbc Template中,使用的是jdbc的事务,会自动的为我们提交事务。但是在实际项目中,一次操作可能会涉及多次数据库的连接,这样是需要我们来保证事务的一致性的,要么同时成功,要么同时失败,使用jdbc实现起来是很麻烦的,使用AOP就可以来完成这些工作。如我单次操作,多次数据库的连接,AOP可以自动的来为我开启事务,关闭事务,提交事务,包括事务的传播特性,隔离级别,都可以使用AOP来做设置。

 

事务我们自己去写,是一件很麻烦的事情,事务的传播,隔离级别,自己去写的话是相当费事的,使用Spring的AOP可以简单很多。

 

1:事务的概述和特征:

1:什么是事务:

一荣俱荣,一损俱损,多个复杂的操作,可以将其看作是一个整体,要么同时成功,要么同时失败。

事务的四个特征(ACID):

这四个特征面试常问,工作中也是很重要的。

 

 

  1. 原子性(Atomic):表示组成一个事务多个数据库的操作不可分割的单元。只有所有的操作成功,才算成功,整个事务提交的时候,其中任何一个操作失败,都会导致所有操作失败,那么事务就会做回滚。

如我一次事务中,有十次对数据库的操作,这十次操作要么同时成功,要么同时失败。一个操作失败,即这十个操作全部失败。

 

  1. 一致性(Consistentcy):事务操作成功后,数据库所处的状态和业务规则保持一致

 

如A转100元给B,就要保证A的账户减少100元,B的账户增加100元。转账前后,A和B账户的余额是不会有变化的。如果A减少了100元,但是B却没有增加100元,这样就是不一致的。

 

  1. 隔离性(islation):多个数据库的操作操作相同的数据并发时,不同的事务有自己的数据空间,事务与事务之间不受干扰。

 

但是不是完全不受干扰,干扰程度由数据库或者操作事务的隔离级别来决定的,隔离级别越高,干扰就越低,并发性就越低,数据的一致性越好。

 

  1. 持久性(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);

    }

   

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值