Spring框架培训

本文基于《Spring框架培训》和尚硅谷教程以及CSDN。

Spring 框架培训

1. Spring简介

ONE——Spring
Spring框架是由RodJohnson在2002年创建的是一个开源的Java框架,旨在简化企业级应用程序的开发,并不断地适应和引领Java生态系统的变化

TWO——Spring Boot
Spring Boot进一步简化了Spring应用的开发,让开发者更专注于业务逻辑的实现而不是繁琐的配置

  • 依赖注入:

Spring的核心思想之一是依赖注,它通过Spring容器管理应用程序中对象的生命周期和配置

  • 事务管理:

Spring的事务管理使得你可以轻松地管理数据库事务

  • Spring Web:

用于构建基于Web的企业应用程序,包括MVC框架、HTTP协议等

  • 面向切面编程:

AOP是一种高级的编程范式,它允许你通过切面在应用中的多个地方定义横切关注点

2.依赖注入DI

2.1 什么是Spring的依赖注入

指Spring创建对象的过程中,将对象依赖属性通过配置进行注入,实现了控制反转的思想

依赖注入常见的实现方式包括两种:

  • 第一种:set注入
  • 第二种:构造注入

所以结论是:IOC 就是一种控制反转的思想, 而 DI 是对IoC的一种具体实现。

2.2 依赖注入之setter注入

①创建学生类Student

package com.atguigu.spring6.bean;

public class Student 
{

    private Integer id;

    public Student() {
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

}

②配置bean时为属性赋值

<bean id="studentOne" class="com.atguigu.spring6.bean.Student">
    <!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 -->
    <!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关) -->
    <!-- value属性:指定属性值 -->
    <property name="id" value="1001"></property>
</bean>

③测试

@Test
public void testDIBySet(){
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring-di.xml");
    Student studentOne = ac.getBean("studentOne", Student.class);
    System.out.println(studentOne);
}

2.3 依赖注入之构造器注入

①在Student类中添加有参构造

public Student(Integer id) {
    this.id = id;
}

②配置bean

<bean id="studentTwo" class="com.atguigu.spring6.bean.Student">
    <constructor-arg value="1002"></constructor-arg>
</bean>

③测试

@Test
public void testDIByConstructor(){
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring-di.xml");
    Student studentOne = ac.getBean("studentTwo", Student.class);
    System.out.println(studentOne);
}

2.4 SOLID原则

在这里插入图片描述

Single Responsibility Principle - 单一功能原则

单一功能原则:一个类或者模块只负责完成一个职责(或者功能)。

简单来说就是保证我们在设计函数、方法时做到功能单一,权责明确,当发生改变时,只有一个改变它的原因。如果函数/方法承担的功能过多,就意味着很多功能会相互耦合,这样当其中一个功能发生改变时,可能会影响其它功能。单一功能原则,可以使代码后期的维护成本更低、改动风险更低。

Open / Closed Principle - 开闭原则

开闭原则:软件实体应该对扩展开放、对修改关闭。

简单来说就是通过在已有代码基础上扩展代码,而非修改代码的方式来完成新功能的添加。开闭原则,并不是说完全杜绝修改,而是尽可能不修改或者以最小的代码修改代价来完成新功能的添加。

Liskov Substitution Principle - 里氏替换原则

里氏替换原则:如果S是T的子类型,则类型T的对象可以替换为类型S的对象,而不会破坏程序。在Go开发中,里氏替换原则可以通过接口来实现。

Interface Segregation Principle - 接口隔离原则

接口隔离原则:客户端程序不应该依赖它不需要的方法。

Dependency Inversion Principle - 依赖倒置原则

依赖倒置原则:依赖于抽象而不是一个实例,其本质是要面向接口编程,不要面向实现编程。

2.4.1为何-Spring框架履约SOLID原则

依赖翻转原则和开闭原则

Spring通过10C容器实现了依赖反转原则通过配置文件或注解,可以灵活地添加、替换或升级组件而无需修改原始源代码

里氏替换原则

Spring鼓励使用接口和抽象类进行编程,通过依赖注入这些接口和抽象实现

单一职责原则

Spring鼓励使用轻量级的、专注于单一任务的组件,如@Service、@Repository、@Controller等,保持每个类的单一职责

接口隔离原则

通过鼓励将接口、抽象类进行细粒度的拆分,通过依赖注入的方式,让使用方能精确依赖自己的所需要的接口

2.5 一些常用的注解

Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。

注解说明
@Component该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。
@Repository该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Service该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Controller该注解通常作用在控制层(如SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Autowired用于字段、构造函数或方法级别,指示Spring容器应自动将相关的依赖项注入到标注的地方
当应用在字段上时,Spring会在Bean实例化后为其注入合适的依赖对象。
应用在构造函数上时,Spring会根据构造函数参数类型来寻找匹配的Bean进行注入,确保在对象创建时就具有所有必需的依赖。
应用在Setter方法上时,Spring会在Bean实例化且属性设置阶段调用此方法注入依赖
@Qualifier与@Autowired配合使用时,可以进一步精确指定要注入的Bean,当有多个同-类型的候选Bean时,通过名称或者自定义注解区分
@Value用于注入基本类型值、SpEL表达式结果、系统属性、环境变量等值到字段或方法中
@PostConstruct
@PreDestroy
定义了Bean生命周期中的初始化后回调方法和销毁前回调方法
标记的方法会在Spring完成依赖注入后(@PostConstruct)立即执行,而带有@PreDestroy的方法将在Bean从容器中移除之前执行
@Configuration
@Bean
在基于Java配置的场景下,这些注解用于定义Bean的创建逻辑
@Bean注解的方法会返回一个对象,这个对象会被Spring视为一个Bean,并可参与到Dl的过程中
@DependsOn指定当前Bean在被创建之前必须先创建和初始化的其他Bean
这个注解强制了Bean间的初始化顺序

3.面向切面编程

3.1 AOP的基本原理

3.1.1 AOP简介

AOP(Aspect Oriented Programming):面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

3.1.2 AOP的应用场景

AOP并非适用任意场合,一般应用在需要多个业务流程中都需要相同或类似的业务处理,且与核心业务无关,则特别适合用AOP技术来解决,例如:存在多个业务操作;多个业务操作中都需要完成某个相同的操作,并且这些操作都和核心业务功能没有直接关系。

3.1.3 AOP原理
  1. 将复杂的需求分解出不同的方面,将公共功能集中解决。
  2. AOP的底层实现是基于动态代理。AOP 代理其实是由 AOP 框架动态生成的一个对象,该对象可作为目标对象使用。AOP 代理包含了目标对象的全部方法,但 AOP 代理中的方法与目标对象的方法存在差异,AOP 方法在特定切入点添加了增强处理,并回调了目标对象的方法。采用代理机制组装起来运行,在不改变程序的基础上对代码段进行增强处理,增加新的功能。

综上,所谓面向切面编程,是一种通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态添加功能的技术。

3.2 AOP的组成

AOP由下边四部分组成:

Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。

注解说明
切面(Aspect)由切点(Pointcut)和通知(Advice)组成,既包含了横切逻辑的定义,也包含了连接点的定义。
连接点(Join Point)应用执行过程中能够插入切面的一个点,这个点可以是方法调用时,抛出异常时,甚至修改字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
切点(Pointcut)Pointcut的作用就是提供一组规则 来匹配Join Point,给满足规则的Join Point 添加Advice.
通知(Advice)切面也有目标-他必须要完成的工作。在AOP中,切面的工作被称为通知。

img

3.3 AOP的实现

实现步骤如下:

  1. 添加 Spring AOP 框架⽀持。
  2. 定义切⾯和切点。
  3. 定义通知。
3.3.1 添加 Spring AOP 框架⽀持

首先建立一个新的Springboot项目,在配置文件中添加AOP框架:

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <version>$$$</version>
 </dependency>
3.3.2 定义切面、切点和通知
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
@Slf4j
@Component
@Aspect
public class LoginAspect {
//定义切点
    @Pointcut("execution(* com.example.springaopdemo.controller.UserController.*(..))")
    public void pointcut(){}
//定义通知
    @Before("pointcut()")
    public void before(){
        log.info("do before,,,,");
    }
    @After("pointcut()")
    public void after(){
        log.info("do after.....");
    }
    @AfterReturning("pointcut()")
    public void afterReturning(){
        log.info("do afterReturning....");
    }
    @AfterThrowing("pointcut()")
    public  void afterThrowing(){
        log.info("do afterThrowing....");
    }
//这里注意,环绕式通知必须自定义返回结果
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint){
        Object oj=null;
        log.info("之前");
        try{
            oj=joinPoint.proceed();//调用目标方法
        }catch(Throwable e){
            throw new RuntimeException(e);
        }
        log.info("之后");
        return oj;
    }

3.4 事务管理

在这里插入图片描述

3.4.1 什么是事务

事务是一组操作的集合,是一个不可分割的工作单位,它会把所有操作当成一个整体,向数据库进行提交或者是撤销的操作请求,所以这组操作要么同时成功,要么同时失败。

3.4.2 事务的操作

主要有三步

  1. 开启事务(一组操作开始前,开启事务)
  2. 提交事务(这组操作全部成功后,提交事务)
  3. 回滚事务(但凡任何一个操作出现异常,就会进行回滚事务)
3.4.3 基于注解的声明式事务的实现
3.4.3.1 准备工作

①添加配置

在beans.xml添加配置

<!--扫描组件-->
<context:component-scan base-package="com.atguigu.spring6"></context:component-scan>

②创建表

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);

③创建组件

创建BookController:

package com.atguigu.spring6.controller;

@Controller
public class BookController {

    @Autowired
    private BookService bookService;

    public void buyBook(Integer bookId, Integer userId){
        bookService.buyBook(bookId, userId);
    }
}

创建接口BookService:

package com.atguigu.spring6.service;
public interface BookService {
    void buyBook(Integer bookId, Integer userId);
}

创建实现类BookServiceImpl:

package com.atguigu.spring6.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);
    }
}

创建接口BookDao:

package com.atguigu.spring6.dao;
public interface BookDao {
    Integer getPriceByBookId(Integer bookId);

    void updateStock(Integer bookId);

    void updateBalance(Integer userId, Integer price);
}

创建实现类BookDaoImpl:

package com.atguigu.spring6.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 set balance = balance - ? where user_id = ?";
        jdbcTemplate.update(sql, price, userId);
    }
}
3.4.3.2 测试无事务情况

①创建测试类

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

@SpringJUnitConfig(locations = "classpath:beans.xml")
public class TxByAnnotationTest {

    @Autowired
    private BookController bookController;

    @Test
    public void testBuyBook(){
        bookController.buyBook(1, 1);
    }

}

②模拟场景

用户购买图书,先查询图书的价格,再更新图书的库存和用户的余额

假设用户id为1的用户,购买id为1的图书

用户余额为50,而图书价格为80

购买图书之后,用户的余额为-30,数据库中余额字段设置了无符号,因此无法将-30插入到余额字段

此时执行sql语句会抛出SQLException

③观察结果

因为没有添加事务,图书的库存更新了,但是用户的余额没有更新

显然这样的结果是错误的,购买图书是一个完整的功能,更新库存和更新余额要么都成功要么都失败

3.4.3.3 加入事务
①添加事务配置

在spring配置文件中引入tx命名空间

<?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:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd">

在Spring的配置文件中添加配置:

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

<!--
    开启事务的注解驱动
    通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务
-->
<!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就是这个默认值,则可以省略这个属性 -->
<tx:annotation-driven transaction-manager="transactionManager" />
②添加事务注解

因为service层表示业务逻辑层,一个方法表示一个完成的功能,因此处理事务一般在service层处理

在BookServiceImpl的buybook()添加注解@Transactional

③观察结果

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

3.4.3.4 @Transactional注解标识的位置

@Transactional标识在方法上,则只会影响该方法

@Transactional标识的类上,则会影响类中所有的方法

3.4.3.5 事务属性:只读

①介绍

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

②使用方式

@Transactional(readOnly = true)
public void buyBook(Integer bookId, Integer userId) {
    //查询图书的价格
    Integer price = bookDao.getPriceByBookId(bookId);
    //更新图书的库存
    bookDao.updateStock(bookId);
    //更新用户的余额
    bookDao.updateBalance(userId, price);
    //System.out.println(1/0);
}

③注意

对增删改操作设置只读会抛出下面异常:

Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed

3.4.3.6 事务属性:超时

①介绍

事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行。

概括来说就是一句话:超时回滚,释放资源。

②使用方式

//超时时间单位秒
@Transactional(timeout = 3)
public void buyBook(Integer bookId, Integer userId) {
    try {
        TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //查询图书的价格
    Integer price = bookDao.getPriceByBookId(bookId);
    //更新图书的库存
    bookDao.updateStock(bookId);
    //更新用户的余额
    bookDao.updateBalance(userId, price);
    //System.out.println(1/0);
}

③观察结果

执行过程中抛出异常:

org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Fri Jun 04 16:25:39 CST 2022

3.4.3.7 事务属性:回滚策略

①介绍

声明式事务默认只针对运行时异常回滚,编译时异常不回滚。

可以通过@Transactional中相关属性设置回滚策略

  • rollbackFor属性:需要设置一个Class类型的对象

  • rollbackForClassName属性:需要设置一个字符串类型的全类名

  • noRollbackFor属性:需要设置一个Class类型的对象

  • rollbackFor属性:需要设置一个字符串类型的全类名

②使用方式

@Transactional(noRollbackFor = ArithmeticException.class)
//@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")
public void buyBook(Integer bookId, Integer userId) {
    //查询图书的价格
    Integer price = bookDao.getPriceByBookId(bookId);
    //更新图书的库存
    bookDao.updateStock(bookId);
    //更新用户的余额
    bookDao.updateBalance(userId, price);
    System.out.println(1/0);
}

③观察结果

虽然购买图书功能中出现了数学运算异常(ArithmeticException),但是我们设置的回滚策略是,当出现ArithmeticException不发生回滚,因此购买图书的操作正常执行

3.4.3.8 事务属性:隔离级别

①介绍

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

隔离级别一共有四种:

  • 读未提交:READ UNCOMMITTED

    允许Transaction01读取Transaction02未提交的修改。

  • 读已提交:READ COMMITTED、

    要求Transaction01只能读取Transaction02已提交的修改。

  • 可重复读:REPEATABLE READ

    确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。

  • 串行化:SERIALIZABLE

    确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

各个隔离级别解决并发问题的能力见下表:

隔离级别脏读不可重复读幻读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE

各种数据库产品对事务隔离级别的支持程度:

隔离级别OracleMySQL
READ UNCOMMITTED×
READ COMMITTED√(默认)
REPEATABLE READ×√(默认)
SERIALIZABLE

②使用方式

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

①介绍

什么是事务的传播行为?

在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。

一共有七种传播行为:

  • REQUIRED:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】
  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行**【有就加入,没有就不管了】**
  • MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常**【有就加入,没有就抛异常】**
  • REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起**【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】**
  • NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务**【不支持事务,存在就挂起】**
  • NEVER:以非事务方式运行,如果有事务存在,抛出异常**【不支持事务,存在就抛异常】**
  • NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】

②测试

创建接口CheckoutService:

package com.atguigu.spring6.service;

public interface CheckoutService {
    void checkout(Integer[] bookIds, Integer userId);
}

创建实现类CheckoutServiceImpl:

package com.atguigu.spring6.service.impl;

@Service
public class CheckoutServiceImpl implements CheckoutService {

    @Autowired
    private BookService bookService;

    @Override
    @Transactional
    //一次购买多本图书
    public void checkout(Integer[] bookIds, Integer userId) {
        for (Integer bookId : bookIds) {
            bookService.buyBook(bookId, userId);
        }
    }
}

在BookController中添加方法:

@Autowired
private CheckoutService checkoutService;

public void checkout(Integer[] bookIds, Integer userId){
    checkoutService.checkout(bookIds, userId);
}

在数据库中将用户的余额修改为100元

③观察结果

可以通过@Transactional中的propagation属性设置事务传播行为

修改BookServiceImpl中buyBook()上,注解@Transactional的propagation属性

@Transactional(propagation = Propagation.REQUIRED),默认情况,表示如果当前线程上有已经开启的事务可用,那么就在这个事务中运行。经过观察,购买图书的方法buyBook()在checkout()中被调用,checkout()上有事务注解,因此在此事务中执行。所购买的两本图书的价格为80和50,而用户的余额为100,因此在购买第二本图书时余额不足失败,导致整个checkout()回滚,即只要有一本书买不了,就都买不了

@Transactional(propagation = Propagation.REQUIRES_NEW),表示不管当前线程上是否有已经开启的事务,都要开启新事务。同样的场景,每次购买图书都是在buyBook()的事务中执行,因此第一本图书购买成功,事务结束,第二本图书购买失败,只在第二次的buyBook()中回滚,购买第一本图书不受影响,即能买几本就买几本。

3.4.3.10 全注解配置事务

①添加配置类

package com.atguigu.spring6.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;

@Configuration
@ComponentScan("com.atguigu.spring6")
@EnableTransactionManagement
public class SpringConfig {

    @Bean
    public DataSource getDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        return dataSource;
    }

    @Bean(name = "jdbcTemplate")
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}

②测试

import com.atguigu.spring6.config.SpringConfig;
import com.atguigu.spring6.controller.BookController;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

public class TxByAllAnnotationTest {

    @Test
    public void testTxAllAnnotation(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookController accountService = applicationContext.getBean("bookController", BookController.class);
        accountService.buyBook(1, 1);
    }
}
3.4.3.11 声明式事务注意事项
  • @Transactional 注解只有作用到 publlc 方法上事务才生效不推荐在接口上使用;
  • @Transactional注解的方法,这样会导致事务失效:梁免同类中互相调用;
  • 正确的设置 @Transactional 的 rollbackFor 和 propagation 属性,否则事务可能会回滚失败;
  • 被 @Transactional 注解的方法所在的类必须被 Spring 管理,否则不生效;
  • 底层使用的数据库必须支持事务机制,否则不生效;

3.5 Spring通知类型

  • 前置通知(Before Advice):在目标方法执行之前执行的通知。前置通知允许你在目标方法执行之前做一些准备工作,比如参数验证、日志记录等
  • 后置通知(After Returning Advice):在目标方法正常执行并返回结果之后执行的通知。后置通知允许你在目标方法执行之后做一些处理,比如记录方法返回值、清理资源等
  • 异常通知(After Throwing Advice):在目标方法抛出异常时执行的通知。异常通知允许你在目标方法抛出异常之后做一些处理,比如记录异常信息、进行事务回滚等
  • 后置最终通知(After Advice):在目标方法执行完成后无论是否抛出异常都执行的通知。后置最终通知类似于 Java 中的 finally 块,它允许你在目标方法执行完毕后执行一些清理工作,无论方法是否正常返回或抛出异常
  • 环绕通知(Around Advice):环绕通知是最灵活的一种通知类型,它可以在目标方法执行之前和之后执行自定义的操作。环绕通知可以完全控制目标方法的执行过程,包括是否执行目标方法、如何执行目标方法等

4 Spring实战

附带尚硅谷 《SpringFramework 实战指南》,不再赘述

buyBook(1, 1);
}
}




##### 3.4.3.11 声明式事务注意事项

- @Transactional 注解只有作用到 publlc 方法上事务才生效不推荐在接口上使用;
- @Transactional注解的方法,这样会导致事务失效:梁免同类中互相调用;
- 正确的设置 @Transactional 的 rollbackFor 和 propagation 属性,否则事务可能会回滚失败;
- 被 @Transactional 注解的方法所在的类必须被 Spring 管理,否则不生效;
- 底层使用的数据库必须支持事务机制,否则不生效;



### 3.5 Spring通知类型

- 前置通知(Before Advice):在目标方法执行之前执行的通知。前置通知允许你在目标方法执行之前做一些准备工作,比如参数验证、日志记录等
- 后置通知(After Returning Advice):在目标方法正常执行并返回结果之后执行的通知。后置通知允许你在目标方法执行之后做一些处理,比如记录方法返回值、清理资源等
- 异常通知(After Throwing Advice):在目标方法抛出异常时执行的通知。异常通知允许你在目标方法抛出异常之后做一些处理,比如记录异常信息、进行事务回滚等
- 后置最终通知(After Advice):在目标方法执行完成后无论是否抛出异常都执行的通知。后置最终通知类似于 Java 中的 finally 块,它允许你在目标方法执行完毕后执行一些清理工作,无论方法是否正常返回或抛出异常
- 环绕通知(Around Advice):环绕通知是最灵活的一种通知类型,它可以在目标方法执行之前和之后执行自定义的操作。环绕通知可以完全控制目标方法的执行过程,包括是否执行目标方法、如何执行目标方法等



## 4 Spring实战

参考《SpringFramework 实战指南》(版权问题,不附带在文章中,如有需求,可通过尚硅谷B站课程),不再赘述




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值