第五章: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 {
          @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>
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值