![](https://i-blog.csdnimg.cn/blog_migrate/0c6d19654c1bd69c5ae553a62942cecf.png)
![](https://i-blog.csdnimg.cn/blog_migrate/bd57257701ab3653bfb78d90e6acc8d3.png)
以上步骤见上文
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,如果是,一定回滚。