【spring初学】spring的事务

1、Spring 的事务管理

事务原本是数据库中的概念,在 Dao 层。但一般情况下,需要将事务提升到业务层,即 Service 层。这样做是为了能够使用事务的特性来管理具体的业务。

在 Spring 中通常可以通过以下两种方式来实现对事务的管理:

(1)使用 Spring 的事务注解管理事务;

(2)使用 AspectJ 的 AOP 配置管理事务。

 

2、Spring 事务管理 API

Spring 的事务管理,主要用到两个事务相关的接口。 官方压缩文档下载地址:https://repo.spring.io/release/org/springframework/spring/

(1)事务管理器接口(重点)

事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。

事务管理器接口的实现类

 A、 常用的两个实现类

PlatformTransactionManager 接口有两个常用的实现类:

DataSourceTransactionManager:使用 JDBC 或 MyBatis 进行数据库操作时使用。

HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。

B、 Spring 的回滚方式(理解)

Spring 事务的默认回滚方式是:发生运行时异常和 error 时回滚,发生受查(编译)异常时提交。不过,对于受查异常,程序员也可以手工设置其回滚方式。

C、 回顾错误与异常(理解)

      Throwable 类是 Java 语言中所有错误或异常的超类。只有当对象是此类(或其子类之一) 的实例时,才能通过Java 虚拟机或者 Java 的 throw 语句抛出。

      Error 是程序在运行过程中出现的无法处理的错误,比如 OutOfMemoryError、 ThreadDeath、NoSuchMethodError 等。当这些错误发生时,程序是无法处理(捕获或抛出) 的,JVM 一般会终止线程。

      程序在编译和运行时出现的另一类错误称之为异常,它是JVM通知程序员的一种方式。 通过这种方式,让程序员知道已经或可能出现错误,要求程序员对其进行处理。

      异常分为运行时异常受查异常

      运行时异常,是 RuntimeException 类或其子类,即只有在运行时才出现的异常。如,NullPointerException、ArrayIndexOutOfBoundsException、IllegalArgumentException 等均属于运行时异常。这些异常由 JVM 抛出,在编译时不要求必须处理(捕获或抛出)。但,只要代码编写足够仔细,程序足够健壮,运行时异常是可以避免的。

      受查异常,也叫编译时异常,即在代码编写时要求必须捕获或抛出的异常,若不处理, 则无法通过编译。如SQLException,ClassNotFoundException,IOException 等都属于受查异常。

      RuntimeException 及其子类以外的异常,均属于受查异常。当然,用户自定义的 Exception 的子类,即用户自定义的异常也属受查异常。程序员在定义异常时,只要未明确声明定义的为 RuntimeException 的子类,那么定义的就是受查异常。

(2) 事务定义接口

事务定义接口TransactionDefinition中定义了事务描述相关的三类常量:事务隔离级别、 事务传播行为、事务默认超时时限,及对它们的操作。 (控制事务是什么样的,怎么操作的?)

A、 定义了五个事务隔离级别常量(掌握)

这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX:

 ➢ DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle 默 认为 READ_COMMITTED。

READ_UNCOMMITTED:读未提交。未解决任何并发问题。

READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。

脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。

REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读 。

SERIALIZABLE:串行化。不存在并发问题。(安全、性能低)

B、 定义了七个事务传播行为常量(掌握)

所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如,A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的维护情况,就称为事务传播行为。事务传播行为是加在方法上的。

事务在方法之间可以传递,方法怎么来使用事务。通过传播行为指定方法怎么使用事务。

事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX:

PROPAGATION_REQUIRED

PROPAGATION_REQUIRES_NEW

PROPAGATION_SUPPORTS

PROPAGATION_MANDATORY

PROPAGATION_NESTED

PROPAGATION_NEVER

PROPAGATION_NOT_SUPPORTED

 

a、 PROPAGATION_REQUIRED:

      指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事 务,则创建一个新事务。这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。

      如该传播行为加在 doOther()方法上。若 doSome()方法在调用 doOther()方法时就是在事 务内运行的,则doOther()方法的执行也加入到该事务内执行。若 doSome()方法在调用 doOther()方法时没有在事务内执行,则doOther()方法会创建一个事务,并在其中执行。

PROPAGATION_REQUIRED的传播行为加在doOther()方法上,doSome调用doOther:

1、doSome中有事务,那么doOther就加到doSome方法之中执行。

2、doSome没有事务。那么doOther就自己创建创建一个事务,并在其中执行。  

 

b、 PROPAGATION_SUPPORTS

指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。(查询操作)

 c、 PROPAGATION_REQUIRES_NEW

总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。

事务是加在业务方法上的

 

C、 定义了默认事务超时时限

超时:事务的最长执行时间,也就是一个方法最长的执行时间。当时间到了,spring会回滚方法的执行。秒为单 位。默认是-1,也就是数据库自己的超时。

常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,及不支持事务超时时限设置 的 none 值。

注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该值 一般就使用默认值即可。

3 、程序举例环境搭建

举例:购买商品 trans_sale 项目

本例要实现购买商品,模拟用户下订单,向订单表添加销售记录,从商品表减少库存。

实现步骤:

Step0:创建数据库表

创建两个数据库表 sale 、goods。

sale 销售表       id:销售表的id; gid:购买商品的id;     nums:购买的数量

goods表       id:商品的id    name:商品的名称      amount:商品的库存       price:商品的单价

Step1: maven 依赖 pom.xml

<dependency>    
    <groupId>junit</groupId>    
    <artifactId>junit</artifactId>    
    <version>4.12</version>    
    <scope>test</scope>  
</dependency> 

<!--spring的依赖-->
<dependency>    
    <groupId>org.springframework</groupId>    
    <artifactId>spring-context</artifactId>    
    <version>4.3.16.RELEASE</version>   
</dependency> 

<!--事务的依赖-->
<dependency>    
    <groupId>org.springframework</groupId>    
    <artifactId>spring-tx</artifactId>    
    <version>4.3.16.RELEASE</version>  
</dependency> 

<!--JDBC的依赖-->
<dependency>    	
    <groupId>org.springframework</groupId>    
    <artifactId>spring-jdbc</artifactId>    
    <version>4.3.16.RELEASE</version>   
</dependency> 

<!--Mybatis的依赖-->
<dependency>    
    <groupId>org.mybatis</groupId>    
    <artifactId>mybatis</artifactId>    
    <version>3.4.5</version>   
</dependency> 

<!--Mybatis整合spring的依赖-->
<dependency>    
    <groupId>org.mybatis</groupId>    
    <artifactId>mybatis-spring</artifactId>    
    <version>1.3.1</version>   
</dependency>

<!--mysql依赖-->
<dependency>    
    <groupId>mysql</groupId>    
    <artifactId>mysql-connector-java</artifactId>    
    <version>5.1.9</version>   
</dependency>

<!--连接池的依赖-->
<dependency>    
    <groupId>com.alibaba</groupId>    
    <artifactId>druid</artifactId> 
    <version>1.1.12</version>   
</dependency> 


<!--插件--> 
<plugins>        
  <plugin>             
    <artifactId>maven-compiler-plugin</artifactId>             
    <version>3.1</version>             
    <configuration>                 
      <source>1.8</source>                 
      <target>1.8</target>             
    </configuration>        
  </plugin> 
</plugins> 

Step2:创建实体类

创建实体类 Sale 与 Goods

Step3:定义 dao 接口

定义两个 dao 的接口 SaleDao , GoodsDao

Step4:定义 dao 接口对应的 sql 映射文件

Step5 mybatis.xml配置文件

Step6 service

package org.example.service.impl;

import org.example.beans.Goods;
import org.example.beans.Sale;
import org.example.dao.GoodsDao;
import org.example.dao.SaleDao;
import org.example.excep.NotEnoughException;
import org.example.service.BuyGoodsService;

/**
 * #-*- coding = utf-8 -*-
 * #@Time: 2020/11/24 20:47
 * #@Author: Winter
 * #@File: BuyGoodsServiceImpl.py
 * #@Software: IntelliJ IDEA
 **/
public class BuyGoodsServiceImpl implements BuyGoodsService {

    //定义Dao的依赖对象
    private GoodsDao goodsDao;        /*商品Dao*/
    private SaleDao saleDao;          /*销售Dao*/

    /*set方法,【设置注入】*/
    public void setGoodsDao(GoodsDao goodsDao) {
        this.goodsDao = goodsDao;
    }

    public void setSaleDao(SaleDao saleDao) {
        this.saleDao = saleDao;
    }

    /**
     *
     * @param goodsId:购买商品的id
     * @param nums:购买商品的数量
     */
    @Override
    public void buyGoods(Integer goodsId, Integer nums) throws NullPointerException,NotEnoughException{

        //生成订单
        Sale sale = new Sale();
        sale.setGid(goodsId);
        sale.setNums(nums);

        saleDao.insertSale(sale);       /*将购买的商品加入到sale中*/

        //修改库存
        Goods goods = goodsDao.selectGoodsById(goodsId);   /*先查找*/
        if(goods==null){
            //没有商品
            throw new NullPointerException(goodsId+"没有该商品......");
        }

        if(goods.getAmount()<nums){
            //库存不足
            throw new NotEnoughException(goodsId+"库存不足......");
        }

        //操作库存
        goods.setAmount(nums);                 /*这个是购买的商品*/
        goodsDao.updateGoods(goods);
    }
}

Step7:定义异常类

Step8:applicationContext.xml

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">


<!--声明Mybatis对象-->
 <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--11、声明数据源DataSource   init和close是DruidDataSource类或者其父类中的方法 。作用:访问数据库-->
    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!--读取属性配置文件的key值,使用${key}-->
        <!--2、指定数据库的url-->
        <property name="url" value="${jdbc.url}"/> <!--setUrl()-->

        <!--3、数据库的用户名-->
        <property name="username" value="${jdbc.username}"/>  <!--setUsername()-->

        <!--4、数据库的密码-->
        <property name="password" value="${jdbc.password}"/>   <!--setPassword()-->
    </bean>
   

    <!--22、声明SqlSessionFactoryBean,创建SqlSessionFactory对象  目的:获取SqlSession    -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--1、数据源-->
        <property name="dataSource" ref="myDataSource"/>

        <!--2、指定Mybatis的主配置文件-->
        <property name="configLocation" value="classpath:mybatis.xml"/>
    </bean>



    <!--33、声明Mybatis的扫描器,创建Dao接口的实现类对象,不需要id。相当于之前的student,上面相当于School-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--1、指定SqlSessionFactory对象,能够获取SqlSession-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>

        <!--2、指定Dao接口的包名,框架会把这个包中的所有接口一次创建Dao对象【这里要修改】-->
        <property name="basePackage" value="org.example.dao"/>
    </bean>

       
<!--声明业务层对象-->
 <!--44、声明Service-->
    <bean id="buyService" class="org.example.service.impl.BuyGoodsServiceImpl">
        <!--name是属性名,ref是对象名-->
        <property name="goodsDao" ref="goodsDao"/>   <!--setGoodsDao()-->
        <property name="saleDao" ref="saleDao"/>     <!--setSaleDao()-->
    </bean>


</bean>

Step9:测试代码

package org.example;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App 
{
    public static void main( String[] args )
    {
        System.out.println( "Hello World!" );
        String config = "applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);     //构造方法
        String names[] = ctx.getBeanDefinitionNames();
        for (String name:names) {
            System.out.println(name);
        }
    }
}

购买商品

public class App 
{
    public static void main( String[] args )
    {
        System.out.println( "Hello World!" );
        String config = "applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);     //构造方法

        //applicationContext.xml中的业务层对象,得到BuyGoodsService对象
        BuyGoodsService service = (BuyGoodsService) ctx.getBean("buyService");

        //买商品
        service.buyGoods(1001,2);
        System.out.println("购买商品成功......");
    }
}

库存不足【原库存没有减少】

service.buyGoods(1001,200);
System.out.println("购买商品成功......");

没有该商品

service.buyGoods(1005,20);
System.out.println("购买商品成功......");

4、使用 Spring 的事务注解管理事务(掌握)

通过@Transactional 注解方式,可将事务织入到相应 public 方法中,实现事务管理。【业务层的公共方法上

@Transactional 的所有可选属性如下所示:

propagation:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值Propagation.REQUIRED。

isolation:用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为 Isolation.DEFAULT。

➢ readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值 为 false。【当查询的时候,可以把这个参数设置为true,告诉数据库这个操作是不需要事务的,提高效率】

timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为 -1,即没有时限。

rollbackFor:指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有 一个异常类时,可以不使用数组。 【当发生这个异常时进行回滚】

➢ rollbackForClassName:指定需要回滚的异常类类名。类型为 String[],默认值为空数组。 当然,若只有一个异常类时,可以不使用数组。

➢ noRollbackFor:指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若 只有一个异常类时,可以不使用数组。

➢ noRollbackForClassName:指定不需要回滚的异常类类名。类型为 String[],默认值为空 数组。当然,若只有一个异常类时,可以不使用数组。

 一般主要是propagationisolationrollbackFor

        需要注意的是,@Transactional 若用在方法上,只能用于 public 方法上。对于其他非 public 方法,如果加上了注解@Transactional,虽然 Spring 不会报错,但不会将指定事务织入到该 方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction 注解。

使用注解的步骤:
1、修改spring的配置文件
    (1)声明事务管理器,管理器完成事务commit、rollback
    (2)声明事务的注解驱动,告诉框架,使用注解完成事务的设置

2、在Service的实现类的public方法之上加入@Tranactional

在applicationContext.xml

(1)声明事务管理器  

<!--声明事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--数据源-->
        <property name="dataSource" ref="myDataSource"/>
    </bean>

(2)开启注解驱动

 <tx:annotation-driven transaction-manager="transactionManager"/>

(3)在Service的实现类的public方法之上加入@Tranactional

@Transactional(propagation = Propagation.REQUIRED,     
    isolation = Isolation.DEFAULT,
    timeout = 20,
    rollbackFor = {NullPointerException.class,NotEnoughException.class})
    @Override
    public void buyGoods(Integer goodsId, Integer nums) throws NullPointerException,NotEnoughException{
      ...
      ...
      ...
    
    }
    
    /**
    传播行为
    propagation = Propagation.REQUIRED
    指定的方法必须在事务内执行。
    若当前存在事务,就加入到当前事务中;若当前没有事务,则创建一个新事务。
    这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。 
    
    隔壁级别
    isolation = Isolation.DEFAULT
    :采用 DB 默认的事务隔离级别。
    MySql 的默认为 REPEATABLE_READ; Oracle 默认为 READ_COMMITTED。 
    
    超时时间
    timeout = 20:
    超时时间是20s
    
    
   rollbackFor = {NullPointerException.class,NotEnoughException.class})
   指定需要回滚的异常类。类型为 Class[],默认值为空数组。
   当然,若只有一个异常类时,可以不使用数组。 
   也就是遇到这两个异常时发生回滚
    
    */

测试

数据库里库存不足没有该商品都不影响数据库,再测试正常购买

只是之前库存不足和没有该商品的id跳过去了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值