第5章Spring 事务(第二个实例,购买商品使用注解方式)

使用 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 注解在类上,则表示该类上所有的方法均将在执行时织入事务。
实现注解的事务步骤:
复制 trans_sale 项目,新项目 trans_sale_annotation
1.
声明事务管理器
2.开启注解驱动

 

transaction-manager :事务管理器 bean id
3. 业务层 public 方法加入事务属性

以上步骤见上文

Step7 :定义 service 的实现类
      定义 service 层接口的实现类 BuyGoodsServiceImpl
 

package com.it.service.impl;

import com.it.dao.GoodsDao;
import com.it.dao.SaleDao;
import com.it.entity.Goods;
import com.it.entity.Sale;
import com.it.exception.NotEnoughException;
import com.it.service.BuyGoodsService;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

public class BuyGoodsServiceImpl implements BuyGoodsService {

    private SaleDao saleDao;
    private GoodsDao goodsDao;

//rollbackFor:表示发生指定的异常,一定回滚。
//    下面的 @Transactional都是使用的默认值,和只写一个@Transactional效果是一样的
//    @Transactional(
//            propagation = Propagation.REQUIRED,
//            isolation = Isolation.DEFAULT,
//            readOnly = false,
//            rollbackFor = {
//                    NullPointerException.class,
//                    NotEnoughException.class
//            }
//    )
    @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 buyGoods=new Goods();
        buyGoods.setId(goodsId);
        buyGoods.setAmount(nums);
        goodsDao.updateGoods(buyGoods);


        System.out.println("=====buy()的结束=======");
    }

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

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


}

 Step8 :修改 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:tx="http://www.springframework.org/schema/tx"
       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/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd">

<!--  数据库的配置信息,写在一个独立的文件,编译修改数据库的配置内容
让spring知道jdbc.properties文件的位置
-->
    <context:property-placeholder location="classpath:jdbc.properties"/>



    <!--声明数据源DataSource,作用是连接数据库的-->
    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close" >
<!--   set注入,给DruidDataSource提供连接数据库信息-->
<!--    使用属性配置文件中的数据,语法${key}-->
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="maxActive" value="${jdbc.max}"/>
    </bean>

<!--    声明mybatis中提供的SqlSessionFactoryBean类-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--set注入,把德鲁伊数据库连接池赋给dataSource属性-->
        <property name="dataSource" ref="myDataSource"/>
<!-- mybais主配置文件的位置-->
        <property name="configLocation" value="classpath:mybatis.xml"/>
    </bean>

<!-- 创建dao对象,使用SqlSession的getMapper(Student.class)
MapperScannerConfigurer:在内部调用getMapper()生成每个dao接口的代理对象。
-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--  指定sqlSessionFactory对象的id-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!--   指定包名,包名是dao接口所在的包名。
MapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行一次,getMapper()方法,
得到每个接口的dao对象,创建好的dao对象是放入到spring的容器中的。
-->
        <property name="basePackage" value="com.it.dao"/>
    </bean>

<!--    声明service-->
    <bean id="buyService" class="com.it.service.impl.BuyGoodsServiceImpl">
        <property name="goodsDao" ref="goodsDao"/>
        <property name="saleDao" ref="saleDao"/>
    </bean>


<!--    使用spring事务处理-->

<!--   1.声明事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 连接的数据库,指定数据源-->
        <property name="dataSource" ref="myDataSource"/>
    </bean>
<!-- 2.开启事务注解驱动,告诉spring使用注解管理事务,创建代理对象
 transaction-manager:事务管理器对象的id
 -->
<!-- annotation-driven是后缀为tx包下的  tx="http://www.springframework.org/schema/tx"-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

Step9 :定义测试类

package com.it;

import com.it.service.BuyGoodsService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Unit test for simple App.
 */
public class AppTest 
{
//  测试正常购买
  @Test
    public void test1(){
      String config="applicationContext.xml";
      ApplicationContext ac=new ClassPathXmlApplicationContext(config);
//      从容器获取service
      BuyGoodsService buyService = (BuyGoodsService) ac.getBean("buyService");

//      调用方法
      buyService.buy(1001,10);
  }

//  测试购买不存在的商品
  @Test
  public void test2(){
    String config="applicationContext.xml";
    ApplicationContext ac=new ClassPathXmlApplicationContext(config);
    BuyGoodsService buyService = (BuyGoodsService) ac.getBean("buyService");
    buyService.buy(1003,10);
  }

  //  测试购买超出库存的商品
  @Test
  public void test3(){
    String config="applicationContext.xml";
    ApplicationContext ac=new ClassPathXmlApplicationContext(config);
    BuyGoodsService buyService = (BuyGoodsService) ac.getBean("buyService");
    buyService.buy(1002,100);
  }







}

数据库初始内容

注解测试

1.测试正常购买

2.测试购买不存在商品时

        相比较没有使用注解写购买商品实例,该使用注解的方式,发生异常时进行了回滚。

       回滚表示:如果一个事务中途发生异常,会进行数据库回滚,把事务发生异常之前的执行的操作进行回退恢复成事务没有执行时的样子。

3.测试购买超过库存数量的商品

        相比较没有使用注解写购买商品实例,该使用注解的方式,发生异常时进行了回滚。

       回滚表示:如果一个事务中途发生异常,会进行数据库回滚,把事务发生异常之前的执行的操作进行回退恢复成事务没有执行时的样子。

4.那么发生的回滚后,是否会留下回滚痕迹呢,答案是肯定的,再次向据库添加一条数据,因为设置的表的id属性是自增的,可以再增加一条数据查看它的自增id是否会间隔两个数(间隔两位数是因为我连续运行了两次中断异常,发生了两次回滚)

 可以看到13和16间隔了两位数

5.

事务的回滚条件

 @Transactional的属性rollbackFor的处理逻辑为:

(1)spring框架会首先检查方法抛出的异常是不是在rollbackFor的属性值中,如果rollbackFor列表中,不管是什么类型的异常一定回滚。

(2)如果抛出的异常不在rollbackFor列表中,spring会判断异常是不是RuntimeException,如果是,一定回滚。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

做一道光

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值