Spring handling transaction examples


Purchase merchandise items, simulate users placing orders, add sales records to the order table, and reduce inventory from the merchandise table

Create database table

sale sales list
Insert picture description here
goods product list
Insert picture description here

Add product data
Insert picture description here

Create entity class

Create entity classes of Goods and Sale respectively, the code is omitted

Create interface

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);
}

Create exception class

In order to see the process of spring transaction processing runtime exceptions, create an inherited class of RuntimeException

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

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

Create 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 configuration file

Mybatis and properties files are omitted, nothing

<?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 Results

First test the following normal purchases

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

Insert picture description here
At this time, the sale table and goods table are added and deleted normally

At this time, check if the runtime is abnormal, the purchase quantity exceeds the inventory quantity

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

If the purchase exceeds the inventory
, the sale table will be updated, but the goods will not be updated. It is not a transaction yet, and the exception occurs after the sale before the goods

Handle affairs

Use the transaction processing solution provided in the spring framework

1. Annotated plan suitable for small and medium-sized projects.
The spring framework itself uses aop to implement the function of adding transactions to business methods, and uses @Transactional annotations to add transactions. This annotation can only be used on public methods
@Transactional all optional attributes:

  • propagation: used to set transaction propagation attributes. The attribute type Propagation enumeration, the default is Propagation.REQUIRED
    Insert picture description here

Use @Transactional steps

1. Need to declare the transaction manager object
2. Turn on the transaction annotation driver and tell the spring framework that we want to use the annotation method to manage the transaction.
Spring uses the aop mechanism to create the class proxy object where @Transactional is located, and add the transaction function to the method.
Before the execution of the business method, first open the transaction, submit or roll back the transaction after the business method, use the surround notification of aop
@Around()
Object myAround(){ //Open the transaction, spring will open try{ buy(1001,15) ); spring transaction management.commit(); }catch(Exception e){ spring transaction management.rollback(); } } add in spring configuration file









Note that the annotation-driven must end with tx, and tx is the one that handles the transaction


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

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

Finally, add @Transactional to the method to complete the transaction operation.
Here you can use the attribute default value.

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

Dependencies used in doing spring transactions

 <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>

Regarding the rollback of the
spring framework, first check whether the exception thrown by the method is in the attribute of roolbakFor. If the exception is run-time or not, it will be rolled back. If not, compare whether it is a run-time exception, and roll back.

Use Aspectj to handle transactions

Suitable for large-scale projects, there are many classes and methods that require a lot of configuration transactions. Use the aspectj framework function, and then speing the configuration files to declare the classes and methods. In this way, business method and configuration are completely separated

First add the dependency of the aspectj framework

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

Declare the transaction manager object
There are above, slightly
declare the type of transaction required by the method (transaction properties of the configuration method [isolation level, propagation behavior, timeout])

In the spring configuration file

<!--  使用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>

Guess you like

Origin blog.csdn.net/qq_36976201/article/details/109142774