当前位置: 首页 > news >正文

第五章:Spring下

第五章:Spring下

5.1:AOP

  1. 场景模拟

    • 创建一个新的模块,spring_proxy_10,并引入下面的jar包。

      <packaging>jar</packaging><dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency>
      </dependencies>
      
    • 声明计算器接口Calculator,包含加减乘除的抽象方法

      package com.wang.proxy;public interface Calculator {int add(int i, int j);int sub(int i, int j);int mul(int i, int j);int div(int i, int j);
      }
      
    • 创建带日志功能的实现类

      package com.wang.proxy;public class CalculatorImpl implements Calculator {@Overridepublic int add(int i, int j) {System.out.println("日志, 方法: add, 参数: " + i + "," + j);int result = i + j;System.out.println("方法内部,result: " + result);System.out.println("日志, 方法: add, 结果: " + result);return result;}@Overridepublic int sub(int i, int j) {System.out.println("日志, 方法: sub, 参数: " + i + "," + j);int result = i - j;System.out.println("方法内部,result: " + result);System.out.println("日志, 方法: sub, 结果: " + result);return result;}@Overridepublic int mul(int i, int j) {System.out.println("日志, 方法: mul, 参数: " + i + "," + j);int result = i * j;System.out.println("方法内部,result: " + result);System.out.println("日志, 方法: mul, 结果: " + result);return result;}@Overridepublic int div(int i, int j) {System.out.println("日志, 方法: div, 参数: " + i + "," + j);int result = i / j;System.out.println("方法内部,result: " + result);System.out.println("日志, 方法: div, 结果: " + result);return result;}
      }
      
    • 现有代码缺陷

      1. 对核心业务功能有干扰,导致程序员在开发核心业务功能时分散了精力。
      2. 附加功能分散在各个业务功能方法中,不利于统一维护。
    • 解决思路

      解耦。我们需要把附加功能从业务功能代码中抽取出来。

  2. 代理模式

    • 概念

      ​ 二十三中设计模式中的一种,属于结构性模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时限调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。

      ​ 使用代理之前:
      在这里插入图片描述

      使用代理之后:
      在这里插入图片描述

      代理:将非核心逻辑剥离出来以后,封装这些非核心逻辑的类、对象、方法。

      目标:被代理"套用"了非核心逻辑代码的类、对象、方法。

    • 静态代理

      // 创建静态代理类
      package com.wang.proxy;public class CalculatorStaticProxy implements Calculator {private CalculatorImpl target;public CalculatorStaticProxy(CalculatorImpl target) {this.target = target;}@Overridepublic int add(int i, int j) {System.out.println("日志, 方法: add, 参数: " + i + "," + j);int result = target.add(i, j);System.out.println("日志, 方法: add, 结果: " + result);return result;}@Overridepublic int sub(int i, int j) {System.out.println("日志, 方法: sub, 参数: " + i + "," + j);int result = target.sub(i, j);System.out.println("日志, 方法: sub, 结果: " + result);return result;}@Overridepublic int mul(int i, int j) {System.out.println("日志, 方法: mul, 参数: " + i + "," + j);int result = target.mul(i, j);System.out.println("日志, 方法: mul, 结果: " + result);return result;}@Overridepublic int div(int i, int j) {System.out.println("日志, 方法: div, 参数: " + i + "," + j);int result = target.div(i, j);System.out.println("日志, 方法: div, 结果: " + result);return result;}
      }
      

      ​ 静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵性。就拿日志功能来说,将来其他地方也需要附加日志,那还得声明跟多静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。

    • 动态代理

      // 生产代理对象的工厂类(JDK动态代理)
      package com.wang.proxy;// 动态代理有两种// 1. JDK动态代理: 要求必须有接口, 最终生产的代理类和目标类实现相同的接口, 在com.sun.proxy包下, 类名为$proxy2// 2. cglib动态代理: 最终生产的代理类会继承目标类,并且和目标在相同的包下
      public class ProxyFactory {private Object target;public ProxyFactory(Object target) {this.target = target;}public Object getProxy() {/*** ClassLoader classLoader: 指定加载动态生成的代理类的类加载器* Class[] interfaces: 获取目标对象的所有接口的class对象的数组* InvocationHandler h: 设置代理类中的抽象方法如何重写*/ClassLoader classLoader = this.getClass().getClassLoader();Class<?>[] interfaces = target.getClass().getInterfaces();InvocationHandler h = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("日志, 方法: " + method.getName() + ", 参数: " + Arrays.toString(args));// Object proxy: 表示代理对象// Method method: 表示要执行的方法// Object[] args: 表示要执行的方法到的参数列表Object   result = method.invoke(target, args);System.out.println("日志, 方法: " + method.getName() + ", 结果: " + result);return result;}};return Proxy.newProxyInstance(classLoader, interfaces, h);}
      }
      
  3. AOP概念及相关术语

    • 概述

      AOP(Aspect Oriemeted Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式来实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。

    • 相关术语

      1. 横切关注点

        从每个方法中抽取出来的同一类非核心业务。

      2. 通知

        每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方式。

        • 前置通知:在被代理的目标方法执行。
        • 返回通知:在被代理的目标方法成功结束后执行(寿终正寝)。
        • 异常通知:在被代理的目标方法异常结束后执行(死于非命)。
        • 后置通知:在被代理的目标方法最终结束后执行(盖棺定论)。
        • 环绕通知:使用try ... catch ... finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置。
      3. 切面

        封装通知方法的类。

      4. 目标

        被代理的目标对象。

      5. 代理

        向目标对象应用通知之后创建的代理对象。

      6. 连接点

        ​ 这也是一个纯逻辑概念,不是语法定义的。把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。

      7. 切入点

        定位连接点的方式。SpringAOP技术可以通过切入点定位到特定的连接点。切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件。

    • 作用

      1. 简化代码:把方法中固定位置的重复代码抽取出来,让被抽取的方法更专注于自己的核心功能。
      2. 代码增强:把特定的功能封装到切面中,看哪里有需要,就网上套,被套用了切面逻辑的方法就被切面增强了。
  4. 基于注解的AOP

    • 准备工作

      1. 创建模块

        创建一个新的模块,spring_aop_11,并引入下面的jar包。

        <packaging>jar</packaging><dependencies><!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.1</version></dependency><!-- junit测试 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><!-- spring-aspects会帮我们传递过来aspectjweaver --><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.3.1</version></dependency>
        </dependencies>
        
      2. 导入spring_proxy_10Calculator接口和CalculatorImpl实现类并添加@Component注解。

      3. 创建切面类

        package com.wang.aop.annotation;@Component
        @Aspect
        public class LoggerAspect {
        }
        
      4. spring的配置文件添加包扫描

        <!-- 创建aop-annotation.xml配置文件-->
        <!--AOP的注意事项:1. 切面类和目标类都需要交给IOC容器管理2. 切面类都必须通过@Aspect注解标识为一个切面
        -->
        <context:component-scan base-package="com.wang.aop.annotation"></context:component-scan><!-- 开启基于注解的AOP -->
        <aop:aspectj-autoproxy />
        
    • 前置通知

      @Before("execution(public int com.wang.aop.annotation.CalculatorImpl.add(int, int))")
      public void beforeAdviceMethod() {System.out.println("LoggerAspect, 前置通知");
      }
      
      @Test
      public void testAOPByAnnotation() {ApplicationContext ioc = new ClassPathXmlApplicationContext("aop-annotation.xml");Calculator calculator = ioc.getBean(Calculator.class);calculator.add(1, 1);
      }
      
    • 各种通知

      1. 前置通知:使用@Before()注解标识,在被代理的目标方法执行。
      2. 返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行(寿终正寝)。
      3. 异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行(死于非命)。
      4. 后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行(盖棺定论)。
      5. 环绕通知:使用@Around注解标识,使用try .. catch .. finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置。
    • 各种通知的执行顺序

      1. Spring版本5.3.x以前:

        前置通知 ——> 目标操作 ——> 后置通知 ——> 返回通知或异常通知。

      2. Spring版本5.3.x以后:

        前置通知 ——> 目标操作 ——> 返回通知或异常通知 ——> 后置通知。

    • 切入点表达式

      设置在标识通知的注解的value属性中。*:表示任意,..表示任意的参数列表。

      @Before("execution(* com.wang.aop.annotation.*.*(..))")
      
    • 重用切入点表达式

      @Pointcut("execution(* com.wang.aop.annotation.CalculatorImpl.*(..))")
      public void pointCut() {}
      
    • 获取连接点的信息

      在通知方法的参数位置,设置JoinPoint类型的参数,就可以获取连接点所对应方法的信息。

      // 获取连接点所对应的签名信息
      Signature signature = joinPoint.getSignature();
      // 获取连接点所对应方法的参数
      Object[] args = joinPoint.getArgs();
      
    • 获取目标方法的返回值

      // 只需要通过@AfterReturning注解的returning属性,就可以将通知方法的某个参数指定为接收目标对象方法的返回值的参数
      @AfterReturning(value = "pointCut()", returning = "result")
      public void afterReturnAdviceMethod(JoinPoint joinPoint, Object result) {Signature signature = joinPoint.getSignature();System.out.println("LoggerAspect, 方法: " + signature.getName() + ", 结果: " + result);
      }
      
    • 获取目标方法的异常

      // 只需要通过AfterThrowing注解的throwing属性,就可以将通知方法的某个参数指定为接收目标对象方法出现的异常的参数
      @AfterThrowing(value = "pointCut()", throwing = "ex")
      public void afterThrowingAdviceMethod(JoinPoint joinPoint, Exception ex){Signature signature = joinPoint.getSignature();System.out.println("LoggerAspect, 方法: " + signature.getName() + ", 异常通知: " + ex);
      }
      
    • 环绕通知

      @Around("pointCut()")
      public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint) {Object result = null;try {System.out.println("环绕通知 --> 前置通知");// 表示目标对象方法的执行result = joinPoint.proceed();System.out.println("环绕通知 --> 返回通知");} catch (Throwable throwable) {throwable.printStackTrace();System.out.println("环绕通知 --> 异常通知");} finally {System.out.println("环绕通知 --> 后置通知");}return result;
      }
      
    • 切面的优先级

      可以通过@Order注解的value属性设置优先级,默认值为Integer的最大值,@Order注解的value属性值越小,优先级越高。

  5. 基于XMLAOP

    <!-- 复用基于AOP注解环境的方法,删除方法上面的注解,创建aop-xml.xml配置文件-->
    <!-- 扫描组件 -->
    <context:component-scan base-package="com.wang.aop.xml"></context:component-scan><aop:config><!-- 一个公共的切入点表达式 --><aop:pointcut id="pointCut" expression="execution(* com.wang.aop.xml.CalculatorImpl.*(..))"/><!-- 将IOC容器中的某个bean设置为切面 --><aop:aspect ref="loggerAspect"><aop:before method="beforeAdviceMethod" pointcut-ref="pointCut"></aop:before><aop:after method="afterAdviceMethod" pointcut-ref="pointCut"></aop:after><aop:after-returning method="afterReturnAdviceMethod" pointcut-ref="pointCut" returning="result"></aop:after-returning><aop:after-throwing method="afterThrowingAdviceMethod" pointcut-ref="pointCut" throwing="ex"></aop:after-throwing><aop:around method="aroundAdviceMethod" pointcut-ref="pointCut" ></aop:around></aop:aspect><aop:aspect ref="validateAspect" order="1"><aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before></aop:aspect>
    </aop:config>
    

5.2:声明式事务

  1. JdbcTemplate

    • 简介

      Spring框架对JDBC进行封装,使用JdbcTemplate方便实现对数据库操作。

    • 准备工作

      1. 创建模块

        创建一个新的模块,spring_transaction_12,并引入下面的jar包。

        <packaging>jar</packaging><dependencies><!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.1</version></dependency><!-- Spring 持久化层支持jar包 --><dependency><groupId>org.springframework</groupId><artifactId>spring-orm</artifactId><version>5.3.1</version></dependency><!-- Spring 测试相关 --><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.3.1</version></dependency><!-- junit测试 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><!-- MySQL驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.16</version></dependency><!-- 数据源 --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.0.31</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.3.1</version></dependency>
        </dependencies>
        
      2. 创建jdbc.properties

        jdbc.driver=com.mysql.cj.jdbc.Driver
        jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
        jdbc.username=root
        jdbc.password=abc123
        
      3. 配置spring的配置文件

        <!--创建spring-jdbc.xml配置文件-->
        <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="${jdbc.driver}"></property><property name="url" value="${jdbc.url}"></property><property name="username" value="${jdbc.username}"></property><property name="password" value="${jdbc.password}"></property>
        </bean>
        <!-- 装配JdbcTemplate -->
        <bean class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"></property>
        </bean>
        
    • 测试

      1. 实现添加功能

        package com.wang.test;//指定当前测试类在Spring的测试环境中执行,此时就可以通过注入的方法直接获取IOC容器中bean
        @RunWith(SpringJUnit4ClassRunner.class)
        // 设置Spring测试环境的配置文件
        @ContextConfiguration("classpath:spring-jdbc.xml")
        public class JdbcTemplateTest {@Autowiredprivate JdbcTemplate jdbcTemplate;@Testpublic void testInsert() {String sql = "insert into t_user values(null, ?, ?, ?, ?, ?)";jdbcTemplate.update(sql, "root", "123", 23, "女", "123@qq.com");}
        }
        
      2. 实现查询功能

        自行创建User实体类。

        // 查询一条数据为实体类对象
        @Test
        public void testGetUserById() {String sql = "select * from t_user where id = ?";User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), 1);System.out.println(user);
        }
        // 查询多条数据为list集合
        @Test
        public void testGetAllUser() {String sql = "select * from t_user";List<User> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));list.forEach(System.out::println);
        }
        // 查询单行单列的值
        @Test
        public void testGetCount() {String sql = "select count(*) from t_user";Integer count = jdbcTemplate.queryForObject(sql, Integer.class);System.out.println(count);
        }
        
  2. 声明式事务概念

    • 编程式事务

      事务概念的相关操作全部通过自己编写代码来实现。

      Connection conn = null;
      try {// 开启事务,关闭事务的自动提交conn.setAutoCommit(false);// 核心操作// 提交事务conn.commit();
      } catch(Exception e) {// 回滚事务conn.rollBack();
      } finally {// 释放资源conn.close();
      }
      

      编程式的实现方式存在缺陷:

      1. 细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
      2. 代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。
    • 声明式事务

      ​ 既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。封装起来,我们只需要在配置文件中进行简单的配置即可完成操作。

      ​ 好处1:提高开发效率。

      ​ 好处2:消除了冗余的代码。

      ​ 好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性能等各个方面的优化。

  3. 基于注解的声明式事务

    • 准备工作

      1. 创建Spring配置文件:复制spring-jdbc.xml重命名为tx-annotation.xml文件。

        <context:component-scan base-package="com.wang"></context:component-scan>
        
      2. 创建表并添加数据

        CREATE TABLE t_book(book_id INT(11) NOT NULL AUTO_INCREMENT COMMENT '主键',book_name VARCHAR(20) DEFAULT NULL COMMENT '图书名称',price INT(11) DEFAULT NULL COMMENT '价格',stock INT(10) UNSIGNED DEFAULT NULL COMMENT '库存(无符号)',PRIMARY KEY (`book_id`)
        ) ENGINE=INNODB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
        INSERT INTO t_book(book_id,book_name,price,stock) VALUES (1,'斗破苍穹',80,100),(2,'斗罗大陆',50,100);CREATE TABLE t_user(user_id INT(11) NOT NULL AUTO_INCREMENT COMMENT '主键',username VARCHAR(20) DEFAULT NULL COMMENT '用户名',balance INT(10) UNSIGNED DEFAULT NULL COMMENT '余额(无符号)',PRIMARY KEY (`user_id`)
        )ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
        INSERT INTO `t_user`(`user_id`,`username`,`balance`) VALUES (1,'admin',50);
        
      3. 无事务情况

        • dao层:

          package com.wang.dao;public interface BookDao {// 查询图书价格Integer getPriceByBookId(Integer bookId);// 更新图书的库存void updateStock(Integer bookId);// 更新用户的余额void updateBalance(Integer userId, Integer price);
          }
          
          package com.wang.dao.impl;@Repository
          public class BookDaoImpl implements BookDao {@Autowiredprivate JdbcTemplate jdbcTemplate;// 查询图书价格@Overridepublic Integer getPriceByBookId(Integer bookId) {String sql = "select price from t_book where book_id = ?";return jdbcTemplate.queryForObject(sql, Integer.class, bookId);}// 更新图书的库存@Overridepublic void updateStock(Integer bookId) {String sql = "update t_book set stock = stock - 1 where book_id = ?";jdbcTemplate.update(sql, bookId);}// 更新用户的余额@Overridepublic void updateBalance(Integer userId, Integer price) {String sql = "update t_user balance = balance - ? where user_id = ?";jdbcTemplate.update(sql, price, userId);}
          }
          
        • service层:

          package com.wang.service;public interface BookService {// 买书void buyBook(Integer bookId, Integer userId);
          }
          
          package com.wang.service.impl;@Service
          public class BookServiceImpl implements BookService {@Autowiredprivate BookDao bookDao;@Overridepublic void buyBook(Integer bookId, Integer userId) {Integer price = bookDao.getPriceByBookId(bookId);bookDao.updateStock(bookId);bookDao.updateBalance(userId, price);}
          }
          
        • controller层:

          package com.wang.controller;@Controller
          public class BookController {@Autowiredprivate BookService bookService;public void buyBook(Integer bookId, Integer userId) {bookService.buyBook(bookId, userId);}
          }
          
        • 测试

          @RunWith(SpringJUnit4ClassRunner.class)
          @ContextConfiguration("classpath:tx-annotation.xml")
          public class TxByAnnotationTest {@Autowiredprivate BookController bookController;@Testpublic void testBuyBook() {bookController.buyBook(1, 1);}
          }
          
      4. 声明式事务

        • 添加事务配置

          <!-- 配置事务管理器 -->
          <bean id="transactionManager"   class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"></property>
          </bean><!-- 开启事务的注解驱动:将使用@Transactional注解锁标识的方法或类中所有的方法使用事务进行管理transaction-manager属性的默认值是transactionManager,若事务管理器bean的id是这个默认值,能省略此属性
          -->
          <tx:annotation-driven transaction-manager="transactionManager" />
          

          在这里插入图片描述

        • 添加事务注解

          ​ 因为service表示业务逻辑层,一个方法表示完成一个功能,因此处理事务一般在service层处理,在BookServiceImplbuyBook方法添加注解@Transactional

          ​ 若@Transactional注解标识在方法上,该方法就会被事务管理,若@Transactional注解标识在类上,则类中所有的方法都会被事务管理。

        • 观察结果

          ​ 由于使用了Spring的声明式事务,更新库存和更新余额都没有执行。

      5. 事务属性:只读

        • 介绍

          ​ 对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作,这样数据库就能够针对查询操作进行优化。

        • 使用方式

          // 默认值为false
          @Transactional(readOnly = true)
          
      6. 事务属性:超时

        • 介绍

          ​ 事务执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题。

          ​ 此时这个很可能出问题的程序应该被回滚,撤销它已经做的操作,事务结束,把资源让出来,让其他正常程序可以执行。概括来说就是一句话:超时回滚,释放资源。

        • 使用方式

          // 单位是秒
          @Transactional( timeout = 3)
          
      7. 事务属性:回滚策略

        • 介绍:

          声明式事务默认只针对运行时异常回滚,编译时异常不会滚。可以通过Transactional中相关属性设置回滚策略。

          1. rollbackFor属性:会造成回滚的异常,需要设置一个Class类型的对象。
          2. rollbackForClassName属性:会造成回滚的异常,需要设置一个字符串类型的全类名。
          3. noRollbackFor属性::不会造成回滚的异常,需要设置一个Class类型的对象。
          4. noRollbackForClassName属性:不会造成回滚的异常,需要设置一个字符串类型的全类名。
      8. 事务属性:事务隔离级别

        • 介绍

          ​ 数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。

        • 使用方式

          @Transactional( isolation = Isolation.DEFAULT) // 使用数据库默认的隔离级别
          @Transactional( isolation = Isolation.READ_UNCOMMITTED) // 读未提交
          @Transactional( isolation = Isolation.READ_COMMITTED) // 读已提交
          @Transactional( isolation = Isolation.REPEATABLE_READ) // 可重复读
          @Transactional( isolation = Isolation.SERIALIZABLE) // 串行化
          
      9. 事务属性:事务传播行为

        • 介绍

          当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。

        • 测试

          1. 创建接口CheckoutService和其实现类CheckoutServiceImpl

            package com.wang.service;public interface CheckoutService {// 结账void checkout(Integer userId, Integer[] bookIds);
            }
            
            package com.wang.service.impl;@Service
            public class CheckoutServiceImpl implements CheckoutService {@Autowiredprivate BookService bookService;@Override@Transactionalpublic void checkout(Integer userId, Integer[] bookIds) {for(Integer bookId: bookIds) {bookService.buyBook(bookId, userId);}}
            }
            
          2. BookController中添加方法

            @Autowired
            private CheckoutService checkoutService;public void checkout(Integer userId, Integer[] bookIds) {checkoutService.checkout(userId, bookIds);
            }
            
          3. BookServiceImpl中的buyBook方法上面的@Transactional注解添加propagation属性

            // 若添加了propagation=Propagation.REQUIRES_NEW则表示结账的事务以buyBook的事务为主【能买一本书是一本】
            // 若没有添加, 使用了默认值则表示以结账自己的事务为主【必须把账单里的所有书都买了才行】
            @Transactional(propagation = Propagation.REQUIRES_NEW)
            
  4. 基于XML的声明式事务

    <!--创建tx-xml.xml配置文件, 参考基于注解的声明式事务 -->
    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"></property>
    </bean><!-- 配置事务通知 -->
    <tx:advice id="tx" transaction-manager="transactionManager"><tx:attributes><!-- name属性:指定方法名,可以使用星号代表多个字符,若下面的方法名在类中没有匹配的方法,则无法进行事务管理 --><tx:method name="buyBook"/></tx:attributes>
    </tx:advice><aop:config><aop:advisor advice-ref="tx" pointcut="execution(* com.wang.service.impl.*.* (..))"></aop:advisor>
    </aop:config>
    

相关文章:

第五章:Spring下

第五章&#xff1a;Spring下 5.1&#xff1a;AOP 场景模拟 创建一个新的模块&#xff0c;spring_proxy_10&#xff0c;并引入下面的jar包。 <packaging>jar</packaging><dependencies><dependency><groupId>junit</groupId><artifactI…...

在CSDN学Golang云原生(Kubernetes基础)

一&#xff0c;k8s集群安装和升级 安装 Golang K8s 集群可以参照以下步骤&#xff1a; 准备环境&#xff1a;需要一组 Linux 服务器&#xff0c;并在每台服务器上安装 Docker 和 Kubernetes 工具。初始化集群&#xff1a;使用 kubeadm 工具初始化一个 Kubernetes 集群。例如&…...

给APK签名—两种方式(flutter android 安装包)

前提&#xff1a;给未签名的apk签名&#xff0c;可以先检查下apk有没有签名 通过命令行查看&#xff1a;打开终端或命令行界面&#xff0c;导入包含APK文件的目录&#xff0c;并执行以下命令&#xff1a; keytool -printcert -jarfile your_app.apk 将 your_app.apk替换为要检查…...

观察者模式、中介者模式和发布订阅模式

观察者模式 定义 观察者模式定义了对象间的一种一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都将得到通知&#xff0c;并自动更新 观察者模式属于行为型模式&#xff0c;行为型模式关注的是对象之间的通讯&#xff0c;观察者模式…...

PHP-Mysql图书管理系统--【白嫖项目】

强撸项目系列总目录在000集 PHP要怎么学–【思维导图知识范围】 文章目录 本系列校训本项目使用技术 首页phpStudy 设置导数据库后台的管理界面数据库表结构项目目录如图&#xff1a;代码部分&#xff1a;主页的head 配套资源作业&#xff1a; 本系列校训 用免费公开视频&am…...

网络传输层协议:UDP和TCP

背景知识 再谈端口号 端口号(Port)标识了一个主机上进行通信的不同的应用程序&#xff1b; 在TCP/IP协议中, 用 "源IP", "源端口号", "目的IP", "目的端口号", "协议号" 这样一个五元组来标识一个通信(可以通过 netstat -…...

ElementUI Select选择器如何根据value值显示对应的label

修改前效果如图所示&#xff0c;数据值状态应显示为可用&#xff0c;但实际上仅显示了状态码1&#xff0c;并没有显示其对应的状态信息。在排查了数据类型对应关系问题后&#xff0c;并没有产生实质性影响&#xff0c;只好对代码进行了如下修改。 修改前代码&#xff1a; <…...

Kotlin 内联函数语法之let、apply、also、run、with的用法与详解

一、介绍 kotlin的语法千奇百怪&#xff0c;今天我们将介绍项目中频率使用比较高的几个内联函数。 二、什么叫内联函数&#xff1f; 内联函数 的语义很简单&#xff1a;把函数体复制粘贴到函数调用处 。使用起来也毫无困难&#xff0c;用 inline关键字修饰函数即可。 语法&a…...

Swift 中如何判断是push 过来的页面 还是present过来的 页面

在 Swift 中&#xff0c;可以通过检查当前视图控制器的 presentingViewController 属性来判断是通过 push 过来的页面还是 present 过来的页面。 下面是一个示例代码&#xff0c;展示如何判断是通过 push 还是 present 过来的页面&#xff1a; if let presentingViewControll…...

基于K8s环境·使用ArgoCD部署Jenkins和静态Agent节点

今天是「DevOps云学堂」与你共同进步的第 47天 第⑦期DevOps实战训练营 7月15日已开营 实践环境升级基于K8s和ArgoCD 本文节选自第⑦期DevOps训练营 &#xff0c; 对于训练营的同学实践此文档依赖于基础环境配置文档&#xff0c; 运行K8s集群并配置NFS存储。实际上只要有个K8s集…...

874. 模拟行走机器人

874. 模拟行走机器人 机器人在一个无限大小的 XY 网格平面上行走&#xff0c;从点 (0, 0) 处开始出发&#xff0c;面向北方。该机器人可以接收以下三种类型的命令 commands &#xff1a; -2 &#xff1a;向左转 90 度-1 &#xff1a;向右转 90 度1 < x < 9 &#xff1a;…...

【Linux】- RPM 与 YUM

RPM 与 YUM 1.1 rpm 包的管理1.2 rpm 包的简单查询指令1.3 rpm 包的其它查询指令&#xff1a;1.4 卸载 rpm 包&#xff1a;2.1 安装 rpm 包3.1 yum3.2 yum 的基本指令3.3 安装指定的 yum 包3.4 yum 应用实例&#xff1a; 1.1 rpm 包的管理 介绍 rpm 用于互联网下载包的打包及安…...

Visual Studio 2015编译器 自动生成 XXX_EXPORTS宏

XXX_EXPORTS宏 XXX_EXPORTS宏是由Visual Studio 2015编译器自动生成的。这个宏用于标识当前项目是一个导出符号的动态链接库&#xff08;DLL&#xff09;项目。在使用Visual Studio 2015创建Win32项目时&#xff0c;编译器会自动添加这个宏到项目的预定义宏中。 这个宏的作用…...

HTML5的应用现状与发展前景

HTML5&#xff0c;作为Web技术的核心&#xff0c;已经深深地改变了我们看待和使用Web的方式。它不仅提供了数不尽的新特性和功能&#xff0c;还使得Web设计和开发更加互动、更加直观。这篇文章将探讨HTML5的当前应用现状&#xff0c;以及它的未来发展前景。 HTML5的应用现状 H…...

day44-Spring_AOP

0目录 1.2.3 1.Spring_AOP 实体类&#xff1a; Mapper接口&#xff1a; Service和实现类&#xff1a; 测试1&#xff1a; 运行后&#xff1a; 测试2&#xff1a;无此型号时 测试3&#xff1a;库存不足时 解决方案1&#xff1a;事务声明管理器 测试&#xff1a…...

selenium IDE 接入jenkins-转载

Selenium-IDE脚本录制,selenium-side-runner自动化测试教程_51CTO博客_selenium ide录制脚本 备忘录...

云计算结合数据科学突破信息泛滥(下)

大家好&#xff0c;本文将继续讨论云计算结合数据科学突破信息泛滥的相关内容&#xff0c;讲述其余三个关键组成部分。 3.数据清理和预处理 收集数据并将其存储在云端之后&#xff0c;下一步是将数据进行转换。因为原始数据经常包含错误、不一致和缺失的值&#xff0c;这些都…...

蓝桥杯单片机第十二届国赛 真题+代码

iic.c /* # I2C代码片段说明1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。2. 参赛选手可以自行编写相关代码或以该代码为基础&#xff0c;根据所选单片机类型、运行速度和试题中对单片机时钟频率的要求&#xff0c;进行代码调试和修改。 */ #include <STC1…...

MyBatis学习笔记之缓存

文章目录 一级缓存一级缓存失效 二级缓存二级缓存失效二级缓存相关配置 MyBatis集成EhCache 缓存&#xff1a;cache 缓存的作用&#xff1a;通过减少IO的方式&#xff0c;来提高程序的执行效率 mybatis的缓存&#xff1a;将select语句的查询结果放到缓存&#xff08;内存&…...

​小程序 WxValidate.js ​再次封装

util.js // 合并验证规则和提示信息 const filterRules (objectItem) > {let rules {}, messages {};for (let key in objectItem) {rules[key] objectItem[key].rulesmessages[key] objectItem[key].message}return { rules, messages } }module.exports {filterRule…...

redis 第三章

目录 1.主从复制 2.哨兵 3.集群 4.总结 1.主从复制 结果&#xff1a; 2.哨兵 3.集群 4.总结 通过集群&#xff0c;redis 解决了写操作无法负载均衡&#xff0c;以及存储能力受到单机限制的问题&#xff0c;实现了较为完善的高可用方案。...

MYSQL常见面试题汇总

MYSQL常见面试题汇总 1. 什么是MYSQL&#xff1f;它有哪些特点&#xff1f; MYSQL是一种开源的关系型数据库管理系统。它具有以下特点&#xff1a; 高性能&#xff1a;MYSQL能够处理大量的并发请求&#xff0c;并提供快速的响应时间。可靠性&#xff1a;MYSQL具有数据持久化…...

Java接口通过token登录实现页面跳转到登录成功后的页面

首先&#xff0c;你需要在接口请求中将token作为参数传递给后端&#xff0c;后端需要对token进行验证并获取登录用户的信息。 在验证通过后&#xff0c;你可以将登录成功后的页面链接返回给前端&#xff0c;前端通过跳转到该链接来实现页面跳转。 以下是一个简单的Java代码演…...

Linux-文件管理

1.文件管理概述 1.Bash Shell对文件进行管理 谈到Linux文件管理&#xff0c;首先我们需要了解的就是&#xff0c;我们要对文件做些什么事情&#xff1f; 其实无非就是对一个文件进行、创建、复制、移动、查看、编辑、压缩、查找、删除、等等 例如 : 当我们想修改系统的主机名…...

Android getevent用法详解

TP驱动调试分享——基于Qualcomm SDM710平台Android9.0&#xff0c;TP 采用I2C方式和CPU进行通信_高通tp驱动_永恒小青青的博客-CSDN博客 手机触摸屏扫描信号实测波形_触摸屏报点率_AirCity123的博客-CSDN博客 如何查看TP报点率&#xff1f;触摸TP查看详细信息 adb shell ge…...

面试题-TS(二):如何定义 TypeScript 中的变量和函数类型?

面试题-TS(二)&#xff1a;如何定义 TypeScript 中的变量和函数类型&#xff1f; 一、 变量类型的定义 在TypeScript中&#xff0c;我们可以使用冒号(:)来指定变量的类型。以下是一些常见的变量类型&#xff1a; 布尔类型&#xff08;boolean&#xff09;&#xff1a;表示tr…...

【4】-多个User执行测试

目录 一个locustfile中有多个User 使用--class-picker指定执行 小结 一个locustfile中有多个User from locust import task, HttpUserclass User01(HttpUser):weight 3 # 权重host https://www.baidu.comtaskdef user_01_task(self):self.client.get(url/, nameuser_01_…...

基于Eisvogel模板的Markdown导出PDF方法

Requirements 模板地址&#xff1a;Wandmalfarbe/pandoc-latex-template Pandoc&#xff1a;Pandoc官网 Latex环境&#xff1a;例如TexLive Pandoc参数 --template"模板存放位置" --listings --pdf-enginexelatex --highlight-style kate -V CJKmainfontSimSun -V C…...

linux服务器安装redis

一、安装下载 下载安装参考文章 下载安装包地址&#xff1a;https://download.redis.io/releases/ 亲测有效&#xff0c;但是启动的步骤有一些问题 安装完成&#xff01;&#xff01;&#xff01; 二、启动 有三种启动方式 默认启动指定配置启动开机自启 说明&#xff1a…...

QT中信号和槽本质

信号 信号的本质就是事件 在QT中信号的发出者是某个实例化的类对象&#xff0c;对象内部可以进行相关事件的检测。 槽 槽函数是一类特殊的功能的函数&#xff0c;也可以作为类的普通成员函数来使用 在Qt中槽函数的所有者也是某个类的实例对象。 信号和槽的关系 在Qt中我…...