购买商品项目,模拟用户下订单,向订单表添加销售记录,从商品表减少库存
创建数据库表
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>