Spring事务

目录
  • 一、事务简介
  • 二、程序举例环境搭建
    • 1. 创建数据表
    • 2. maven依赖pom.xml
    • 3. 创建实体类
    • 4. 定义dao接口
    • 5. 定义dao接口对应的sql映射文件
    • 6. 定义异常类
    • 7. 定义service接口及实现类
    • 8. mybatis.xml
    • 9. Spring配置文件
    • 10. 测试
  • 三、使用 Spring 的事务注解管理事务
    • 1. 声明事务管理器
    • 2. 开启注解驱动
    • 3. 完整Spring配置文件
    • 4. 业务层 public 方法加入事务属性
    • 5. 测试
  • 四、使用 AspectJ 的 AOP 配置管理事务
    • 1. maven依赖pom.xml
    • 2. 在容器中添加事务管理器
    • 3. 配置事务通知
    • 4. 配置增强器
    • 5. 完整Spring配置文件
    • 6. 测试类
  • 五、总结
    • 1. Spring 的事务注解管理事务
    • 2. AspectJ 的 AOP 配置管理事务

一、事务简介

具体的看数据库中关于事务的知识点,这里做一个大概

1. 什么是事务?

讲mysql的时候,提出了事务。 事务是指一组sql语句的集合, 集合中有多条sql语句可能是insert , update ,select ,delete, 我们希望这些多个sql语句都能成功,或者都失败, 这些sql语句的执行是一致的,作为一个整体执行。

2. 在什么时候想到使用事务

当我的操作,涉及得到多个表,或者是多个sql语句的insert,update,delete。需要保证这些语句都是成功才能完成我的功能,或者都失败,保证操作是符合要求的

在java代码中写程序,控制事务,此时事务应该放在那里呢?

service类的业务方法上,因为业务方法会调用多个dao方法,执行多个sql语句

3. 通常使用JDBC访问数据库, 还是mybatis访问数据库怎么处理事务
jdbc访问数据库,处理事务 Connection conn ; conn.commit(); conn.rollback();
mybatis访问数据库,处理事务, SqlSession.commit(); SqlSession.rollback();
hibernate访问数据库,处理事务, Session.commit(); Session.rollback();

4. 问题中事务的处理方式,有什么不足

  1. 不同的数据库访问技术,处理事务的对象,方法不同,需要了解不同数据库访问技术使用事务的原理
  2. 掌握多种数据库中事务的处理逻辑。什么时候提交事务,什么时候回顾事务
  3. 处理事务的多种方法。

总结: 就是多种数据库的访问技术,有不同的事务处理的机制,对象,方法

5. 怎么解决不足
spring提供一种处理事务的统一模型, 能使用统一步骤,方式完成多种不同数据库访问技术的事务处理。

使用spring的事务处理机制,可以完成mybatis访问数据库的事务处理
使用spring的事务处理机制,可以完成hibernate访问数据库的事务处理。

6. 处理事务,需要怎么做,做什么
spring处理事务的模型,使用的步骤都是固定的。把事务使用的信息提供给spring就可以了

  1. 事务内部提交,回滚事务,使用的事务管理器对象,代替你完成commit,rollback事务管理器是一个接口和他的众多实现类。

    接口:PlatformTransactionManager ,定义了事务重要方法 commit ,rollback

    实现类:spring把每一种数据库访问技术对应的事务处理类都创建好了。
    mybatis访问数据库---spring创建好的是DataSourceTransactionManager
    hibernate访问数据库----spring创建的是HibernateTransactionManager

    怎么使用:你需要告诉spring 你用是那种数据库的访问技术,怎么告诉spring呢?
    声明数据库访问技术对于的事务管理器实现类, 在spring的配置文件中使用 声明就可以了
    例如,你要使用mybatis访问数据库,你应该在xml配置文件中

  2. 你的业务方法需要什么样的事务,说明需要事务的类型。

    ​ 1. 说明方法需要的事务:
    事务的隔离级别:有4个值。
    DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle默认为 READ_COMMITTED。
    ➢ READ_UNCOMMITTED:读未提交。未解决任何并发问题。
    ➢ READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
    ➢ REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
    ➢ SERIALIZABLE:串行化。不存在并发问题。

    1. 事务的超时时间: 表示一个方法最长的执行时间,如果方法执行时超过了时间,事务就回滚
      • 单位是秒, 整数值, 默认是 -1.
    2. 事务的传播行为 : 控制业务方法是不是有事务的, 是什么样的事务的, 7个传播行为,表示你的业务方法调用时,事务在方法之间是如果使用的,记得前三个就行
      • PROPAGATION_REQUIRED
      • PROPAGATION_REQUIRES_NEW
      • PROPAGATION_SUPPORTS
      • PROPAGATION_MANDATORY
      • PROPAGATION_NESTED
      • PROPAGATION_NEVER
      • PROPAGATION_NOT_SUPPORTED
    3. 事务提交事务,回滚事务的时机
1. 当你的业务方法,执行成功,没有异常抛出,当方法执行完毕,spring在方法执行后提交事务。事务管理器commit
2. 当你的业务方法抛出运行时异常或ERROR, spring执行回滚,调用事务管理器的rollback
	 运行时异常的定义: RuntimeException  和他的子类都是运行时异常, 例如NullPointException , NumberFormatException
3. 当你的业务方法抛出非运行时异常, 主要是受查异常时,提交事务
	受查异常:在你写代码中,必须处理的异常。例如IOException, SQLException

7. 总结spring的事务

  1. 管理事务的是 事务管理和他的实现类
  2. spring的事务是一个统一模型
  • 指定要使用的事务管理器实现类,使用

  • 指定哪些类,哪些方法需要加入事务的功能

  • 指定方法需要的隔离级别,传播行为,超时

    你需要告诉spring,你的项目中类信息,方法的名称,方法的事务传播行为

二、程序举例环境搭建

购买商品,用户下单,向销售表中添加销售记录,从商品表中减少数据

1. 创建数据表

创建数据表sale(销售表)和goods(商品表)

sale:id自增,方便后面测试

Spring事务_第1张图片

goods:id不自增,

Spring事务_第2张图片

注意,平时开发price用decimal类型

向goods表示适当的添加两天数据,用于测试

2. maven依赖pom.xml




  4.0.0

  com.md
  07-spring-trans
  1.0-SNAPSHOT

  
    UTF-8
    1.8
    1.8
  

  

    
    
      junit
      junit
      4.11
      test
    

    
    
      org.springframework
      spring-context
      5.2.5.RELEASE
    

    
    
      org.springframework
      spring-tx
      5.2.5.RELEASE
    
    
      org.springframework
      spring-jdbc
      5.2.5.RELEASE
    


    
    
      org.mybatis
      mybatis
      3.5.1
    

    
    
      org.mybatis
      mybatis-spring
      1.3.1
    

    
    
      mysql
      mysql-connector-java
      5.1.9
    


    
    
      com.alibaba
      druid
      1.1.12
    




  

  

    
    
      
        src/main/java
        
          **/*.properties
          **/*.xml
        
        false
      
    



  



3. 创建实体类

package com.md.domain;

/**
 * @author MD
 * @create 2020-08-11 9:20
 */
public class Sale {
    private Integer id;
    private Integer gid;
    private Integer nums;


    public Sale() {
    }

    public Sale(Integer id, Integer gid, Integer nums) {
        this.id = id;
        this.gid = gid;
        this.nums = nums;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public void setGid(Integer gid) {
        this.gid = gid;
    }

    public void setNums(Integer nums) {
        this.nums = nums;
    }


    public Integer getId() {
        return id;
    }

    public Integer getGid() {
        return gid;
    }

    public Integer getNums() {
        return nums;
    }

    @Override
    public String toString() {
        return "SaleDao{" +
                "id=" + id +
                ", gid=" + gid +
                ", nums=" + nums +
                '}';
    }
}

//-----------------
package com.md.domain;

/**
 * @author MD
 * @create 2020-08-11 9:21
 */
public class Goods {

    private Integer id;

    private String name;

    private Integer amount;

//实际开发中不用float
    private Float price;


    public Goods() {
    }

    public Goods(Integer id, String name, Integer amount, Float price) {
        this.id = id;
        this.name = name;
        this.amount = amount;
        this.price = price;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAmount() {
        return amount;
    }

    public void setAmount(Integer amount) {
        this.amount = amount;
    }

    public Float getPrice() {
        return price;
    }

    public void setPrice(Float price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Goods{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", amount=" + amount +
                ", price=" + price +
                '}';
    }
}
//--------------------------------

4. 定义dao接口

package com.md.dao;

import com.md.domain.Sale;

/**
 * @author MD
 * @create 2020-08-11 9:24
 */
public interface SaleDao {

    // 增加销售记录
    int insertSale(Sale sale);

}
//----------------------
package com.md.dao;

import com.md.domain.Goods;

/**
 * @author MD
 * @create 2020-08-11 9:30
 */
public interface GoodsDao {
    // 更新库存
    int updateGoods(Goods goods);

    // 查询商品的信息,根据id
    Goods selectGoods(Integer id);

}

5. 定义dao接口对应的sql映射文件

SaleDao.xml




    
      insert into sale(gid,nums) values(#{gid},#{nums})
    

GoodsDao.xml




    



    
    
        update goods set amount = amount - #{amount} where id=#{id}
    


6. 定义异常类

定义service层可能抛出的异常类

package com.md.excep;

/**
 * @author MD
 * @create 2020-08-11 9:49
 */

// 自定义的运行时异常
public class NotEnoughException extends RuntimeException {


    public NotEnoughException() {
        super();
    }

    public NotEnoughException(String message) {
        super(message);
    }
}

7. 定义service接口及实现类

package com.md.service;

/**
 * @author MD
 * @create 2020-08-11 9:43
 */
public interface BuyGoodsService {

    // 购买商品,goodsId:购买商品的编号,nums:购买的数量
    void buy(Integer goodsId , Integer nums);

}
//-----------------------------------
package com.md.service.impl;

import com.md.dao.GoodsDao;
import com.md.dao.SaleDao;
import com.md.domain.Goods;
import com.md.domain.Sale;
import com.md.excep.NotEnoughException;
import com.md.service.BuyGoodsService;

/**
 * @author MD
 * @create 2020-08-11 9:45
 */
public class BuyGoodsServiceImpl implements BuyGoodsService {


    private SaleDao saleDao;
    private GoodsDao goodsDao;

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

    public void setGoodsDao(GoodsDao goodsDao) {
        this.goodsDao = goodsDao;
    }

    @Override
    public void buy(Integer goodsId, Integer nums) {

        System.out.println("=========buy方法开始===========");

        // 记录销售记录,向sale表中添加数据
        Sale sale = new Sale();
        sale.setGid(goodsId);
        sale.setNums(nums);
        saleDao.insertSale(sale);



        // 先查询该商品
        Goods goods = goodsDao.selectGoods(goodsId);
        if (goods == null){
            // 商品不存在
            throw new NullPointerException("编号:"+goodsId+" 的商品不存在");
        }else if (goods.getAmount() < nums){
            // 商品库存不足
            throw new NotEnoughException("编号:"+goodsId+" 的商品不足,只能购买: "+goods.getAmount()+" 个");
        }


        // 更新库存
        Goods buyGoods = new Goods();
        buyGoods.setId(goodsId);
        buyGoods.setAmount(nums);
        goodsDao.updateGoods(buyGoods);

        System.out.println("=========buy方法结束===========");

    }
}

8. mybatis.xml







    
    

        
        
    



    
    
        
        

    




    
    

        
        
    


9. Spring配置文件

applicationContext.xml




    
    


    
    

        
        
        
        
        

        
        
        
        

    



    
    
    

        
        
        
        
    



    
    
        
        

        


        

        
        


    


    




    



    
    

        
        
        
    


10. 测试

 @Test
    public void test01(){
        String config = "applicationContext.xml";

        ApplicationContext ac = new ClassPathXmlApplicationContext(config);

        // 从容器中获取service,你声明service时候的id
        BuyGoodsService buyGoodsService = (BuyGoodsService) ac.getBean("buyGoodsService");
        buyGoodsService.buy(1001,101);

    }

此时程序一切正常

Spring事务_第3张图片

三、使用 Spring 的事务注解管理事务

通过@Transactional 注解方式,可将事务织入到相应 public 方法中,实现事务管理

主要记住前三个就行了

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

  • propagation:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为Propagation.REQUIRED
  • isolation:用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为Isolation.DEFAULT。
  • readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值为 false。
  • timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为-1,即没有时限。
  • rollbackFor:指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
  • rollbackForClassName:指定需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组
  • noRollbackFor:指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
  • noRollbackForClassName:指定不需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。

需要注意的是,

@Transactional 若用在方法上,只能用于 public 方法上

对于其他非 public方法,如果加上了注解@Transactional,虽然 Spring 不会报错,但不会将指定事务织入到该方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction 注解。
若@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。

实现注解的事务步骤:

把上面写好的程序重新复制一份,内容不变

1. 声明事务管理器

还是在Spring的配置文件中加入


    
        
        
    

2. 开启注解驱动


    
    
    

3. 完整Spring配置文件

applicationContext.xml




    


    
    
        
        
        
        
    



    
    
        
        
    



    
    
        
        
    

    
    

        
        
        
    

    

    
    
        
        
    

    
    
    
    


    


4. 业务层 public 方法加入事务属性

在方法上面加@Transactional

这里也就是在service接口的实现类里的方法上,全部代码如下:

package com.md.service.impl;

import com.md.dao.GoodsDao;
import com.md.dao.SaleDao;
import com.md.domain.Goods;
import com.md.domain.Sale;
import com.md.excep.NotEnoughException;
import com.md.service.BuyGoodsService;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author MD
 * @create 2020-08-11 9:45
 */
public class BuyGoodsServiceImpl implements BuyGoodsService {


    private SaleDao saleDao;
    private GoodsDao goodsDao;

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

    public void setGoodsDao(GoodsDao goodsDao) {
        this.goodsDao = goodsDao;
    }


    /**
     *rollbackFor:表示发生指定的异常一定回滚
     *
     */

//    @Transactional(
//            propagation = Propagation.REQUIRED,
//            isolation = Isolation.DEFAULT,
//            readOnly = false,
//            rollbackFor = {
//                    NullPointerException.class,NotEnoughException.class
//            }
//    )

    // 都使用默认值也是可以的,默认的传播行为是REQUIRED,默认的隔离级别是DEFAULT
    // 默认抛出运行时异常,回滚事务
    @Transactional
    @Override
    public void buy(Integer goodsId, Integer nums) {

        System.out.println("=========buy方法开始===========");

        // 记录销售记录,向sale表中添加数据
        Sale sale = new Sale();
        sale.setGid(goodsId);
        sale.setNums(nums);
        saleDao.insertSale(sale);



        // 先查询该商品
        Goods goods = goodsDao.selectGoods(goodsId);
        if (goods == null){
            // 商品不存在
            throw new NullPointerException("编号:"+goodsId+" 的商品不存在");
        }else if (goods.getAmount() < nums){
            // 商品库存不足
            throw new NotEnoughException("编号:"+goodsId+" 的商品不足,只能购买: "+goods.getAmount()+" 个");
        }


        // 更新库存
        Goods buyGoods = new Goods();
        buyGoods.setId(goodsId);
        buyGoods.setAmount(nums);
        goodsDao.updateGoods(buyGoods);

        System.out.println("=========buy方法结束===========");

    }
}

5. 测试

@Test
    public void test01(){
        String config = "applicationContext.xml";

        ApplicationContext ac = new ClassPathXmlApplicationContext(config);

        // 从容器中获取service,你声明service时候的id
        BuyGoodsService buyGoodsService = (BuyGoodsService) ac.getBean("buyGoodsService");

        // jdk动态代理对象com.sun.proxy.$Proxy16
        //System.out.println(buyGoodsService.getClass().getName());

        buyGoodsService.buy(1003,100);

    }

假设此时的1003号商品不存在,即使上面已经向sale表中添加数据了,但是由于还没有执行到更新库存方法的时候出现了异常,此时由于添加了事务,这个时候就会回滚,你查看sale的数据表没有数据,但是你之后买一个存在的商品,发现id号已经不是连续的了,如图所示:

Spring事务_第4张图片

由于sale的id设置的自增,就是因为回滚的原因,id不连续

四、使用 AspectJ 的 AOP 配置管理事务

使用 XML 配置事务代理的方式的不足是,每个目标类都需要配置事务代理。当目标类较多,配置文件会变得非常臃肿。
使用 XML 配置顾问方式可以自动为每个符合切入点表达式的类生成事务代理。其用法很简单,只需将前面代码中关于事务代理的配置删除,再替换为如下内容即可

还是使用上面举例的程序,复制一份

1. maven依赖pom.xml

还是直接把完整的pom.xml放这,主要就是添加了一个aspectj的




  4.0.0

  com.md
  09-spring-trans-aspectj
  1.0-SNAPSHOT

  
    UTF-8
    1.8
    1.8
  

  

    
    
      junit
      junit
      4.11
      test
    



    
    
      org.springframework
      spring-aspects
      5.2.5.RELEASE
    


    
    
      org.springframework
      spring-context
      5.2.5.RELEASE
    

    
    
      org.springframework
      spring-tx
      5.2.5.RELEASE
    
    
      org.springframework
      spring-jdbc
      5.2.5.RELEASE
    


    
    
      org.mybatis
      mybatis
      3.5.1
    

    
    
      org.mybatis
      mybatis-spring
      1.3.1
    

    
    
      mysql
      mysql-connector-java
      5.1.9
    


    
    
      com.alibaba
      druid
      1.1.12
    




  

  

    
    
      
        src/main/java
        
          **/*.properties
          **/*.xml
        
        false
      
    



  



2. 在容器中添加事务管理器

还是在Spring的配置文件中加入

 
    
        
    

3. 配置事务通知

为事务通知设置相关属性。用于指定要将事务以什么方式织入给哪些方法。
例如,应用到 buy 方法上的事务要求是必须的,且当 buy 方法发生异常后要回滚业务

 
    
    
        
        
            
            



            
            
            

            


        

    

4. 配置增强器

指定将配置好的事务通知,织入给谁

 

    
        
        

        
        
    

5. 完整Spring配置文件

applicationContext.xml




    
    


    
    
        
        
        
        

    



    
    
        
        
    



    
    
        

        
    



    



    
    

        
        
        
    




    

    
    
        
    


    
    
    
        
        
            
            



            
            
            

            


        

    



    

    
        
        

        
        
    





6. 测试类

@Test
    public void test01(){
        String config = "applicationContext.xml";

        ApplicationContext ac = new ClassPathXmlApplicationContext(config);

        // 从容器中获取service,你声明service时候的id
        BuyGoodsService buyGoodsService = (BuyGoodsService) ac.getBean("buyGoodsService");
        buyGoodsService.buy(1001,10);

    }

五、总结

1. Spring 的事务注解管理事务

适合中小项目使用的注解方案

spring框架自己用aop实现给业务方法增加事务的功能, 使用@Transactional注解增加事务。
@Transactional注解是spring框架自己注解,放在public方法的上面,表示当前方法具有事务。
可以给注解的属性赋值,表示具体的隔离级别,传播行为,异常信息等等

使用@Transactional的步骤:

  1. 需要声明事务管理器对象

  2. 开启事务注解驱动, 告诉spring框架,我要使用注解的方式管理事务

    • spring使用aop机制,创建@Transactional所在的类代理对象,给方法加入事务的功能
    • spring给业务方法加入事务:在你的业务方法执行之前,先开启事务,在业务方法之后提交或回滚事务,使用aop的环绕通知
    @Around("你要增加的事务功能的业务方法名称")
    		 Object myAround(){
               开启事务,spring给你开启
    			  try{
    			     buy(1001,10);
    				  spring的事务管理器.commit();
    			  }catch(Exception e){
                 spring的事务管理器.rollback();
    			  }
    			 
    		 }
    
  3. 在你的方法的上面加入@Trancational

2. AspectJ 的 AOP 配置管理事务

适合大型项目,有很多的类,方法,需要大量的配置事务,使用aspectj框架功能,在spring配置文件中声明类,方法需要的事务

这种方式业务方法和事务配置完全分离

实现步骤: 都是在xml配置文件中实现

  1. 要使用的是aspectj框架,需要加入依赖

    
    		org.springframework
    		spring-aspects
    		5.2.5.RELEASE
    	
    
  2. 声明事务管理器对象

  3. 声明方法需要的事务类型(配置方法的事务属性【隔离级别,传播行为,超时】)

  4. 配置aop:指定哪些哪类要创建代理

你可能感兴趣的:(Spring事务)