spring处理事务实例


购买商品项目,模拟用户下订单,向订单表添加销售记录,从商品表减少库存

创建数据库表

sale销售表
在这里插入图片描述
goods商品表
在这里插入图片描述

添加商品数据
在这里插入图片描述

创建实体类

分别创建Goods和Sale的实体类,代码略

创建接口

SaleDao

public interface SaleDao {
    
    
    //增加销售记录
    @Insert("insert into sale(gid,num) values(#{gid},#{num})")
    int insertSale(Sale sale);
}

GoodsDao

public interface GoodsDao {
    
    
    //更新库存
    //goods表示本次用户购买的商品信息,id,购买数量
    @Update("update goods set amount=amount-#{amount} where id=#{id}")
    int updateGoods(Goods goods);

    //查询商品信息
    @Select("select * from goods where id=#{id}")
    Goods selectGoods(Integer id);
}

创建异常类

为了看一下spring事务处理运行时异常的过程,创建一个RuntimeException的继承类

//自定义运行时异常
public class NotEnoughException extends RuntimeException{
    
    
    public NotEnoughException() {
    
    
    }

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

创建service

public interface BuyGoodsService {
    
    
    //购买商品
    void buy(Integer id, Integer num);
}
@Component
public class BuyGoodsServiceImpl implements BuyGoodsService {
    
    
    @Autowired
    private SaleDao saleDao;
    @Autowired
    private GoodsDao goodsDao;

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

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

    @Override
    public void buy(Integer id, Integer num) {
    
    
        //记录销售信息,向Sale添加记录
        Sale sale=new Sale();
        sale.setGid(id);
        sale.setNum(num);
        saleDao.insertSale(sale);

        //更新库存
        Goods goods=goodsDao.selectGoods(id);
        if(goods==null){
    
    
            //商品不存在
            throw new NullPointerException(id+"号商品不存在");
        }else if(goods.getAmount()<num){
    
    
            throw new NotEnoughException(id+"号商品库存不足");
        }
        Goods buygoods=new Goods();
        buygoods.setId(id);
        buygoods.setAmount(num);
        goodsDao.updateGoods(buygoods);
                System.out.println("购买成功");
    }
}

spring配置文件

Mybatis和属性文件省略了,没什么东西

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--    引用属性文件-->
    <context:property-placeholder location="jdbc.properties"/>
    <context:component-scan base-package="org.example"/>

<!--    数据源声明DataSource,连接数据库-->
    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
      init-method="init" destroy-method="close">
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    </bean>

<!--声明的是Mybatis中提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--        数据源,ref是id-->
        <property name="dataSource" ref="myDataSource"/>
<!--        mybatis主配置文件的位置-->
    <property name="configLocation" value="mybatis.xml"/>
    </bean>

<!--    创建dao对象,使用SqlSession的getmapper(Studentdao.class)-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--        指定SqlSessionFactory的id-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!--       指定包名,dao接口所在的包名,MapperScannerConfigurer
会扫描这个包中的所有接口,把每个接口都执行一次getMapper()方法,
得到每个接口的dao对象,默认名称是接口名首字母小写 -->
        <property name="basePackage" value="org.example.dao.dao"/>
    </bean>

</beans>

测试结果

先测试以下正常购买

    @Test
    public void shouldAnswerWithTrue()
    {
    
    
        String config="spring_total.xml";
        ApplicationContext ctx=new ClassPathXmlApplicationContext(config);
        BuyGoodsService buy =(BuyGoodsService) ctx.getBean("buyGoodsServiceImpl");
        buy.buy(1001,7);
    }

在这里插入图片描述
此时sale表和goods表正常添加删除

此时测一下运行时异常,购买数量超过库存数量

        String config="spring_total.xml";
        ApplicationContext ctx=new ClassPathXmlApplicationContext(config);
        BuyGoodsService buy =(BuyGoodsService) ctx.getBean("buyGoodsServiceImpl");
        buy.buy(1001,15);

如果购买超过库存
此时会更新sale表,而不会更新goods,现在还不是一个事务,异常是发生在sale之后goods之前

处理事务

使用spring框架中提供的事务处理方案

1、适合中小项目使用的,注解方案。
spring框架自己用aop实现给业务方法增加事务的功能,使用@Transactional注解增加事务。这个注解只能用在public方法上
@Transactional所有可选属性:

  • propagation:用于设置事务传播属性。该属性类型Propagation枚举,默认为Propagation.REQUIRED
    在这里插入图片描述

使用@Transactional步骤

1、需要声明事务管理器对象
2、开启事务注解驱动,告诉spring框架我们要使用注解方式管理事务
spring使用aop机制,创建@Transactional所在的类代理对象,给方法加入事务的功能。
在业务方法执行之前,先开启事务,在业务方法之后提交或者回滚事务,使用aop的环绕通知
@Around()
Object myAround(){
//开启事务,spring给你开启
try{
buy(1001,15);
spring事务管理.commit();
}catch(Exception e){
spring事务管理.rollback();
}
}
spring配置文件中添加
注意annotation-driven要选tx结尾的,tx才是处理事务的


<!--  使用spring的事务处理  -->
<!--    声明事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--        连接数据库,指定数据源-->
        <property name="dataSource" ref="myDataSource"/>
    </bean>

<!--    开启事务注解驱动,告诉spring使用注解管理事务,创建代理对象-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

最后在方法上加上@Transactional即可完成事务操作
这里用属性默认值即可

@Transactional
public void buy(Integer id, Integer num) {
    
    
……………………
}

做spring事务用到的依赖

 <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.2.9.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.9.RELEASE</version>
    </dependency>

关于回滚
spring框架首先检查方法抛出的异常是不是在roolbakFor的属性中,如果在不管是不是运行时异常都会回滚,不在的话再比较是不是运行时异常,是就回滚

使用Aspecj处理事务

适合大型项目,有很多类,方法,需要大量的配置事务,使用aspectj框架功能,再speing配置文件中声明类,方法需要的事务。这种方式业务方法和配置完全分离

先加入aspectj框架的依赖

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>

声明事务管理器对象
上面有,略
声明方法需要的事务类型(配置方法的事务属性【隔离级别、传播行为、超时】)

在spring的配置文件中

<!--  使用spring的事务处理  -->
<!--    声明事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--        连接数据库,指定数据源-->
        <property name="dataSource" ref="myDataSource"/>
    </bean>

<!--    声明业务方法它的属性(隔离级别、传播行为、超时时间)-->
    <tx:advice id="myAdvice" transaction-manager="transactionManager">
<!--    tx:attributes表示配置事务属性   -->
        <tx:attributes>
<!--          tx:method  表示给具体的方法配置事务属性,可以有多个
此时方法并不区分哪个包的方法使用事务,大家名字一样都用-->
<!--            name:完整的方法名,不带包和类,也可以使用通配符*表示任意字符-->

<!--            三种写法分优先级:全名最高优先级,带有通配符的第二优先级,只有通配符的是第三优先级
意思就是先找buy,找不到buy就找add*,最后找剩下的*-->
            <tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
                       rollback-for="java.lang.NullPointerException,org.example.dao.exception.NotEnoughException"/>
            <tx:method name="add*" propagation="REQUIRES_NEW"/>
            <tx:method name="*" propagation="SUPPORTS" read-only="true"/>
        </tx:attributes>
    </tx:advice>

<!--    如果要区分哪个包用事务,可以用aop配置-->
    <aop:config>
<!--        配置切入点表达式:指定哪些包中的类需要使用事务
id:切入点表达式的名称,唯一值
expression:切入点表达式,指定哪些类要使用事务,aspectj会创建代理对象-->
        <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>
<!--        配置增强器:关联advice和pointcut-->
        <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/>
    </aop:config>

猜你喜欢

转载自blog.csdn.net/qq_36976201/article/details/109142774