03-Spring AOP

一、Spring AOP 简介

1.概述

  • 对于spring来说,有三大组件,IOC,ID,AOP

  • aop概述:AOP(Aspect Oriented Programming)面向切面编程。

  • 作用:不改变原有代码设计的基础上实现功能增强

    • 例子

      • 传统打印日志

        在这里插入图片描述

      • 使用AOP增强之后

        在这里插入图片描述

2.代理模式

  • 如果没有听过代理模式,点击链接先学习代理模式 : https://www.bilibili.com/video/BV1tY411Z799/?share_source=copy_web&vd_source=fdccda7d1272a2e0f49cadca354a5073
  • 静态代理
  • 动态代理
    • jdk 动态代理
    • cglib 动态代理

二、AOP概念

1.案例分析

  • 创建类提供增删改查方法,实现事务增强操作功能

    public interface IStudentService {
        void save(Student student);
    
        int update(Student student);
    
        Student queryStudentById(Long id);
    }
    
  • 接口实现类

    public class StudentServiceImpl implements IStudentService {
    
        public void save(Student student) {
    //        System.out.println("开启事务");
            System.out.println("保存操作");
    //        System.out.println("关闭事务");
        }
    
        public int update(Student student) {
    //        System.out.println("开启事务");
            System.out.println("更新操作");
    //        System.out.println("关闭事务");
            return 0;
        }
    
        public Student queryStudentById(Long id) {
            System.out.println("查询操作");
            return null;
        }
    }
    
  • 提供通知类

    public class TransactionAdvice {
        public void before(){
            System.out.println("开启事务");
        }
    
        public void after(){
            System.out.println("关闭事务");
        }
    
        public void invoke(){
            before();
            //具体的业务执行
            after();
        }
    }
    

2.核心概念

2.1概念

  • 连接点(JoinPoint):对于需要增强的方法就是连接点
  • 切入点(Pointcut):需要增强的方法是切入点,匹配连接点的式子
  • 通知(Advice):存放需要增强功能的共性代码,就叫通知
  • 切面(Aspect):通知是需要增强的功能存在多个,切入点是需要增强的方法也存在多个,需要去给切入点和通知做关联,知道哪个切入点对应哪个通知,这种描述关系就叫切面
  • 通知类:存放通知(方法)的类

2.2图示

image-20221103181229013

3.核心概念

  • 目标对象 target
  • 代理 proxy

三、通过注解实现AOP配置

1.导入依赖

  • 导入aop依赖

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.2.17.RELEASE</version>
    </dependency>
    
  • 导入Spring依赖

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.17.RELEASE</version>
    </dependency>
    

2.配置AOP支持

  • @EnableAspectJAutoProxy

  • 说明

    名称@EnableAspectJAutoProxy
    使用位置配置类上
    作用开启注解的aop支持
  • 代码

    @Configuration
    @EnableAspectJAutoProxy
    @ComponentScan("cn.sycoder")
    public class AppConfig {
    }
    

3.创建切面类

  • @Aspect

  • 说明

    名称@Aspect
    作用设置当前类为切面类
    使用位置类上
    属性String value() default “”;可以给切面指定名称
  • @Pointcut

  • 说明

    名称@Pointcut
    作用设置切入点方法
    使用位置方法上
    属性String value() default “”;切入点表达式
  • 代码

    @Component
    @Aspect
    public class TransactionAdvice {
        //定义通知 绑定切点和通知的关系
        @Before("pc()")
        public void before(){
            System.out.println("开启事务");
        }
        @After("pc()")
        public void after(){
            System.out.println("关闭事务");
        }
        //定义切点
        @Pointcut("execution(void cn.sycoder.service.impl.StudentServiceImpl.save(..))")
        public void pc(){
        }
    }
    

4.测试aop

  • 测试代码

    @Test
        public void testAop(){
            AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
            IStudentService bean = applicationContext.getBean(IStudentService.class);
            bean.save(null);
        }
    
    • 打印输出

      image-20221103205153372

5.各种通知

5.1@Before

  • 前置通知:被代理的目标方法执行前执行

  • 说明

    名称@Before
    使用位置方法上
    作用前置通知,目标方法执行前执行
    属性String value(); 切入点表达式
    可以提供的入参JoinPoint joinPoint ,切点
  • 使用

    @Before("execution(void cn.sycoder.service.impl.StudentServiceImpl.save(..))")
    public void before(JoinPoint joinPoint){
        System.out.println("开启事务");
    }
    

5.2@After

  • 后置通知:被代理的目标方法执行后执行

  • 说明

    名称@After
    使用位置方法上
    作用后置通知:被代理的目标方法执行后执行
    属性String value(); 切入点表达式
    可以提供的入参JoinPoint joinPoint ,切点
  • 使用

    @After("execution(void cn.sycoder.service.impl.StudentServiceImpl.save(..))")
    public void after(){
        System.out.println("关闭事务");
    }
    

5.3@AfterReturning

  • 返回通知:被代理的目标方法成功结束后执行

  • 说明

    名称@AfterReturning
    使用位置方法上
    作用返回通知:被代理的目标方法成功结束后执行
    属性String value(); 切入点表达式,String returning();方法返回值
    可以提供的入参JoinPoint joinPoint ,切点,方法返回值 obj
  • 使用

    @AfterReturning(returning = "obj",value = "execution(* cn.sycoder.service.impl.StudentServiceImpl.update(..))")
    public void afterReturning(JoinPoint joinPoint,Object obj){
        System.out.println(obj);
        System.out.println("返回通知");
    }
    

5.4@AfterThrowing

  • 异常通知:被代理的目标方法出现异常后执行

  • 说明

    名称@AfterThrowing
    使用位置方法上
    作用异常通知:被代理的目标方法出现异常后执行
    属性String value(); 切入点表达式String throwing();异常返回
    可以提供的入参JoinPoint joinPoint ,切点,异常返回值 th
  • 使用

    @AfterThrowing(throwing = "th",value = "execution(void cn.sycoder.service.impl.StudentServiceImpl.save(..))")
    public void afterThrowing(JoinPoint pointcut,Throwable th){
        System.out.println("异常通知");
    }
    

5.5@Around

  • 环绕通知:可以使用 try 代码块把被代理的目标方法围绕住,就可以做自己想做的操作,可以在里面做任何的操作

  • 说明

    名称@Around
    使用位置方法上
    作用异常通知:被代理的目标方法出现异常后执行
    属性String value(); 切入点表达式
    可以提供的入参ProceedingJoinPoint joinPoint,可以通过该对象调用原始方法
  • 使用

    @Around("execution(void cn.sycoder.service.impl.StudentServiceImpl.save(..))")
        public void around(ProceedingJoinPoint joinPoint){
            try{
                System.out.println("前置通知");
                Object proceed = joinPoint.proceed();//执行目标方法
                System.out.println("返回通知");
            }catch (Exception e){
                e.printStackTrace();
            } catch (Throwable throwable) {
                System.out.println("异常通知");
                throwable.printStackTrace();
    
            } finally {
    
            }
        }
    

5.6各种通知执行顺序

  • 环绕通知—前置通知—目标方法—返回通知或异常通知—后置通知

6.切入点表达式

  • 概述:切入点表达式是用来寻找目标代理方法的

    execution(public void cn.sycoder.service.impl.StudentServiceImpl.save(..))
    
  • 图示

    image-20221104150052284

  • 表达式实操

    编号名称使用位置作用
    1*代替权限修饰符和返回值表示任意权限和返回
    2*使用到包位置一个*表示当前一层的任意
    3*…使用到包位置任意包任意类
    4*使用到类表示任意类
    5*Service使用到类表示寻找以Service 结尾的任意接口或类
    6使用到参数表示任意参数
    1. 案例:找到实现类中的任意save方法

      execution(* cn.sycoder.service.impl.StudentServiceImpl.save(..))
      
    2. 案例:sycoder 包下面的类中的任意update 方法

      execution(* cn.sycoder.*.update(..))
      
    3. 案例:找到sycoder 包下面及其任意子包中的任意update 方法

      execution(* cn.sycoder.*..update(..))
      
    4. 案例:找到service 下面任意类的update 方法

      execution(* cn.sycoder.service.*.update(..))
      
    5. 案例:找到以Service 结尾的接口或者类的update 方法

      execution(* cn.sycoder.service.*Service.update(..))
      
    6. 案例:找到Service 结尾的接口或者类的update 方法,任意参数的

      execution(* cn.sycoder.service.*Service.update(..))
      
  • 注意:如果你切的越模糊,那性能就会越低,所以实际开发中,建议把范围切小一点

  • 优先级

    • 如果想手动指定优先级关系,可以使用@Order(1)注解
      • 提供的值越小,优先级越高

    在这里插入图片描述

  • 重用切入点表达式

    • 定义切点

      @Component
      @Aspect
      public class TransactionAdvice {
          //定义切点
          @Pointcut("execution(public void cn.sycoder.service.impl.StudentServiceImpl.save(..))")
          public void pc(){
              System.out.println("----切点");
          }
      }
      
    • 在其他切面类通知里面重用切点

      @Component
      @Aspect
      public class LogAdvice {
      
          @Before("cn.sycoder.advice.TransactionAdvice.pc()")
          public void log(){
              System.out.println("-0-----这里是打印日志");
          }
      }
      
    • 切面内部自己重用

      @Component
      @Aspect
      public class TransactionAdvice {
          //定义切点
          @Pointcut("execution(public void cn.sycoder.service.impl.StudentServiceImpl.save(..))")
          public void pc(){
              System.out.println("----切点");
          }
          //定义通知 绑定切点和通知的关系
          //前置通知
          @Before("pc()")
          public void before(JoinPoint joinPoint){
              String name = joinPoint.getSignature().getName();
              System.out.println(name);
              System.out.println("开启事务");
          }
      

7.获取通知相关信息

  • 获取连接点信息,在通知方法中添加参数 JoinPoint 即可

    @Before("pc()")
    public void before(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println(name);
        System.out.println("开启事务");
    }
    
  • 获取目标方法返回值

    • 使用AfterReturning 中的 returning 属性,这里指定的名称即是我们方法传入的名称
    @AfterReturning(returning = "obj",value = "pc()")
        public void afterReturning(JoinPoint joinPoint,Object obj){
            System.out.println(obj);
            System.out.println("返回通知");
        }
    
  • 获取异常

    • 使用AfterThrowing 中的 throwing 属性,这里指定的名称即是我们方法传入的参数名称
    @AfterThrowing(throwing = "th",value = "execution(* cn.sycoder.service.impl.StudentServiceImpl.save(..))")
        public void afterThrowing(JoinPoint pointcut,Throwable th){
            System.out.println("异常通知");
        }
    
  • 如果使用环绕通知

    • 使用ProceedingJoinPoint joinPoint
     @Around("execution(void cn.sycoder.service.*..save(..))")
        public void around(ProceedingJoinPoint joinPoint){
            try{
                System.out.println("环绕通知");
    //            System.out.println("前置通知");
                Object proceed = joinPoint.proceed();//执行目标方法
    //            System.out.println("返回通知");
            }catch (Exception e){
                e.printStackTrace();
            } catch (Throwable throwable) {
    //            System.out.println("异常通知");
                throwable.printStackTrace();
    
            } finally {
    
            }
        }
    

四、XML配置AOP

1.导入依赖

<dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.17.RELEASE</version>
    </dependency>

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

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <!--            <scope>test</scope>-->
    </dependency>

2.基本准备

  • 创建 service 接口以及方法

    public interface IStudentService {
        void save(Student student);
    }
    
    public class StudentServiceImpl implements IStudentService {
    
        public void save(Student student) {
            System.out.println("保存操作");
        }
    }
    
  • 创建切面类

    public class XmlAspect {
        public void before(){
            System.out.println("前置通知");
        }
    
        public void pointCut(){
    
        }
    
        public void after(JoinPoint joinPoint){
            System.out.println("后置通知");
        }
    
        public void afterReturning(Object obj){
            System.out.println("返回通知"+obj);
        }
    
        public void afterThrowing(Throwable t){
            System.out.println("异常通知");
        }
    }
    

3.创建xml 配置文件

  • aop.xml

    <?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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
        <bean id="service" class="cn.sycoder.service.impl.StudentServiceImpl"></bean>
        <bean id="xmlAspect" class="cn.sycoder.aspect.XmlAspect"></bean>
        <aop:aspectj-autoproxy/>
        <aop:config>
    <!--        配置切面类-->
            <aop:aspect ref="xmlAspect">
    <!--            配置切点-->
                <aop:pointcut id="pc" expression="execution(* cn.sycoder.service.*..*(..))"/>
    <!--            配置前置通知-->
                <aop:before method="before" pointcut-ref="pc"></aop:before>
    <!--            配置后置通知-->
                <aop:after method="after" pointcut-ref="pc"></aop:after>
    <!--            配置返回通知-->
                <aop:after-returning method="afterReturning" returning="obj" pointcut-ref="pc"></aop:after-returning>
    <!--            异常通知-->
                <aop:after-throwing method="afterThrowing" throwing="t" pointcut-ref="pc"></aop:after-throwing>
                
            </aop:aspect>
        </aop:config>
    </beans>
    

4.总结

  • 以后在公司使用注解的方式最流行,所以,xml 配置作为了解内容即可

五、Spring整合Mybatis

1.添加依赖

  • 添加依赖

    <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.2.17.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <!--            <scope>test</scope>-->
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.16</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.29</version>
            </dependency>
    <!--        spring 整合 mybatis 的包-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis-spring</artifactId>
                <version>1.3.0</version>
            </dependency>
    <!--        mybatis 包-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.6</version>
            </dependency>
    <!--        spring 操作 jdbc 包-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>4.3.29.RELEASE</version>
            </dependency>
        </dependencies>
    

2.配置类

2.1配置Spring配置类

  • 配置类

    @Configuration
    @ComponentScan("cn.sycoder")
    public class SpringConfig {
    }
    

2.2配置JDBC配置类

  • 配置类

    public class JdbcConfig {
        @Value("${jdbc.username}")
        private String username;
        @Value("${jdbc.password}")
        private String password;
        @Value("${jdbc.driverClassName}")
        private String driverClassName;
        @Value("${jdbc.url}")
        private String url;
        //配置连接池
        @Bean
        public DataSource dataSource(){
            DruidDataSource source = new DruidDataSource();
            source.setUsername(username);
            source.setPassword(password);
            source.setDriverClassName(driverClassName);
            source.setUrl(url);
            return source;
        }
    }
    

2.3添加数据库配置文件

  • 配置文件

    jdbc.username=root
    jdbc.password=123456
    jdbc.driverClassName=com.mysql.cj.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/spring
    
  • 导入到spring 中

    @Configuration
    @ComponentScan("cn.sycoder")
    @PropertySource("db.properties")
    @Import({JdbcConfig.class,MyBatisConfig.class})
    public class SpringConfig {
    }
    
  • 创建数据库和数据表

    create table account
    (
    	id bigint auto_increment
    		primary key,
    	money int null
    );
    

2.4创建Mybatis 配置类

  • 创建配置类

    public class MyBatisConfig {
    //    配置SqlSqlSessionFactoryBean
        @Bean
        public SqlSessionFactoryBean sessionFactory(DataSource dataSource){
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
            bean.setDataSource(dataSource);//设置数据源
            return bean;
        }
        
        @Bean
        public MapperScannerConfigurer mapperScannerConfigurer(){
            MapperScannerConfigurer configurer = new MapperScannerConfigurer();
            configurer.setBasePackage("cn.sycoder.mapper");
            return configurer;
        }
    }
    
  • 配置与 xml 配置的对比

    在这里插入图片描述

3.添加代码生成器

  • 拷贝代码生成器到项目中并且配置插件

  • 需要修改代码生成器的数据库连接信息

    <build>
            <plugins>
                <!--           引入 generator 插件-->
                <plugin>
                    <groupId>org.mybatis.generator</groupId>
                    <artifactId>mybatis-generator-maven-plugin</artifactId>
                    <version>1.3.2</version>
                    <configuration>
                        <verbose>true</verbose>
                        <overwrite>false</overwrite>
                    </configuration>
                    <dependencies>
                        <!--                    引入插件需要的依赖-->
                        <dependency>
                            <groupId>mysql</groupId>
                            <artifactId>mysql-connector-java</artifactId>
                            <version>8.0.29</version>
                        </dependency>
                    </dependencies>
                </plugin>
            </plugins>
        </build>
    

4.出现问题

  • class not find

    image-20221105143719643

  • 解决方案

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.6</version>
    </dependency>
    

    image-20221105143910125

  • 解决方案

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>4.3.29.RELEASE</version>
    </dependency>
    
  • 包之间的关系

    image-20221105144118678

六、Spring 事务管理

1.Spring 事务简介

  • 事务概述:保证数据库操作同时成功或者同时失败

  • Spring 事务的概述:在数据层保证数据库操作同时成功或者同时失败

2.转账案例分析

  • 转账肯定有一个业务方法:给转出用户减钱,给转入用户加钱
  • 要求:
    • 要么同时成功要么同时失败

2.1Spring 平台事务管理器

  • 提供规范接口

    public interface PlatformTransactionManager {
        TransactionStatus getTransaction(TransactionDefinition var1) throws TransactionException;
    
        void commit(TransactionStatus var1) throws TransactionException;
    
        void rollback(TransactionStatus var1) throws TransactionException;
    }
    
  • 方法

    • void commit(TransactionStatus var1):用于提交事务
    • void rollback(TransactionStatus var1):用户事务回滚
  • 具体实现:DataSourceTransactionManager 来实现的,通过DataSource dataSource 以 JDBC 事务的方式来控制事务

    image-20221105201148758

2.2转账案例分析

  • 业务分析

    • 在业务层需要保证事务的同时成功或者同时失败
    • 结果
      • 出现异常:张三转账给李四,比如中途出现问题,张三的钱和李四的钱应该不出现误差
      • 没有出现异常:张三转账给李四,没有出现问题,张三的钱正常减少,李四的钱正常增多
  • 提供service 方法

    public interface IAccountService {
        /**
         * 实现转账操作
         * @param srcId 转账人
         * @param deskId 接收人
         * @param money 转账金额
         */
        public void transfer(Long srcId,Long deskId,int money);
    }
    
  • 下载插件的官方地址 https://plugins.jetbrains.com/

  • 提供service 实现方法

    @Service
    public class AccountServiceImpl implements IAccountService {
    
        //会使用到 mapper
        @Autowired
        private AccountMapper mapper;
    
        public void transfer(Long srcId, Long deskId, int money) {
            mapper.outAccount(srcId,money);//转账扣钱
    
            mapper.inAccount(deskId,money);//接收转账钱
        }
    }
    
  • 提供 mapper 接口

    public interface AccountMapper {
        void outAccount(@Param("id") Long srcId, @Param("money") int money);
    
        void inAccount(@Param("id")Long deskId,@Param("money") int money);
    }
    
  • 提供 mapper.xml

    </update>
        <update id="outAccount">
        update account
        set money = money-#{money,jdbcType=INTEGER}
        where id = #{id,jdbcType=BIGINT}
        </update>
      <update id="inAccount">
        update account
        set money = money+#{money,jdbcType=INTEGER}
        where id = #{id,jdbcType=BIGINT}
      </update>
    
  • 测试

     @Test
        public void testMybatis(){
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
            IAccountService bean = context.getBean(IAccountService.class);
            bean.transfer(1L,2L,500);
    
        }
    

3.基于注解的方式实现

3.1@EnableTransactionManagement

  • @EnableTransactionManagement:用于开启事务支持的,直接添加到spring 配置类

  • 说明

    名称@EnableTransactionManagement
    位置配置类上方
    作用设置当前spring环境支持事务
  • 修改配置类

    @Configuration
    @ComponentScan("cn.sycoder")
    @PropertySource("db.properties")
    @Import({JdbcConfig.class,MyBatisConfig.class})
    //开启事务支持
    @EnableTransactionManagement
    public class SpringConfig {
    }
    

3.2@Transactional

  • @Transactional:为业务添加事务的

  • 说明

    名称@Transactional
    位置业务层接口上方,或者实现类上方,或者具体业务方法上方
    作用为当前的业务方法添加事务支持
  • 修改业务层

    • 业务方法上添加

      @Transactional
      public void transfer(Long srcId, Long deskId, int money) {
          mapper.outAccount(srcId,money);//转账扣钱
          System.out.println(1/0);
          mapper.inAccount(deskId,money);//接收转账钱
      }
      
    • 业务类上添加

      @Service
      @Transactional
      public class AccountServiceImpl implements IAccountService {
      
          //会使用到 mapper
          @Autowired
          private AccountMapper mapper;
      
      
          public void transfer(Long srcId, Long deskId, int money) {
              mapper.outAccount(srcId,money);//转账扣钱
              System.out.println(1/0);
              mapper.inAccount(deskId,money);//接收转账钱
          }
      }
      
    • 接口层添加

      @Transactional
      public interface IAccountService {
          /**
           * 实现转账操作
           * @param srcId 转账人
           * @param deskId 接收人
           * @param money 转账金额
           */
          public void transfer(Long srcId,Long deskId,int money);
      }
      

3.3配置事务管理

  • PlatformTransactionManager

  • 代码

    public class JdbcConfig {
        @Value("${jdbc.username}")
        private String username;
        @Value("${jdbc.password}")
        private String password;
        @Value("${jdbc.driverClassName}")
        private String driverClassName;
        @Value("${jdbc.url}")
        private String url;
        //配置连接池
        @Bean
        public DataSource dataSource(){
            DruidDataSource source = new DruidDataSource();
            source.setUsername(username);
            source.setPassword(password);
            source.setDriverClassName(driverClassName);
            source.setUrl(url);
            return source;
        }
    
        @Bean
        public PlatformTransactionManager transactionManager(DataSource dataSource){
            DataSourceTransactionManager manager = new DataSourceTransactionManager();
            manager.setDataSource(dataSource);
            return manager;
        }
    }
    

4.事务角色

  • 在没有开启Spring事务之前:两条语句分别开启两个事务 T1 和 T2

    • 如果同时成功,T1和T2都会正常提交
    • 如果T1正常,T2之前抛出异常,就会出现T1能够正常转账,但是T2收不到钱,因为不是同一个事务导致金钱异常
    public void transfer(Long srcId, Long deskId, int money) {
        mapper.outAccount(srcId,money);//转账扣钱
        System.out.println(1/0);
        mapper.inAccount(deskId,money);//接收转账钱
    }
    

    image-20221105210048391

  • 开启Spring 事务管理之后

    image-20221105210911281

    • 在转账 transfer 方法上加入 @Transactional 注解之后,该方法会新建一个事务T
    • 把 mapper 中 outAccount 事务 T1 加入到 事务T中,把 mapper 中 inAccount 事务 T2 也加入到事务T中
    • 通过 @Transactional 注解统一了 transfer 方法的事务保证转账和入账方法变成同一事务操作

4.1事务管理员&事务协调员

  • 事务管理员:发起新事务,使用 @Transactional 注解开启事务
  • 事务协调员:加入新事务,保证多个事务变成同一事务下的操作

5.@Transactional 属性

5.1readOnly

  • 概述:表示只读,没有写操作。可以通过这个属性告诉数据库我们没有写操作,从而数据库可以针对只读sql做优化操作

  • 使用

    @Transactional(readOnly = true)
    public Account selectById(Long id){
        return mapper.selectByPrimaryKey(id);
    }
    
  • 如果对于有写操作的使用这个属性,会报如下错误

    在这里插入图片描述

5.2timeout

  • 超时概述:事务再执行的时候,由于某些原因卡住,长时间占用数据库资源。此时很可能程序sql有问题,希望撤销事务,能够让事务结束,释放资源,即超时回滚。

  • 默认值是-1.-1表示用不回滚,单位是秒

  • int timeout() default -1;
    
  • 使用

    @Transactional(readOnly = true,timeout = 1)
        public Account selectById(Long id){
            try {
                Thread.sleep(10000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return mapper.selectByPrimaryKey(id);
        }
    

    在这里插入图片描述

5.3rollbackFor&rollbackForClassName

  • 回滚概述:回滚策略,希望对于什么样的异常回顾

  • 注意:并不是所有的异常 Spring 都会回滚,Spring 只对 Error 异常和 RuntimeException 异常回滚

  • 使用

    @Transactional(rollbackFor = IOException.class)
        public void transfer(Long srcId, Long deskId, int money) throws IOException {
            mapper.outAccount(srcId,money);//转账扣钱
            if(true){
                throw new IOException("");
            }
            mapper.inAccount(deskId,money);//接收转账钱
        }
    
    @Transactional(rollbackForClassName = "IOException")
        public void transfer(Long srcId, Long deskId, int money) throws IOException {
            mapper.outAccount(srcId,money);//转账扣钱
            if(true){
                throw new IOException("");
            }
            mapper.inAccount(deskId,money);//接收转账钱
        }
    

5.4noRollbackFor&noRollbackForClassName

  • 不会滚概述:出现这个异常不回滚

  • 使用

    @Transactional(noRollbackFor = ArithmeticException.class)
        public void transfer(Long srcId, Long deskId, int money) throws IOException {
            mapper.outAccount(srcId,money);//转账扣钱
            System.out.println(1/0);
            mapper.inAccount(deskId,money);//接收转账钱
        }
    
    @Transactional(noRollbackForClassName = "ArithmeticException")
        public void transfer(Long srcId, Long deskId, int money) throws IOException {
            mapper.outAccount(srcId,money);//转账扣钱
            System.out.println(1/0);
            mapper.inAccount(deskId,money);//接收转账钱
        }
    

5.5isolation

  • 概述:设置事务隔离级别;

  • 如果不记得事务隔离级别,回去复习一下我讲的MySql

    • DEFAULT :默认隔离级别, 会采用数据库的隔离级别
    • READ_UNCOMMITTED : 读未提交
    • READ_COMMITTED : 读已提交
    • REPEATABLE_READ : 重复读取
    • SERIALIZABLE: 串行化
  • 使用

    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public Account selectById(Long id) throws IOException {
        return mapper.selectByPrimaryKey(id);
    }
    

5.6propagation

  • 事务传播行为:事务协调员对事务管理员所携带的事务的处理态度

  • 说明

    传播属性说明
    REQUIRED外围方法会开启新事务,内部方法会加入到外部方法的事务中
    SUPPORTS外围方法没有事务,则内部方法不执行事务
    MANDATORY使用当前事务,如果当前没有事务就抛异常
    REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起
    NOT_SUPPORTED不支持事务
    NEVER不支持事务,如果存在事务还会抛异常
    NESTED如果当前存在事务,则在嵌套事务内执行,如果不存在,执行REQUIRED类似操作
  • 实操

    • REQUIRED:T1和T2会加入T中

      @Transactional(propagation = Propagation.REQUIRED)//事务T
      public void transfer(Long srcId, Long deskId, int money) throws IOException {
          mapper.outAccount(srcId,money);//转账扣钱 //事务T1
          System.out.println(1/0);
          mapper.inAccount(deskId,money);//接收转账钱 //事务T2
      }
      
    • SUPPORTS:外围没事务,所以内部只执行自己的事务,T1 和 T2 单独执行

      public void transfer(Long srcId, Long deskId, int money) throws IOException {
              mapper.outAccount(srcId,money);//转账扣钱//事务T1
              System.out.println(1/0);
              mapper.inAccount(deskId,money);//接收转账钱//事务T2
          }
      
    • REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起

       @Transactional(propagation=Propagation.REQUIRES_NEW)
          public void outAccount(Long id,  int money){
              mapper.outAccount(id,money);//转账扣钱
          }
      
          @Transactional(propagation=Propagation.REQUIRES_NEW)
          public void inAccount(Long id,  int money){
              mapper.inAccount(id,money);//转账扣钱
          }
      
      public void transfer(Long srcId, Long deskId, int money) throws IOException {
          outAccount(srcId,money);
          inAccount(deskId,money);
          throw new RuntimeException();
      
      }
      
      • 这种情况上面一条语句能够正常执行
      @Transactional(propagation = Propagation.REQUIRES_NEW)
          public void outAccount(Long id, int money) {
              mapper.outAccount(id, money);//转账扣钱
          }
      
          @Transactional(propagation = Propagation.REQUIRES_NEW)
          public void inAccount(Long id, int money) {
              if (true)
                  throw new RuntimeException();
              mapper.inAccount(id, money);//转账扣钱
          }
      

6.基于XML事务

  • 导入依赖

    <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.2.17.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aspects</artifactId>
                <version>5.2.17.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <!--            <scope>test</scope>-->
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.16</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.29</version>
            </dependency>
            <!--        spring 整合 mybatis 的包-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis-spring</artifactId>
                <version>1.3.0</version>
            </dependency>
            <!--        mybatis 包-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.6</version>
            </dependency>
            <!--        spring 操作 jdbc 包-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>4.3.29.RELEASE</version>
            </dependency>
        </dependencies>
    
  • 配置文件

    <?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:aop="http://www.springframework.org/schema/aop"
           xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/tx
     http://www.springframework.org/schema/tx/spring-tx.xsd
     http://www.springframework.org/schema/aop
     http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    
        <bean id="accountService" class="cn.sycoder.service.impl.AccountServiceImpl">
    <!--        <property name="mapper" ref="mapper"/>-->
        </bean>
        <!--    <bean id="mapper" class="cn.sycoder.mapper.AccountMapper"></bean>-->
    
        <aop:config>
            <aop:advisor advice-ref="tx" pointcut="execution(* cn.sycoder.service.impl.*.*(..))"></aop:advisor>
        </aop:config>
    
        <tx:advice id="tx" transaction-manager="txManager">
            <tx:attributes>
                <tx:method name="get*" read-only="true"/>
            </tx:attributes>
        </tx:advice>
    
        <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!-- (this dependency is defined somewhere else) -->
            <property name="dataSource" ref="dataSource"/>
        </bean>
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="${jdbc.driverClassName}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </bean>
        <context:property-placeholder location="db.properties"/>
    
    
    </beans>
    
  • 配置详解

    • 引入db.properties

      <context:property-placeholder location="db.properties"/>
      
    • 配置连接池

      <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
          <property name="driverClassName" value="${jdbc.driverClassName}"/>
          <property name="url" value="${jdbc.url}"/>
          <property name="username" value="${jdbc.username}"/>
          <property name="password" value="${jdbc.password}"/>
      </bean>
      
    • 配置事务管理器

      <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
          <property name="dataSource" ref="dataSource"/>
      </bean>
      
    • 配置 aop 事务增强

      <aop:config>
              <aop:advisor advice-ref="tx" pointcut="execution(* cn.sycoder.service.impl.*.*(..))"/>
      </aop:config>
              
      <tx:advice id="tx" transaction-manager="txManager">
          <tx:attributes>
              <tx:method name="get*" read-only="true"/>
          </tx:attributes>
      </tx:advice>    
      
  • 注意:如果你还想通过 xml 配置 mybatis ,那么你还需要把 mybatis 配置文件搞一份过来,通过xml 配置好 mybatis 之后,然后再获取 sqlSessionFactory 去获取 mapper 文件

  • 注意:spring 是面试重头戏,所以,你需要花时间认真巩固和复习,ioc 和 di 特别是对于常用注解,以及事务机制,aop 等都很爱问。
    me=“username” value=“ j d b c . u s e r n a m e " / > < p r o p e r t y n a m e = " p a s s w o r d " v a l u e = " {jdbc.username}"/> <property name="password" value=" jdbc.username"/><propertyname="password"value="{jdbc.password}”/>

    <context:property-placeholder location=“db.properties”/>

    ```
  • 配置详解

    • 引入db.properties

      <context:property-placeholder location="db.properties"/>
      
    • 配置连接池

      <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
          <property name="driverClassName" value="${jdbc.driverClassName}"/>
          <property name="url" value="${jdbc.url}"/>
          <property name="username" value="${jdbc.username}"/>
          <property name="password" value="${jdbc.password}"/>
      </bean>
      
    • 配置事务管理器

      <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
          <property name="dataSource" ref="dataSource"/>
      </bean>
      
    • 配置 aop 事务增强

      <aop:config>
              <aop:advisor advice-ref="tx" pointcut="execution(* cn.sycoder.service.impl.*.*(..))"/>
      </aop:config>
              
      <tx:advice id="tx" transaction-manager="txManager">
          <tx:attributes>
              <tx:method name="get*" read-only="true"/>
          </tx:attributes>
      </tx:advice>    
      
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值