第五章:Spring下

news2024/9/26 1:26:14

第五章: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 {
          @Override
          public 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;
          }
      
          @Override
          public 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;
          }
      
          @Override
          public 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;
          }
      
          @Override
          public 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;
          }
      
          @Override
          public 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;
          }
      
          @Override
          public 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;
          }
      
          @Override
          public 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;
          }
      
          @Override
          public 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() {
                  @Override
                  public 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 {
            @Autowired
            private JdbcTemplate jdbcTemplate;
        
            @Test
            public 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 {
              @Autowired
              private JdbcTemplate jdbcTemplate;
          
              // 查询图书价格
              @Override
              public Integer getPriceByBookId(Integer bookId) {
                  String sql = "select price from t_book where book_id = ?";
                  return jdbcTemplate.queryForObject(sql, Integer.class, bookId);
              }
              
              // 更新图书的库存
              @Override
              public void updateStock(Integer bookId) {
                  String sql = "update t_book set stock = stock - 1 where book_id = ?";
                  jdbcTemplate.update(sql, bookId);
              }
              
              // 更新用户的余额
              @Override
              public 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 {
              @Autowired
              private BookDao bookDao;
          
              @Override
              public 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 {
              @Autowired
              private BookService bookService;
          
              public void buyBook(Integer bookId, Integer userId) {
                  bookService.buyBook(bookId, userId);
              }
          }
          
        • 测试

          @RunWith(SpringJUnit4ClassRunner.class)
          @ContextConfiguration("classpath:tx-annotation.xml")
          public class TxByAnnotationTest {
          
              @Autowired
              private BookController bookController;
          
              @Test
              public 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 {
                @Autowired
                private BookService bookService;
            
                @Override
                @Transactional
                public 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>
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/805546.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[CrackMe]Chafe.2.exe的逆向及注册机编写

1. 逆向分析过程 先上手把玩一下, 从外观上看感觉和Chafe.1.exe差不了多少, 还是那个界面 找到RegisterClassEx从而找到其对应的窗口过程 找到对应的WM_COMMAND分支 首先其修改了代码中的4个字节, 将其修改成0x00584554, 然后通过GetDlgItemInt获取了serial值 这里其实原本…

笔记20230727

1. http2.0&#xff0c;概念就不说了&#xff0c;查看是否使用&#xff1a;network调试&#xff0c;查看请求的header-view source&#xff0c;可以查看http版本&#xff1b;后端&#xff0c;如nginx&#xff0c;配置&#xff0c;http2表示开启。后端开启、浏览器支持&#xff…

利用小波包对一维信号进行降噪或压缩(MATLAB)

function [ output_args ] example4_12( input_args ) %EXAMPLE4_12 Summary of this function goes here % Detailed explanation goes here clc; clear; % 设置信噪比和随机数的初始值 snr 3; init 2055615866; % 生成一个原始信号xref和含高斯白噪声的信号x [xref,x] …

nginx配置auth_basic认证

nginx配置auth_basic认证 windows 1、下载 httpd 下载地址: https://www.apachelounge.com/download/ 2、生成密码&#xff1a;打开cmd&#xff0c;进入到 Apache24\bin后 html htpasswd.exe -c G:\nginx\nginx-1.14.2\conf\.htpasswd user 其中: G:\nginx\nginx-1.14.2\conf…

【微服务】springboot 多模块打包使用详解

目录 一、前言 1.1 为什么需要掌握多模块打包 二、工程模块概述 2.1 前后端不分离 2.2 部署方式多样化 2.3 单模块向多模块演进 三、单模块打包构建 3.1 环境准备 3.1.1 创建测试用的单模块工程 3.1.2 多环境配置文件 3.1.3 新增测试接口 3.2 pom配置详解 3.2.1 添…

Git代码管理

目录&#xff1a; git环境配置 git工作流程git常用命令gitlab实战gitlog分析与检索分支管理策略git合并与冲突 1.git环境配置 Git 简介&#xff1a; Git 是目前世界上最先进的分布式版本控制系统。Git 优点&#xff1a; 适合分布式开发&#xff0c;强调个体…

搭建自己第一个golang程序

概念&#xff1a; golang 和 java有些类似&#xff0c;配置好环境就可以直接编写运行了&#xff1b;这里分两种&#xff1a; 一.shell模式 创建一个go类型的文件 往里面编写代码 二.开发工具模式 这里的开发工具 我选用goland package mainimport "fmt"func mai…

模糊神经网络机械故障诊断(MATLAB代码)

效果 用训练好的模糊神经网络对机械故障进行诊断,根据网络的预测值得到机械的技术状态。预测值小于 1.5 时为正常状态,预测值在 1.5~2.5 之间时为曲轴轴承轻微异响,预测值在 2.5~3.5 之间时为曲轴轴承严重异响预测值在 3.5~4.5 之间时为连杆轴承轻微异响,预测值大于 4.5 时为连…

Debian LNMP架构的简单配置使用

一、LNMP简介 LinuxNginxMysqlPHP组成的网站架构&#xff0c;常用于中小型网站服务。 二、环境 Debian 6.1.27-1kali1 (2023-05-12) Nginx/1.22.1 10.11.2-MariaDB&#xff08;mysql&#xff09; PHP 8.2.7 &#xff08;Debian 6.1.27包含以上包&#xff0c;直接使用即…

0138 内存管理2

目录 3.内存管理 3.2虚拟内存管理 3.2部分习题 3.内存管理 3.2虚拟内存管理 3.2部分习题 1.在请求分页存储管理中&#xff0c;若采用FIFO页面淘汰算法&#xff0c;则当可供分配的页帧数增加时&#xff0c;缺页中断的次数&#xff08;&#xff09; A.减少 B.增加…

【嵌入式学习笔记】嵌入式基础9——STM32启动过程

1.MAP文件浅析 1.1.MDK编译后生成的中间过程文件 1.2.Map文件构成&#xff1a; 程序段交叉引用关系&#xff08;Section Cross References&#xff09;&#xff1a;描述各文件之间函数调用关系删除映像未使用的程序段&#xff08;Removing Unused input sections from the im…

Linux进程的认识

查看进程指令proc/ps 注意哦, 我们经常使用的指令, 像ls, touch…这些指令在启动之后本质上也是进程 proc 是内存文件系统, 存放着当前系统的实时进程信息. 每一个进程在系统中, 都会存在一个唯一的标识符(pid -> process id), 就如同学生在学校里有一个专门的学号一样. 大…

MyBatis——Java与数据库再续前缘

迭代更新&#xff0c;更新时代 时代发展&#xff0c;发展技术 技术进步&#xff0c;进步迭代 ——Lungcen 目录 MyBatis的简介入门 1、硬编码 2、操作繁琐 MyBatis简单入门 1、处理硬编码 2、处理操作繁琐 3、参数传递 MyBatis核心配置文件 配置文档的顶层结构 环境配置&…

MMsegmentaion自定义数据集运行出错AssertionError: failed to load image

问题 我的数据集中图像是TIF格式&#xff0c;4通道&#xff0c;想用mmsegmentation模型跑一下看看效果&#xff0c;按照官方手册里的方法自定义完数据集后&#xff0c;运行出错&#xff0c;错误截图如下。 解决 1.错误原因 通过上面的截图&#xff0c;发现错误出在红色框框…

vue动态引入静态资源

vue动态引入静态资源 静态资源位置&#xff08;../../assets/piecture/page404.jpg&#xff09;或者&#xff08;/assets/piecture/page404.jpg&#xff09; 错误引入方式 错误引入方式&#xff08;一&#xff09; <template><div><img :src"../../asset…

Spark-hadoop集群中8020:Connection refused

一、使用CentOs7虚拟机运行spark案例报出8020端口出现错误 前提条件&#xff0c;使用standalone模式下的Spark&#xff0c;使用spark-shell运行example中的相关测试案例spark-examples_2.12-3.0.0.jar&#xff0c;来计算pi。当spark-shell 停止掉后&#xff0c;集群监控base:40…

macos下安装john the ripper并配置zip2john+破解加密zip文件

为了破解加密的zip文件&#xff0c;需要用到john进行爆破密码。 1、首先使用homebrew安装john&#xff0c;可以安装它的增强版john-jumbo: brew install john-jumbo 2、安装后可以使用 john 命令验证&#xff1a; john 3、配置zip2john的环境——.zshrc下&#xff0c;&#x…

Linux Mint 21.2 ISO 镜像开放下载

导读Linux Mint 21.2 ISO 镜像于 2023 年 6 月 21 日公测&#xff0c;开发者在这段时间内收集并修复了用户反馈的诸多问题。 代号为“Victoria”的 Linux Mint 21.2 ISO 镜像于今天正式开放下载&#xff0c;新版本基于 Ubuntu 22.04 LTS&#xff0c;提供 Cinnamon 5.8、Xfce 4.…

CNN卷积详解

转载自&#xff1a;https://blog.csdn.net/yilulvxing/article/details/107452153 仅用于自己学习过程中经典文章讲解的记录&#xff0c;防止原文失效。 1&#xff1a;单通道卷积 以单通道卷积为例&#xff0c;输入为&#xff08;1,5,5&#xff09;&#xff0c;分别表示1个通道…

Flutter 添加 example流程

一、已有Flutter工程&#xff08;命令&#xff09;添加 example 1、cd 工程(flutter_plugin ,是自己创建的)根目录 例: flutter create example 执行命令创建example PS&#xff1a;cd example 后执行flutter doctor 后就可以看到效果 2、如果需要指定iOS/Android 语言,请添加…