Spring——感谢尚硅谷官方文档

news2024/12/25 15:08:22

Spring——尚硅谷学习笔记

      • 1 Spring简介👾
        • 1.1 Spring概述
        • 1.2 Spring Framework
          • 1.2.1 Spring Framework特性
          • 1.2.2 Spring Framework五大功能模块
      • 2 IOC-IOC容器思想👾
          • IOC容器思想
          • IOC在Spring中的实现
      • 3 IOC-基于XML文件管理Bean👾
        • 3.1 准备工作
        • 3.2 获取bean
          • 3.2.1 方式一:通过bean的id
          • 3.2.2 方式二:通过类型(类的Class对象)
          • 3.2.3 方式三:通过类型和id
          • 3.2.4 获取bean的方式总结
          • 3.2.5 对于接口
          • 3.2.6 根据类型获取bean的实质
        • 3.3 依赖注入
          • setter注入和构造器注入⭐️
          • 3.3.1 特殊值处理
          • 3.3.2 类类型的属性赋值
          • 3.3.3 数组
          • 3.3.4 list集合
          • 3.3.5 map集合
          • 3.3.6 p命名空间
        • 3.4 管理数据源和外部文件
          • 加入依赖
          • 创建jdbc的配置文件
          • 配置DataSource
        • 3.5 bean的作用域
        • 3.6 bean的生命周期
        • 3.7 工厂bean
          • 概念
          • 实现FactoryBean接口
        • 3.8 自动装配⭐️
          • 概念
          • 实现
      • 4 IOC-基于注解管理Bean👾
        • 4.1 使用注解注册bean组件
        • 4.2 扫描组件
        • 4.3 @Autowired自动装配
          • 4.3.1 能够标识的位置
          • 4.3.2 注入的方式
        • 4.4 例子
      • 5 AOP-概念👾
        • 5.1 代理模式
          • 5.1.1 概念
          • 5.1.2 静态代理
          • 5.1.3 动态代理
        • 5.2 AOP概念及相关术语
          • AOP
          • 横切关注点
          • 通知
          • 切面
          • 目标
          • 代理
          • 连接点
          • 切入点
          • 总结
      • 6 AOP-基于注解的AOP👾
        • 6.1 技术说明
        • 6.2 配置
            • maven依赖
            • 配置文件
        • 6.3 切面类(重点)
      • 7 声明式事务👾
        • 4.1 JdbcTemplate
          • 4.1.1 配置
          • 4.1.2 使用(在Spring中进行测试)🎾
        • 4.2 声明式事务的概念
          • 4.2.1 编程式事务
          • 4.2.2 声明式事务
        • 4.3 基于注解的声明式事务
          • 场景模拟
          • 加入事务
        • 4.4 事务的属性
          • 1、只读
          • 2、超时
          • 3、回滚策略
          • 4、隔离级别
          • 5、事务的传播

在这里插入图片描述
在这里插入图片描述
仅自用,如有侵权,立刻删!——感谢【尚硅谷】官方文档

1 Spring简介👾

1.1 Spring概述

image-20220724093300702

Spring 是最受欢迎的企业级 Java 应用程序开发框架,数以百万的来自世界各地的开发人员使用
Spring 框架来创建性能好、易于测试、可重用的代码。
Spring 框架是一个开源的 Java 平台,它最初是由 Rod Johnson 编写的,并且于 2003 年 6 月首次在 Apache 2.0 许可下发布。
Spring 是轻量级的框架,其基础版本只有 2 MB 左右的大小。
Spring 框架的核心特性是可以用于开发任何 Java 应用程序,但是在 Java EE 平台上构建 web 应用程序是需要扩展的。 Spring 框架的目标是使 J2EE 开发变得更容易使用,通过启用基于 POJO编程模型来促进良好的编程实践。

1.2 Spring Framework

Spring 基础框架,可以视为 Spring 基础设施,基本上任何其他 Spring 项目都是以 Spring Framework为基础的。

1.2.1 Spring Framework特性
  1. 非侵入式:使用 Spring Framework 开发应用程序时,Spring 对应用程序本身的结构影响非常小。对领域模型可以做到零污染;对功能性组件也只需要使用几个简单的注解进行标记,完全不会破坏原有结构,反而能将组件结构进一步简化。这就使得基于 Spring Framework 开发应用程序时结构清晰、简洁优雅。
  2. 控制反转:IOC——Inversion of Control,翻转资源获取方向。把自己创建资源、向环境索取资源变成环境将资源准备好,我们享受资源注入。
  3. 面向切面编程:AOP——Aspect Oriented Programming,在不修改源代码的基础上增强代码功能。
  4. 容器:Spring IOC 是一个容器,因为它包含并且管理组件对象的生命周期。组件享受到了容器化的管理,替程序员屏蔽了组件创建过程中的大量细节,极大的降低了使用门槛,大幅度提高了开发效率。
  5. 组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用 XML和 Java 注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不紊的搭建超大型复杂应用系统。
  6. 声明式:很多以前需要编写代码才能实现的功能,现在只需要声明需求即可由框架代为实现。
  7. 一站式:在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库。而且Spring 旗下的项目已经覆盖了广泛领域,很多方面的功能性需求可以在 Spring Framework 的基础上全部使用 Spring 来实现。
1.2.2 Spring Framework五大功能模块
  • 功能模块功能介绍
  • Core Container 核心容器,在 Spring 环境下使用任何功能都必须基于 IOC 容器。
  • AOP&Aspects 面向切面编程
  • Testing 提供了对 junit 或 TestNG 测试框架的整合。
  • Data Access/Integration 提供了对数据访问/集成的功能。
  • Spring MVC 提供了面向Web应用程序的集成功能。

2 IOC-IOC容器思想👾

先简单说一下,IOC容器就是放置已经创建好的实例的一个地方。这样实例不需要程序员来手动创建,而是交给容器管理,更加高效。

下面这一部分更加详细阐述了,如果要深入了解IOC容器,需要阅读源码,这个以后安排上。这个笔记主要用于学习如何使用框架

IOC容器思想

IOC:Inversion of Control,翻译过来是反转控制。
①获取资源的传统方式
自己做饭:买菜、洗菜、择菜、改刀、炒菜,全过程参与,费时费力,必须清楚了解资源创建整个过程中的全部细节且熟练掌握。
在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率。

②反转控制方式获取资源
点外卖:下单、等、吃,省时省力,不必关心资源创建过程的所有细节。
反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式。
③DI
DI:Dependency Injection,翻译过来是依赖注入。
DI 是 IOC 的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器的资源注入。相对于IOC而言,这种表述更直接。
所以结论是:IOC 就是一种反转控制的思想, 而 DI 是对 IOC 的一种具体实现。

IOC在Spring中的实现

Spring 的 IOC 容器就是 IOC 思想的一个落地的产品实现。IOC 容器中管理的组件也叫做 bean。在创建bean 之前,首先需要创建 IOC 容器。Spring 提供了 IOC 容器的两种实现方式:
①BeanFactory
这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。
②ApplicationContext
BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用
ApplicationContext 而不是底层的 BeanFactory。

image-20220725161008451

3 IOC-基于XML文件管理Bean👾

对于xml文件的方式也要学会掌握,因为对于工程里面的一些jar包,你是无法在上面给他加注释的,只是使用xml文件方式。

3.1 准备工作

maven依赖:

<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>
</dependencies>

image-20220803161905754

创建Spring的配置文件:

image-20220803161959462

3.2 获取bean
3.2.1 方式一:通过bean的id

配置文件中:


<bean id="studentOne" class="com.zylai.spring.pojo.Student"></bean>

<bean id="studentTwo" class="com.zylai.spring.pojo.Student"></bean>

测试:

@Test
public void testIOC(){
        ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
        //获取bean
//        1.通过bean的id获取
        Student studentOne = (Student) ioc.getBean("studentOne");
}
3.2.2 方式二:通过类型(类的Class对象)

注意:要求ioc容器中有且仅有一个与之匹配的bean
若没有任何一个类型匹配的bean,抛出NoSuchBeanDefinitionException
若有多个类型匹配的bean,抛出NoUniqueBeanDefinitionException

@Test
public void testIOC(){
        ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
     //获取bean
//        2.通过类的Class对象获取
        Student studentOne = ioc.getBean(Student.class);
}
3.2.3 方式三:通过类型和id
@Test
public void testIOC(){
        ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");

    //3.通过类型和id获取
    Student studentOne = ioc.getBean("studentOne",Student.class);
    System.out.println(studentOne);

}
3.2.4 获取bean的方式总结

以后用的最多的就是第二种方式,一个类型的bean只需要配置一次就可以了,如果真的需要多个实例,那么配置时加上scope属性选择多例就可以了

注意:在IOC容器中通过工厂模式和反射技术创建对象,所以需要对象的无参构造器

3.2.5 对于接口

xml文件中不能写接口,很简单,接口没有实例对象,也没有无参构造器
如果组件类实现了接口,根据接口类型可以获取 bean 吗?

可以,前提是bean唯一

如果一个接口有多个实现类,这些实现类都配置了 bean,根据接口类型可以获取 bean 吗?

不行,因为bean不唯一

下面这个会得到接口的实现类对象

    @Test
    public void testIOC(){
        ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");

        //通过接口获取实例
        Person person = ioc.getBean(Person.class);
        System.out.println(person);
    }
3.2.6 根据类型获取bean的实质

根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:

对象 instanceof 指定的类型 的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到。

即通过bean的类型、bean所继承的类的类型、bean所实现的接口的类型都可以获取bean

3.3 依赖注入
setter注入和构造器注入⭐️

分别是调用类的set方法和有参构造

<!--主要属性和成员变量的区别-->
<bean id="studentTwo" class="com.zylai.spring.pojo.Student">
    <!--
        property:通过成员变量的set方法进行赋值
        name:设置需要赋值的属性名(和set方法有关,set方法名,去掉set之后首字母大写)
        value:设置属性的值
    -->
    <property name="sid" value="1001"/>
    <property name="sname" value="张三"/>
    <property name="age" value="23"/>
    <property name="gender" value=""/>
</bean>

<bean id="studentThree" class="com.zylai.spring.pojo.Student">
    <constructor-arg value="1002"/>
    <constructor-arg value="李四"/>
    <constructor-arg value=""/>
    <constructor-arg value="23" name="age"/>
</bean>
3.3.1 特殊值处理

特殊字符和CDATA节的处理如下:

<!--特殊值处理-->
<bean id="studentFour" class="com.zylai.spring.pojo.Student">
    <property name="sid" value="1003"/>
    <!--
        对于特殊字符,应该使用该特殊字符对应的实体
        <  &lt;
        >  &gt;

        另外也可以使用CDATA节其中的内容会原样解析,
        CDATA节是xml中一个特殊的标签,不能写在属性中,只能通过一个标签写入
    -->
    <!--<property name="sname" value="&lt;王五&gt;"/>-->
    <property name="sname">
        <value><![CDATA[<王五>]]></value>
    </property>
    <property name="gender">
        <!--使性别这个属性为null-->
        <null/>
    </property>
</bean>
3.3.2 类类型的属性赋值

三种方式:

ref:ref引用IOC容器中的某个bean的id

<bean id="studentFive" class="com.zylai.spring.pojo.Student">
    <property name="sid" value="1006"/>
    <property name="sname" value="张六"/>
    <property name="age" value="23"/>
    <property name="gender" value=""/>
    
    <!--ref引用IOC容器中的某个bean的id-->
    <property name="clazz" ref="clazzOne"/>
   
</bean>

级联:级联的方式,要求clazz属性赋值或者实例化。所以一般不用

<bean id="studentFive" class="com.zylai.spring.pojo.Student">
    <property name="sid" value="1006"/>
    <property name="sname" value="张六"/>
    <property name="age" value="23"/>
    <property name="gender" value=""/>
   
    <!--级联的方式,要求clazz属性赋值或者实例化。所以一般不用-->
    <property name="clazz.cid" value="1122"/>
    <property name="clazz.cname" value="哈哈班"/>
</bean>

内部bean:在属性中设置一个bean。

注意:不能通过ioc容器获取,相当于内部类,只能在当前student内使用

<bean id="studentFive" class="com.zylai.spring.pojo.Student">
    <property name="sid" value="1006"/>
    <property name="sname" value="张六"/>
    <property name="age" value="23"/>
    <property name="gender" value=""/>
    <!--内部bean,在属性中设置一个bean。
    注意:不能通过ioc容器获取,相当于内部类,只能在当前student内使用-->
    <property name="clazz">
        <bean id="clazzInner" class="com.zylai.spring.pojo.Clazz">
            <property name="cid" value="1122"/>
            <property name="cname" value="王班"/>
        </bean>
    </property>
</bean>
3.3.3 数组

在属性中使用array标签,标签中数组元素的值写在value

<!--数组-->
<bean id="studentSix" class="com.zylai.spring.pojo.Student">
    <property name="sid" value="1006"/>
    <property name="sname" value="张六"/>
    <property name="age" value="23"/>
    <property name="gender" value=""/>
    <property name="clazz">
        <bean id="clazzInner" class="com.zylai.spring.pojo.Clazz">
            <property name="cid" value="1122"/>
            <property name="cname" value="王班"/>
        </bean>
    </property>
    
    <!--数组属性-->
    <property name="hobbies">
        <array>
            <value></value>
            <value></value>
            <value>rap</value>
        </array>
    </property>

</bean>
3.3.4 list集合

方式一:在属性中使用list标签

<!--
    list集合
    1.在属性中使用list标签
    2.再写一个集合类型的bean
-->
<bean id="clazzTwo" class="com.zylai.spring.pojo.Clazz">
    <property name="cid" value="1111"/>
    <property name="cname" value="王者班"/>
    <property name="studentList">
        <list>
            <ref bean="studentOne"/>
            <ref bean="studentTwo"/>
            <ref bean="studentThree"/>
        </list>
    </property>
</bean>

方式二:再写一个集合类型的bean

<!--
    list集合
    1.在属性中使用list标签
    2.再写一个集合类型的bean
-->
<bean id="clazzTwo" class="com.zylai.spring.pojo.Clazz">
    <property name="cid" value="1111"/>
    <property name="cname" value="王者班"/>
    
    <property name="studentList" ref="studentList"/>
</bean>

<!--配置一个集合类型的bean,需要使用util的约束-->
<util:list id="studentList">
    <ref bean="studentFour"/>
    <ref bean="studentFive"/>
    <ref bean="studentSix"/>
</util:list>
3.3.5 map集合

方式和list差不多

方式一:设置map标签

<bean id="studentSix" class="com.zylai.spring.pojo.Student">
    <property name="sid" value="1006"/>
    <property name="sname" value="张六"/>
    <property name="age" value="23"/>
    <property name="gender" value=""/>
    <property name="clazz">
        <bean id="clazzInner" class="com.zylai.spring.pojo.Clazz">
            <property name="cid" value="1122"/>
            <property name="cname" value="王班"/>
        </bean>
    </property>
    <property name="hobbies">
        <array>
            <value></value>
            <value></value>
            <value>rap</value>
        </array>
    </property>

    <!--map集合,和list差不多,就是需要设置键值对。第二种方式就是util了-->
    <property name="teacherMap">
        <map>
            <entry key="语文老师" value-ref="teacherOne"/>
            <entry key="数学老师" value-ref="teacherTwo"/>
        </map>
    </property>
</bean>

方式二:写一个集合类型的bean

<bean id="studentSix" class="com.zylai.spring.pojo.Student">
   ···略
    <property name="teacherMap" ref="teacherMap"/>
</bean>
<util:map id="teacherMap">
   <entry key="语文老师" value-ref="teacherOne"/>
   <entry key="数学老师" value-ref="teacherTwo"/>
</util:map>
3.3.6 p命名空间
<!--p命名空间,需要在xml文件最开始设置引入-->
<bean id="studentSeven" class="com.zylai.spring.pojo.Student"
      p:sid="1008" p:sname="小红" p:clazz-ref="clazzOne"
      p:teacherMap-ref="teacherMap">

</bean>
3.4 管理数据源和外部文件
加入依赖
<!-- 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>
创建jdbc的配置文件

jdbc.propertiesjdbc.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
jdbc.username=root
jdbc.password=root
initialSize=5
maxActive=9
配置DataSource

注意:需要通过context标签引入外部文件,注意context标签在xml文件头对应的链接

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.2.xsd">

    <!--引入jdbc.properties,之后可以通过${key}的方式访问value-->
    <!--这里的context标签,注意上面引入的-->
    <context:property-placeholder location="jdbc.properties"></context:property-placeholder>

    <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <!--<property name="initialSize" value="${initialSize}"/>-->
        <!--<property name="maxActive" value="${maxActive}"/>-->
    </bean>
</beans>
3.5 bean的作用域

在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义参加下表:

取值含义创建对象的时机
singleton (默认)在IOC容器中,这个bean的对象始终为单实例IOC容器初始化时
prototype这个bean在IOC容器中有多个实例获取bean时

如果是在WebApplicationContext环境下还会有另外两个作用域(但不常用) :

取值含义
request在一个请求范围内有效
session在一个会话范围内有效
<!--
    scope默认是单例,可以选择prototype多例
    scope="singleton|prototype"
    singleton:单例,表示获取该bean所对应的对象都是同一个
    prototype:多例,表示获取该bean所对应的对象都不是同一个
-->
<bean id="student" class="com.zylai.spring.pojo.Student" scope="prototype">
    <property name="sid" value="1001"/>
    <property name="sname" value="张三"/>
</bean>
3.6 bean的生命周期
  1. 实例化,调用无参构造
  2. 实例注入,调用set方法
  3. 初始化之前的操作(由后置处理器负责)
  4. 初始化,需要通过bean的init-method属性指定初始化方法
  5. 初始化之后的操作(由后置处理器负责)
  6. IOC容器关闭时销毁

测试验证:

实例:

public class User {
    private Integer id;
    private String username;
    private String password;
    private Integer age;

    public User() {
        System.out.println("生命周期:1、创建对象");
    }

    public User(Integer id, String username, String password, Integer age) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.age = age;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        System.out.println("生命周期:2、依赖注入");
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public void initMethod() {
        System.out.println("生命周期:3、初始化");
    }

    public void destroyMethod() {
        System.out.println("生命周期:4、销毁");
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", age=" + age +
                '}';
    }
}

后置处理:

public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        //此方法在bean的生命周期初始化之前执行
        System.out.println("MyBeanPostProcessor-->后置处理postProcessBeforeInitialization");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        //此方法在bean的生命周期初始化之后执行
        System.out.println("MyBeanPostProcessor-->后置处理postProcessAfterInitialization");
        return bean;
    }
}

测试方法:

/**
 * 1、实例化,调用无参构造
 * 2、实例注入,调用set
 * 3、后置处理的postProcessBeforeInitialization方法
 * 4、初始化,需要通过bean的init-method属性指定初始化方法
 *    使用bean
 * 5、后置处理的postProcessAfterInitialization方法
 * 6、IOC容器关闭时销毁,需要使用bean的destroy-method属性指定销毁方法
 *
 * bean的作用域是单例时,在创建IOC容器时,bean实例就会被创建
 * 作用域是多例时,只有在获取bean实例时才会被创建
 *
 * bean的后置处理器会在生命周期的初始化前后添加额外的操作,
 * 需要实现BeanPostProcessor接口,
 * 且配置到IOC容器中,需要注意的是,bean后置处理器不是单独针对某一个bean生效,
 * 而是针对IOC容器中所有bean都会执行
 */
@Test
public void test(){
    //ClassPathXmlApplicationContext扩展了刷新和销毁的子方法
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-lifecycle.xml");
    User user = ioc.getBean(User.class);
    System.out.println("获取bean并使用:"+user);
    ioc.close();
}

结果:

image-20220803165753224

3.7 工厂bean
概念

FactoryBean是Spring提供的一种整合第三方框架的常用机制。

和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。

通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。

将来我们整合Mybatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。

一句话:IOC容器会创建工厂bean getObject方法返回的实例类型,不会去创建工厂bean的实例。这样我们直接从ioc容器中获取工厂创建的实例对象

实现FactoryBean接口

接口中的三个方法:

  • getObject():返回一个对象给IOC容器
  • getObjectType():设置所提供对象的类型
  • isSingleton():所提供的对象是否为单例

当把FactoryBean的实现类配置为bean时,会将当前类中的getObject方法返回的对象交给IOC容器管理

public class UserFactoryBean implements FactoryBean<User> {
    @Override
    public User getObject() throws Exception {
        return new User();
    }

    @Override
    public Class<?> getObjectType() {
        return User.class;
    }
}

public class FactoryTest {

    @Test
    public void test(){
//         在配置文件中只需要配置FactoryBean即可
//        当把FactoryBean的实现类配置为bean时,
//        真正交给IOC容器管理的对象,是FactoryBean工厂中getObject方法返回的对象
//        也就是说,省略了传统的工厂模式从工厂实例中获取产品的步骤,
//        而是直接把工厂的产品交给了ioc容器管理
//        另外,还可以设置是否为单例
        ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-factory.xml");
        User user = ioc.getBean(User.class);
        System.out.println(user);
    }
}
3.8 自动装配⭐️
概念

根据指定的策略,在IOC容器中匹配某个bean,自动为为bean中的类类型的属性或者接口类型的属性赋值

实现

可以通过bean标签的autowire属性设置自动装配的策略

自动装配的策略:

  1. no,default:表示不装配,即bean中的属性不会自动匹配某个bean为某个属性赋值

  2. byType:根据赋值的属性的类型,在IOC容器中匹配某个bean为属性赋值

    异常情况:

    • IOC中一个类型都匹配不上:属性就不会装配,使用默认值
    • 有多个类型的bean,此时会抛出异常
    • 总结:当使用ByType实现自动装配时,IOC容器中有且仅有一个类型匹配的bean能够为属性赋值
  3. byName:将要赋值的属性的属性名作为bean的id在IOC容器中匹配某个bean,为属性赋值

  4. 总结:一般使用byType。特殊情况下:当类型匹配的bean有多个时,此时可以使用byName实现自动装配

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="com.zylai.spring.controller.UserController"
          autowire="byType">
        <!--<property name="userService" ref="userService"/>-->
    </bean>

    <bean id="userService"
          class="com.zylai.spring.service.impl.UserServiceImpl"
          autowire="byType">
        <!--<property name="userDao" ref="userDao"/>-->
    </bean>

    <bean id="userDao" class="com.zylai.spring.dao.impl.UserDaoImpl"></bean>
</beans>

4 IOC-基于注解管理Bean👾

4.1 使用注解注册bean组件
  1. @Component:将类标识为普通组件
  2. @Controller:将类标识为控制层组件
  3. @Service:将类标识为业务层组件
  4. @Repository:将类标识为持久层组件

这四个注解本质和功能上完全一样,后面三个相当于Component改了个名字,但是对于开发人员便于理解

注意:

  • 在service层和dao层,注解应该标识在接口的实现类上

  • 加了注解的类在IOC容器中的默认id为类名的小驼峰

4.2 扫描组件
    <!--开启组件扫描-->
<!--    <contxt:component-scan base-package="com.zylai.spring.controller,com.zylai.spring.service.impl,-->
<!--com.zylai.spring.dao.impl"/>-->
<!--    这个包下面下的所有类都会扫描-->
    <contxt:component-scan base-package="com.zylai.spring">
        <!--contxt:exclude-filter 排除扫描,设置不扫描哪些
            annotation:根据注解类型进行排除,expression中设置排除的注解的全类名
            assignable:根据类名进行排除,expression中设置排除的类的全类名
            -->
        <!--<contxt:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>-->
        <!--<contxt:exclude-filter type="assignable" expression="com.zylai.spring.controller.UserController"/>-->

        <!--contxt:include-filter 包含扫描,设置只扫描谁
            注意:需要在contxt:component-scan标签中设置属性use-default-filters="false",
            为false时,设置的包下面所有的类都不需要扫描,此时可以使用包含扫描
            为true时(默认的),设置的包下面所有的类都进行扫描,此时可以使用排除扫描
            -->
        <!--<contxt:include-filter type="assignable" expression="org.springframework.stereotype.Service"/>-->
    </contxt:component-scan>

通过注解加扫描所配置的bean的id,默认值为类名的小驼峰,即类名的首字母为小写的结果,注意,接口应该是其实现类。可以通过标识组件注解的value属性设置bean的自定义的id

4.3 @Autowired自动装配
4.3.1 能够标识的位置
  • 成员变量上,此时不需要设置成员变量的set方法
  • set方法上
  • 为当前成员变量赋值的有参构造器上
4.3.2 注入的方式

@Autowired默认通过byType方式自动注入,在IOC容器中通过类型匹配某个bean为属性赋值

若有多个类型匹配的bean,此时会自动转化为byName的方式来实现自动装配的效果

即将要赋值的属性的属性名作为bean的id匹配某个bean为属性赋值

若byType和byName的方式都无法实现自动装配,即IOC容器中有多个类型匹配的bean,且这些bean的id和要复制的属性的属性名都不一致,此时抛异常。

此时可以在要赋值的属性上,添加一个注解@Qualifier("value")通过该注解的value属性值,指定某个bean的id,然后将这个bean为属性赋值

注意:若IOC容器中没有任何一个类型匹配bean,此时抛出异常:NoSuchBeanDefinitionException

在@Autowired注解中有个required属性,默认值为true,要求必须完成自动装配可以将required设置为false,此时能装配则装配,无法装配则使用属性的默认值

4.4 例子

controller:

@Controller
public class UserController {

    @Autowired
    private UserService userService;

    public void saveUser(){
        userService.saveUser();
    }
}

service

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Override
    public void saveUser() {
        System.out.println("保存信息-->service");
        userDao.saveUser();
    }
}

dao

@Repository
public class UserDaoImpl implements UserDao {
    @Override
    public void saveUser() {
        System.out.println("保存成功-->dao");
    }
}

image-20220803171641013

5 AOP-概念👾

5.1 代理模式
5.1.1 概念

二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。

让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。

image-20220728115238117

5.1.2 静态代理

静态代理的思想就是代理对象和目标对象都实现同一个接口,然后在代理对象中调用目标对象的方法。

接口:

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

目标对象:

public class CalculatorImpl implements Calculator{
    @Override
    public int add(int i, int j) {
        int result = i + j;
        System.out.println("方法内部,result:"+result);
        return result;
    }

    @Override
    public int sub(int i, int j) {
        int result = i - j;
        System.out.println("方法内部,result:"+result);
        return result;
    }

    @Override
    public int mul(int i, int j) {
        int result = i * j;
        System.out.println("方法内部,result:"+result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法内部,result:"+result);
        return result;
    }
}

代理对象:

public class CalculatorStaticProxy implements Calculator {
// 将被代理的目标对象声明为成员变量
    private Calculator target;
    public CalculatorStaticProxy(Calculator target) {
    this.target = target;
    }
    @Override
    public int add(int i, int j) {
    // 附加功能由代理类中的代理方法来实现
    System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);
    // 通过目标对象来实现核心业务逻辑
    int addResult = target.add(i, j);
    System.out.println("[日志] add 方法结束了,结果是:" + addResult);
        return addResult;
    }
}

可以看到,通过静态代理,达到了我们想要的目标。

缺点:代理都写死了,不具备灵活性。比如将来要给其他的类加上这个日志功能,那么还需要创建很多的代理类,产生大量重复的代码。

5.1.3 动态代理

比如说,将日志功能都集中到一个代理类中,将来有任何的日志需求,都通过这一个代理类实现。这里就用到了动态代理的技术。

使用JDK动态代理:

public class ProxyFactory {

    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxy(){

        //使用JDK动态代理
        /**
         * 必须给出类加载器,根据类加载器来创建类
         * ClassLoader loader:指定目标对象使用的类加载器
         * Class<?>[] interfaces:获取目标对象实现的所有接口的class对象的数组
         * InvocationHandler h :设置代理类中的抽象方法如何重写
         */
        ClassLoader classLoader = target.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        InvocationHandler h = new InvocationHandler() {

            //这里是设置代理类中的方法如何重写
            //proxy:表示代理对象,method表示要执行的方法,args表示要执行的方法的参数列表
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object res = null;
                try {
                    System.out.println("日志,方法:"+method.getName()+",参数:"+ Arrays.toString(args));
                    //调用目标对象的方法
                    res = method.invoke(target, args);
                    System.out.println("日志,方法:"+method.getName()+",结果:"+res);
                    return res;
                } catch (Exception e) {
                    e.printStackTrace();
                    System.out.println("日志,方法:"+method.getName()+",异常:"+e);
                } finally {
                    System.out.println("日志,方法:"+method.getName()+",方法执行完毕");
                }
                return res;
            }
        };
        return Proxy.newProxyInstance(classLoader,interfaces,h);
    }
}
ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());
Calculator proxy = (Calculator)proxyFactory.getProxy();
proxy.add(1,2);
5.2 AOP概念及相关术语
AOP

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

简单来说:就是把非核心业务抽取出来,给切面类管理。再把抽取出来的放到相应的位置。

横切关注点

从目标对象中抽取出来的非核心业务,比如之前代理模式中的日志功能,针对于计算器功能来说,日志就是非核心业务。

这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。

image-20220728150235449

通知

非核心的业务再目标对象中叫做横切关注点,将横切关注点抽取出来封装到切面类中,他就是这个类中的一个方法叫做通知。

每一个横切关注点要做的事情都封装成一个方法,这样的方法就叫做通知方法。

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

image-20220728150243765

切面

封装横切关注点的类,通知的方法都写在切面类中

image-20220728150255542

目标

被代理的目标对象,比如计算器的实现类

代理

代理对象

连接点

一个纯逻辑的概念:抽取横切关注点的位置,比如方法执行之前,方法捕获异常的时候等等。

连接点的作用:我们不但要抽取出来,还要套回去。

image-20220728150342960

切入点

定位连接点的方式。

总结

抽和套,抽取出来横切关注点,封装到切面中,就是一个通知。然后通过切入点找到连接点,把通知套到连接点的位置。

6 AOP-基于注解的AOP👾

image-20220728150653683

6.1 技术说明
  • 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。
  • cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
  • AspectJ:本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。
6.2 配置
maven依赖
<!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.1</version>
</dependency>
配置文件
  • 切面类和目标类都需要交给IOC容器管理
  • 切面类必须通过@Aspect注解标识为一个切面
  • 切面类必须通过@Aspect注解标识为一个切面
<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

<!--
    AOP的注意事项:
    切面类和目标类都需要交给IOC容器管理
    切面类必须通过@Aspect注解标识为一个切面
    在spring的配置文件中设置<aop:aspectj-autoproxy/>标签,开启基于注解的AOP功能
-->
    <context:component-scan base-package="com.zylai.spring.aop.annotation"/>

    <!--开启基于注解的AOP功能-->
    <aop:aspectj-autoproxy/>
</beans>
6.3 切面类(重点)
  1. 在切面中,需要通过指定的注解将方法标识为通知方法

    • @Before:前置通知,在目标方法执行之前执行
    • @After:后置通知,在目标对象方法的finally子句中执行
    • @AfterReturning:返回通知,在目标对象方法返回值之后执行
    • @AfterThrowing:异常通知,在目标对象方法的catch子句中执行
  2. 切入点表达式:设置在表示通知的注解的value属性中

    * "execution(public int com.zylai.spring.aop.annotation.CalculatorImpl.add(int,int))"
    * "execution(* com.zylai.spring.aop.annotation.*.*(..))"
    * 第一个*表示任意的访问修饰符和返回值类型
    * 第二个*表示包下所有的类
    * 第三个*表示类中任意的方法
    * ..表示任意的参数列表
    
  3. 重用连接点表达式

    * //声明一个公共的切入点表达式
    * @Pointcut("execution(* com.zylai.spring.aop.annotation.CalculatorImpl.*(..))")
    * public void pointCut(){}
    * 使用方式:  @After("pointCut()")
    
  4. 重用连接点表达式

    * 在通知方法的参数位置,设置JoinPoint类型的参数,就可以获取连接点所对应的方法信息
    *  //获取连接点对应方法的签名信息
    *  Signature signature = joinPoint.getSignature();
    *  //获取连接点所对应的参数
    *  Object[] args = joinPoint.getArgs();
    
  5. 切面的优先级

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

总的:

* 1. 在切面中,需要通过指定的注解将方法标识为通知方法

*
* 2. 切入点表达式:设置在表示通知的注解的value属性中
* "execution(public int com.zylai.spring.aop.annotation.CalculatorImpl.add(int,int))"
* "execution(* com.zylai.spring.aop.annotation.*.*(..))"
* 第一个*表示任意的访问修饰符和返回值类型
* 第二个*表示包下所有的类
* 第三个*表示类中任意的方法
* ..表示任意的参数列表
*
* 3.重用连接点表达式
* //声明一个公共的切入点表达式
* @Pointcut("execution(* com.zylai.spring.aop.annotation.CalculatorImpl.*(..))")
* public void pointCut(){}
* 使用方式:  @After("pointCut()")
*
*
* 4. 获取连接点信息
* 在通知方法的参数位置,设置JoinPoint类型的参数,就可以获取连接点所对应的方法信息
*  //获取连接点对应方法的签名信息
*  Signature signature = joinPoint.getSignature();
*  //获取连接点所对应的参数
*  Object[] args = joinPoint.getArgs();
*
*  5.切面的优先级
*  可以通过@Order注解的value属性设置优先级,默认值为Integer.MAX
*  value值越小优先级越高

@Component
@Aspect//将当前组件表示为切面
public class LoggerAspect {

    @Pointcut("execution(* com.zylai.spring.aop.annotation.CalculatorImpl.*(..))")
    public void pointCut(){}


//    @Before("execution(public int com.zylai.spring.aop.annotation.CalculatorImpl.add(int,int))")
    //表示这个类下所有的方法,用*表示所有,参数列表用..表示所有的参数列表
//    @Before("execution(* com.zylai.spring.aop.annotation.CalculatorImpl.*(..))")
    @Before("pointCut()")
    public void beforeAdviceMethod(JoinPoint joinPoint){
        //获取连接点对应方法的签名信息(签名信息就是方法的声明信息)
        Signature signature = joinPoint.getSignature();
        //获取连接点所对应的参数
        Object[] args = joinPoint.getArgs();
        System.out.println("LoggerAspect,前置通知,方法:"+signature.getName()+",参数:"+ Arrays.toString(args));
    }

    @After("pointCut()")
    public void afterAdviceMethod(JoinPoint joinPoint){
        //获取连接点对应方法的签名信息(签名信息就是方法的声明信息)
        Signature signature = joinPoint.getSignature();
        System.out.println("LoggerAspect,后置通知,方法:"+signature.getName()+",执行完毕");
    }

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

    //在返回通知中若要获取目标对象方法的异常,只需要通过注解的throwing属性值
    //就可以将通知方法的某个参数指定为接收目标对象方法出现的异常
    @AfterThrowing(value = "pointCut()",throwing = "ex")
    public void afterThrowingAdviceMethod(JoinPoint joinPoint,Throwable 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;
    }
}

7 声明式事务👾

在Spring中,提供了封装了jdbc的JdbcTemplate,在之后的事务模块中,我们使用JdbcTemplate执行SQL

4.1 JdbcTemplate
4.1.1 配置

maven

<!-- Spring 持久化层支持jar包 -->
<!-- Spring 在执行持久化层操作、与持久化层技术进行整合过程中,需要使用orm、jdbc、tx三个
jar包 -->
<!-- 导入 orm 包就可以通过 Maven 的依赖传递性把其他两个也导入 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>5.3.1</version>
</dependency>

配置文件:

<!--引入jdbc.properties,之后可以通过${key}的方式访问value-->
<!--这里的context标签,注意上面引入的-->
<!--在web项目中,有两种路径,一个是类路径,一个是web资源路径-->
<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 name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    <!--<property name="initialSize" value="${initialSize}"/>-->
    <!--<property name="maxActive" value="${maxActive}"/>-->
</bean>

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
</bean>
4.1.2 使用(在Spring中进行测试)🎾
//指定当前测试类在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,?,?,?,?,?)";
        int update = jdbcTemplate.update(sql, "root", "123", 23, "女", "123@163.com");
        System.out.println(update);
    }

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

    @Test
    public void testGetAllUser(){
        String sql = "select * from t_user";
        List<User> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
        System.out.println(list);
    }

    @Test
    public void testGetCount(){
        String sql = "select count(*) from t_user";
        Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
        System.out.println(count);
    }
}
4.2 声明式事务的概念
4.2.1 编程式事务

image-20220728153210291

4.2.2 声明式事务

既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。

封装起来的好处:

  • 好处1:提高开发效率
  • 好处2:消除了冗余的代码
  • 好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性能等各个方面的优化

编程式:自己写代码实现功能
声明式:通过配置让框架实现功能

4.3 基于注解的声明式事务
场景模拟

用户购买图书,先查询图书的价格,再更新图书的库存和用户的余额 假设用户id为1的用户,购买id为1的图书 ,用户余额为50,而图书价格为80 购买图书之后,用户的余额为-30,数据库中余额字段设置了无符号,因此无法将-30插入到余额字段 ,此时执行sql语句会抛出SQLException

加入事务
  1. 配置事务管理器,并且加上数据源属性
  2. 开启事务的注解驱动
<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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">


    <!--扫描组件-->
    <context:component-scan base-package="com.zylai.spring">
    </context:component-scan>
    <!-- 导入外部属性文件 -->
    <context:property-placeholder location="classpath:jdbc.properties" />
    <!-- 配置数据源 -->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <!-- 配置 JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 装配数据源 -->
        <property name="dataSource" ref="druidDataSource"/>
    </bean>

<!--    配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="druidDataSource"/>
    </bean>

<!--
    开启事务的注解驱动
    将使用@Transactional注解所标识的方法或类中所有的方法使用事务进行管理
    @Transactional注解在哪,连接点就在哪里
    transaction-manager属性设置事务管理器的id
    若事务管理器的id默认为transactionManager,则改属性可以不写
    -->
    <tx:annotation-driven transaction-manager="transactionManager"/>


</beans>

在需要进行事务操作的类或方法上加上注解即可

@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookDao bookDao;

    @Override
    @Transactional
    public void buyBook(Integer userId, Integer bookId) {
        //查询图书的价格
        Integer price = bookDao.getPriceByBookId(bookId);

        //更新图书的库存
        bookDao.updateStock(bookId);
        //更新用户的余额
        bookDao.updateBalance(userId,price);
    }
}

结果:如果更新图书的库存顺利执行,而更新用户余额执行失败,那么将会回滚事务,图书库存将会恢复。

4.4 事务的属性
1、只读
  1. 介绍

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

  2. 使用方式

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

    对增删改操作设置只读会抛出下面异常:
    Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed

2、超时
  1. 介绍:

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

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

  2. 使用方式:

    @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);
    }
    
  3. 观察结果
    执行过程中抛出异常:
    org.springframework.transaction.TransactionTimedOutException: Transaction timed out:
    deadline was Fri Jun 04 16:25:39 CST 2022

3、回滚策略

声明式事务默认对于运行时异常都进行回滚,一般使用的是在此基础上加上不因为哪个异常而回滚

可以通过@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);
}
4、隔离级别

数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。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)//串行化
5、事务的传播

简单来说:

结账,买两本书。

如果以结账时间为事务,第二本买失败,第一本一会回滚。

如果用买书本身的操作,就是能买几本就是几本。

详细介绍:

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

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

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

相关文章

Cannot read properties of undefined (reading ‘resetFields‘)“ 报错解决

遇到这种报错 先去相关页面搜索关键字 定位到具体的报错代码 Cannot read properties of undefined (reading ‘resetFields’)" 关键字&#xff1a;resetFields 此方法作用&#xff1a;对整个表单进行重置 将所有字段值重置为初始值并移除校验结果 报错场景&#xff1a;…

基于高质量训练数据,GPT-4 Turbo更出色更强大

11月7日消息&#xff0c;OpenAI在首届开发者大会上正式推出了GPT-4 Turbo。 与GPT-4相比&#xff0c;GPT-4 Turbo主要有6方面的提升&#xff1a; 1、扩展下文对话长度&#xff1a;GPT4最大只能支持8k的上下文长度&#xff08;约等于6000个单词&#xff09;&#xff0c;而GPT-4…

Go 异常处理流程

在 Go 语言中&#xff0c;panic、recover 和 defer 是用于处理异常情况的关键字。它们通常一起使用来实现对程序错误的处理和恢复。 1. defer 语句 defer 用于在函数返回之前执行一段代码。被 defer 修饰的语句或函数会在包含 defer 的函数执行完毕后执行。defer 常用于资源清…

Django学习日志09

choices参数的使用 """对于以上可能被我们列举完的字段我们一般都是选择使用choices参来做""" class UserInfo(models.Model):username models.CharField(max_length64)password models.CharField(max_length32)# 先写一个映射关系gender_cho…

电机应用开发-直流有刷电机速度环控制实现

直流有刷电机速度环控制实现 硬件设计 可选&#xff1a;L298N电机驱动板、野火MOS搭建的驱动板。 直流电机速度环控制-位置式PID实现 编程要点 配置定时器可以输出PWM控制电机 配置定时器可以读取编码器的计数值 配置基本定时器可以产生定时中断来执行PID运算 编写位置式PID算…

如何选择一款快速可靠的文件自动同步软件?

在企业的数据流转管控过程中&#xff0c;经常会遇到频繁的数据备份、同步&#xff0c;人工重复这样的工作程序&#xff0c;既繁琐又容易出错。越来越多的企业&#xff0c;要求内部各种业务数据在多台服务器之间、多个数据中心之间&#xff0c;乃至多云和本地之间调度和同步。很…

黔院长 | 中医上所说的虚实到底是什么?

虚实是中医上经常出现的词语&#xff0c;例如脾虚、肾虚等也都是我们经常听到的症状。各种病症的发生都有虚实的不同&#xff0c;那什么是虚实呢&#xff1f;在《黄帝内经》当中就有相关的叙述&#xff1a; 所谓虚实&#xff0c;是正气与邪气相比较而言的。什么是正什么是邪&am…

python 实现银行卡号查询银行名称和简称

本章教程利用python实现查询银行卡号名称和简称 。 目录 一、 实例代码 二、运行效果 一、 实例代码 #!/usr/bin/python # -*- coding: UTF-8 -*- """ author: Roc-xb desc: python 实现银行卡号查询银行简称 """ import requestsdef bank_mes…

Java入门篇 之 内部类

本篇碎碎念&#xff1a;本篇没有碎碎念&#xff0c;想分享一段话&#xff1a; 你不笨&#xff0c;你只是需要时间&#xff0c;耐心就是智慧&#xff0c;不见得快就好&#xff0c;如果方向都不对&#xff0c;如果心术不正&#xff0c;如果德不配位&#xff0c;快就是对自己天分的…

如何配置ESB单据集成接口

ESB企业服务总线在实际项目中主要用于各业务系统之间的集成&#xff0c;集成包括数据集成、应用集成以及业务单据集成等&#xff0c;ESB企业服务总线主要包含三部分&#xff1a;ESB设计器、SMC管理控制台以及Server运行环境&#xff0c;ESB设计器用于服务以及集成流程的开发&am…

关于sklearn的:还可能是网络的问题???

前提&#xff1a;安装sklearn需要 numpy、scipy等库&#xff08;这个自行搜索&#xff09; 昨天安装numpy、scipy很快&#xff0c;一会就好了&#xff0c;然后安装sklearn一直报错&#xff0c;还以为是版本问题。 今天大早上起来&#xff0c;再次安装&#xff0c;顺利成功&…

②⑩ 【MySQL Log】详解MySQL日志:错误日志、二进制日志、查询日志、慢查询日志

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ MySQL日志 ②⑩ MySQL日志&#xff1a;错误日志…

Speech | openSMILE语音特征提取工具

官方地址&#xff1a;openSMILE 3.0 - audEERING 使用指导&#xff1a;openSMILE — openSMILE Documentation (audeering.github.io) openSMILE 简介 openSMILE是一款以命令行形式运行的工具&#xff0c;通过配置config文件来提取音频特征。主要应用于语音识别、情感计算、音…

ERP对接淘宝/天猫/京东/拼多多商品详情数据API接口

引言 今天&#xff0c;我们时代变化非常快&#xff0c;传统行业做法&#xff0c;已经无法完全适应时代的发展。互联网的发展&#xff0c;造成了一股网购热。京东&#xff0c;天猫&#xff0c;淘宝&#xff0c;易购……网购&#xff0c;给我们生活带来了方便&#xff0c;消费者…

【产品安全平台】上海道宁与Cybellum将整个产品安全工作流程整合到一个专用平台中,保持构建的互联产品的网络安全和网络合规性

Cybellum将 整个产品安全工作流程 整合到一个专用平台中 使设备制造商能够 保持他们构建的互联产品的 网络安全和网络合规性 产品安全性对 每个人来说都不一样 每个行业的系统、工作流程和 法规都存在根本差异 因此&#xff0c;Cybellum量身定制了 Cybellum的平台和技…

如何看待程序员领域内的“内卷”现象?

要搞清楚这个问题&#xff0c;我首先就来阐释一下“内卷”的概念。 内卷本身是从一个学术名词演化为网络流行词的&#xff0c;本是指文化模式因达到某种最终形态&#xff0c;既无法保持稳定也不能转化为更高级的新形态&#xff0c;而只能在这种文化模式内部无限变得复杂的现象。…

Linux wait函数用法

wait 函数是用于等待子进程结束并获取子进程的终止状态的系统调用。它在父进程中使用&#xff0c;用于等待其子进程终止并获得子进程的退出状态。 函数原型&#xff1a; pid_t wait(int *status);status 是一个指向整型的指针&#xff0c;用于存储子进程终止时的退出状态&…

Redis 与其他数据库的不同之处 | Navicat

Redis&#xff0c;即远程字典服务器&#xff08;Remote Dictionary Server&#xff09;&#xff0c;它是一个多功能且高性能的键值存储系统&#xff0c;在数据库领域中已获得广泛关注和认可。在处理简单数据结构方面&#xff0c;它因其快速和高效而著称。本文中&#xff0c;我们…

使用 millis() 函数作为延迟的替代方法(电位器控制延迟时间)

接线图&#xff1a; 代码&#xff1a; unsigned long currentMillis 0; unsigned long previousMillis_LED1 0; unsigned long LED1_delay0; unsigned long previousMillis_LED2 0; unsigned long LED2_delay0; #define LED1 3 #define LED2 9 #define P1 A2 …

泛型边界的问题

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 我们花了两篇文章讲述了…