八、Spring事务(注解方案)

八、Spring事务(注解方案)

声明式事务:AOP
编程式事务:需要在代码中进行事务管理

1、Spring事务处理模型

*使用步骤是固定的,只要把事务的使用信息提供给Spring就完事了。

  1. 事务内部提交、回滚事务,使用事务管理器对象,代替你完成commit、rolback
    1. 事务管理器是一个接口和它众多的实现类
    2. 事务管理器接口:PlatformTransactionManager,定义了事务重要的方法commit、rolback
    3. 事务管理器接口实现类:Spring把每一种数据库访问技术对应的事务处理类都创建好了
      1. mybatis访问数据库:DataSourceTransactionManager
      2. hibernate访问数据库:HibernateTransactionManager
      3. … …
    4. 使用方法:告诉Spring要使用哪种数据库访问技术
      1. 在spring配置文件使用声明需要使用的数据库访问技术对应的事务管理器实现类
        1. eq:
  2. 说明需要事务的类型(TransactionDefinition)
    1. 事务的隔离级别(对应TransactionDefinition下定义的5个以**ISOLATION_**开头的常量)
      1. DEFAULT:采用DB默认事务隔离级别,Mysql为REPEATABLE_READ,Oracle为READ_COMMITTED
      2. **READ_UNCOMMITTED:**读未提交。未解决任何并发问题
      3. **READ_COMMITTED:**读已提交。解决脏读,存在不可重复读和幻读
      4. **REPEATABLE_READ:**可重复读。解决脏读、不可重复读,存在幻读
      5. **SERIALIZABLE:**串行化。不存在并发问题
    2. 事务的超时时间(TIMEOUT_DEFAULT):表示一个事务最长的执行时间,如果超时就回滚,单位:秒(整数值),默认-1不限时
    3. 事务的传播行为(有7个):控制业务方法是否有事务,有什么样的事务(对应TransactionDefinition下定义的7个以**PROPAGATION_**开头的常量)
      1. **PROPAGATION_REQUIRED:**指定的方法必须在事务内执行,若存在当前事务则加入,不存在则创建一个新事务(最常见的选择,也是Spring默认的事务传播行为)
      2. **PROPAGATION_REQUIRES_NEW:**总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事物执行完毕
      3. **PROPAGATION_SUPPORTS:**若存在当前事务就加入,不存在就以非事务方式执行
      4. PROPAGATION_MANDATORY
      5. PROPAGATION_NESTED
      6. PROPAGATION_NEVER
      7. PROPAGATION_NOT_SUPPORTED
  3. 事务提交、回滚时机
    1. 业务方法正确执行完毕,没有抛出异常,spring自动提交事务
    2. 业务方法执行过程中抛出了运行时异常(RuntimeException及其子类)或ERROR,spring自动回滚事务
    3. 业务方法执行过程中抛出了非运行时异常(主要是受查异常),也会提交事务
      1. 受查异常:写代码时必须要捕获的异常(IOException、SQLException…)

2、Spring事务处理方案

1、注解方案(适合中小型项目使用,本文重点介绍)

  1. Spring框架自己用AOP实现业务方法增加事务的功能,使用**@Transactional**注解添加事务
  2. @Transactional注解是Spring框架提供的注解,放在public方法上面,表示当前方法开启了事务
  3. 可以给@Transactional注解的属性赋值,表示具体的隔离级别、传播行为、异常信息等

2、使用aspectJ框架(适合大型项目)

​ 在Spring配置文件中声明类、方法需要的事务,达到业务方法和事务配置完全分离。一键跳转~


1、@Transactional属性

  1. **propagation:**事务传播行为。类型为Propagation枚举,默认值为Propagation.REQUIRED
  2. **isolation:**事务隔离级别。类型为Isolation枚举,默认值为Isolation.DEFAULT
  3. **readOnly:**设置该方法对数据库的操作是否是只读的。类型为boolean,默认值为false
  4. **timeout:**设置本操作与数据库连接的超时时限。单位为秒,类型为int,默认值为-1(无时限)
  5. **rollbackFor:**设置需要回滚的异常类。类型为Class[],默认值为空数组(只有一个异常类时可不用数组)
  6. **rollbackForClassName:**设置需要回滚的异常类名字。类型为String[],默认值为空数组(只有一个异常类名字时可不用数组)
  7. **notRollbackFor:**设置不需要回滚的异常类。类型为Class[],默认值为空数组(只有一个异常类时可不用数组)
  8. **notRollbackForClassName:**设置不需要回滚的异常类名字。类型为String[],默认值为空数组(只有一个异常类名字时可不用数组)

2、@Transactional的使用步骤

  1. 声明事务管理器对象

  2. 开启事务注解驱动

    1. 告诉spring框架爹要用注解的方式管理事务

    2. spring使用AOP机制,创建@Transactional所标注类的代理对象,给方法加入事务功能

    3. spring给业务方法加入事务:使用AOP环绕通知,运行方法前开启事务,运行方法后提交或回滚事务

      1. @Around(value = "execution(..)")
        Object myAround(){
            // TODO: Spring开启事务
            try{
                // TODO: 执行业务方法
                // TODO: Spring提交事务
            } catch(Exception e){
                // TODO: Spring回滚事务
            }
        }
        
  3. 在业务方法上加@Transactional注解


3、实现步骤

  1. 创建maven项目
  2. 加入maven依赖
    1. spring依赖
    2. mybatis依赖
    3. mysql驱动
    4. spring的事务依赖
    5. mybatis和spring集成依赖(mybatis官方提供的,用来在spring项目中创建mybatis的SqlSessionFactory、dao对象的)
  3. 创建实体类
  4. 创建dao接口及mapper文件
  5. 创建mybatis主配置文件
  6. 创建Service接口及实现类,属性包含dao
  7. 创建spring配置文件(声明mybatis对象交给spring创建)
    1. 数据源(dataSource)
    2. SqlSessionFactory
    3. Dao对象
    4. 声明自定义的service
  8. 创建测试类,获取service对象,通过service对象完成对数据库的访问

4、项目结构

八、Spring事务(注解方案)_第1张图片

*MySql-Table-goods

八、Spring事务(注解方案)_第2张图片

*MySql-Table-sale

八、Spring事务(注解方案)_第3张图片

pom.xml


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0modelVersion>

  <groupId>hom.wanggroupId>
  <artifactId>st-07-spring-transartifactId>
  <version>1.0-SNAPSHOTversion>

  <properties>
    <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
    <maven.compiler.source>1.8maven.compiler.source>
    <maven.compiler.target>1.8maven.compiler.target>
  properties>

  <dependencies>
    <dependency>
      <groupId>junitgroupId>
      <artifactId>junitartifactId>
      <version>4.11version>
      <scope>testscope>
    dependency>
    <dependency>
      <groupId>org.springframeworkgroupId>
      <artifactId>spring-contextartifactId>
      <version>5.3.18version>
    dependency>

    
    <dependency>
      <groupId>org.springframeworkgroupId>
      <artifactId>spring-txartifactId>
      <version>5.3.18version>
    dependency>
    
    <dependency>
      <groupId>org.springframeworkgroupId>
      <artifactId>spring-jdbcartifactId>
      <version>5.3.18version>
    dependency>
    
    <dependency>
      <groupId>org.mybatisgroupId>
      <artifactId>mybatisartifactId>
      <version>3.5.7version>
    dependency>
    
    <dependency>
      <groupId>org.mybatisgroupId>
      <artifactId>mybatis-springartifactId>
      <version>2.0.6version>
    dependency>
    
    <dependency>
      <groupId>mysqlgroupId>
      <artifactId>mysql-connector-javaartifactId>
      <version>8.0.29version>
    dependency>
    
    <dependency>
      <groupId>com.alibabagroupId>
      <artifactId>druidartifactId>
      <version>1.2.6version>
    dependency>
  dependencies>

  <build>
    
    <resources>
      <resource>
        
        <directory>src/main/javadirectory>
        
        <includes>
          <include>**/*.xmlinclude>
          
        includes>
        <filtering>falsefiltering>
      resource>
    resources>
    
    <plugins>
      <plugin>
        <artifactId>maven-compiler-pluginartifactId>
        <version>3.8.1version>
        <configuration>
          <source>1.8source>
          <target>1.8target>
        configuration>
      plugin>
    plugins>
  build>
project>

data.Goods.java

package hom.wang.data;
import java.math.BigDecimal;
public class Goods {
    private String goodsId;
    private String goodsName;
    private Integer goodsAmount;
    private BigDecimal goodsPrice;
}

data.Sale.java

package hom.wang.data;
public class Sale {
    private Integer saleId;
    private String goodsId;
    private Integer saleNum;
}

dao.Goods.java

package hom.wang.dao;
import hom.wang.data.Goods;
public interface GoodsDao {
    int updateGoods(Goods goods);

    Goods selectGoodsById(Goods goods);
}

dao.GoodsMapper.xml


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="hom.wang.dao.GoodsDao">

    <update id="updateGoods">
        update goods
        set goods_amount = #{goodsAmount}
        where goods_id = #{goodsId}
    update>

    <resultMap id="goodsMap" type="hom.wang.data.Goods">
        <id property="goodsId" column="goods_id" />
        <result property="goodsName" column="goods_name" />
        <result property="goodsAmount" column="goods_amount" />
        <result property="goodsPrice" column="goods_price" />
    resultMap>

    <select id="selectGoodsById" resultMap="goodsMap">
        select
               goods_id,
               goods_name,
               goods_amount,
               goods_price
        from goods
        where goods_id = #{goodsId}
    select>

mapper>

dao.SaleDao.java

package hom.wang.dao;
import hom.wang.data.Sale;
public interface SaleDao {
    int insertSale(Sale sale);
}

dao.SaleMapper.xml


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="hom.wang.dao.SaleDao">

    <insert id="insertSale">
        insert into sale(goods_id, sale_num) values(#{goodsId}, #{saleNum})
    insert>

mapper>

exce.NotEnoughException.java

package hom.wang.exce;
/* 库存不足异常 */
public class NotEnoughException extends RuntimeException {
    public NotEnoughException() {
    }
    public NotEnoughException(String message) {
        super(message);
    }
}

service.impl.BuyServiceImpl.java

package hom.wang.service.impl;

import hom.wang.dao.GoodsDao;
import hom.wang.dao.SaleDao;
import hom.wang.data.Goods;
import hom.wang.data.Sale;
import hom.wang.exce.NotEnoughException;
import hom.wang.service.BuyService;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
public class BuyServiceImpl implements BuyService {
    GoodsDao goodsDao;
    SaleDao saleDao;

    /**
     * rollbackFor:发生指定异常一定回滚
     *  spring处理逻辑:
     *      1、首先判断抛出异常是否是rollbackFor包含的,是就回滚
     *      2、不在rollbackFor数组中则判断是否是RuntimeException,是就回滚
     */
    /* @Transactional(
                propagation = Propagation.REQUIRED,
                isolation = Isolation.DEFAULT,
                readOnly = false,
                rollbackFor = {
                        NullPointerException.class,
                        NotEnoughException.class
                }
        )*/
    // 上面配置的都是默认值,故而直接使用@Transactional就可以
    @Transactional
    @Override
    public void buy(Sale sale) {
        System.out.println("============ buy start =============");

        // 1、判断库存是否充足
        Goods goods = new Goods();
        goods.setGoodsId(sale.getGoodsId());
        goods = goodsDao.selectGoodsById(goods);

        if(null == goods){
            throw new NullPointerException("编号[" + sale.getGoodsId() + "]商品不存在!");
        }

        if(goods.getGoodsAmount() < sale.getSaleNum()){
            throw new NotEnoughException("库存不足!");
        }

        // 2、减少库存
        goods.setGoodsAmount(goods.getGoodsAmount() - sale.getSaleNum());
        goodsDao.updateGoods(goods);

        // 3、增加销售记录
        saleDao.insertSale(sale);

        System.out.println("============ buy end =============");
    }

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

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

mybatis.xml


DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    
    <typeAliases>
        
        <package name="hom.wang.data"/>
    typeAliases>

    <mappers>
        
        <mapper resource="hom/wang/dao/GoodsMapper.xml"/>
        <mapper resource="hom/wang/dao/SaleMapper.xml"/>

        
        
    mappers>
configuration>

jdbc.properties

jdbc.url=jdbc:mysql://127.0.0.1:3306/ms_user
jdbc.username=root
jdbc.password=root
jdbc.maxActive=20

applicationContext.xml


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

    <context:property-placeholder location="classpath:jdbc.properties">context:property-placeholder>

    
    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        
        <property name="url" value="${jdbc.url}">property>
        <property name="username" value="${jdbc.username}">property>
        <property name="password" value="${jdbc.password}">property>
        <property name="maxActive" value="${jdbc.maxActive}">property>
    bean>

    
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="myDataSource">property>
        <property name="configLocation" value="classpath:mybatis.xml">property>
    bean>

    
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory">property>
        
        <property name="basePackage" value="hom.wang.dao">property>
    bean>

    <bean id="buyService" class="hom.wang.service.impl.BuyServiceImpl">
        <property name="goodsDao" ref="goodsDao">property>
        <property name="saleDao" ref="saleDao">property>
    bean>

    
    
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        
        <property name="dataSource" ref="myDataSource" />
    bean>

    
    
    <tx:annotation-driven transaction-manager="transactionManager" />
beans>

MyTest.java

package hom.wang;
import hom.wang.data.Sale;
import hom.wang.service.BuyService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
    @Test
    public void test(){
        ApplicationContext ac = 
            new ClassPathXmlApplicationContext("applicationContext.xml");
        BuyService buyService = (BuyService) ac.getBean("buyService");
        //buyService的代理对象是:com.sun.proxy.$Proxy17,jdk动态代理对象
        System.out.println("buyService的代理对象是:" + buyService.getClass().getName());

        Sale sale = new Sale();
        sale.setGoodsId("p_yz_00002");
        sale.setSaleNum(1);

        buyService.buy(sale);
    }
}

*没错,最终结果正如你所预料的!

你可能感兴趣的:(Spring,简单回顾,spring,java)