1.SpringFramework学习

1.SpringFramework学习

明廷盛 嘻嘻😁

第一章 学习SpringFramework的切入点

官网链接SpringFramework

  1. IOC(Inversion Of Control)控制反转: 指的是将对象的创建权交给Spring 去创建。使用Spring之前,对象的创建都是由我们自己在代码中new创建。而使用Spring之后。对象的创建都是由给了Spring框架。
  2. DI(Dependency Injection)依赖注入: 是指对象的属性不需要手动调用setXXX方法去设置,而是通过Configration赋值。
  3. AOP(Aspect Oriented Programming)面向切面编程: 在不修改源代码的基础上进行功能扩展。
  4. JDBCTemplate: Spring的”Mybatis”
  5. 声明式事务: 事务
    在开发中不成文的规定 ==约定:约束>配置【注解>XML】>代码==

|500

第二章 关于测试

第一节 Spring集成Junit4

  • ==一定要导入spring-test包==
1
2
3
@ContextConfiguration(locations = "classpath:applicationContext.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class TestSpringJunit4 {

第二节 Spring集成Junit5

  • 方式一:
1
2
3
@ContextConfiguration(locations = "classpath:applicationContext.xml")
@Extendwith(SpringExtension.class)
public class Test01 {}
  • 方式二[推荐]:
1
2
@SpringJUnitConfig(locations = "classpath:applicationContext.xml")
public class Test01 {}

第三章 HelloSpring

|25

第一节 STEP1: pom.xml配置

1
2
3
4
5
6
7
<!--pom.xml-->
<!--导入spring-context-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>

第二节 STEP2: applicationContext.xml配置

  • 配置文件名称:applicationContext.xml【beans.xml或spring.xml】
  • 配置文件路径:src/main/resources
1
2
3
4
5
6
7
8
9
10
11
12
13
<!--applicationContext.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<!--IOC(Inversion Of Control)控制反转: bean相当于在容器中实例化对象-->
<bean id="StuMingTingSheng" class="com.mts.pojo.Student">
<!--DI(Dependence Injection)依赖注入: 相当于给这个对象中的属性赋值-->
<property name="stuName" value="明廷盛"/>
<property name="stuNumber" value="2240110323"/>
</bean>
</beans>

第三节 STEP3: 如何测试

1
2
3
4
5
// 1.创建容器对象
ApplicationContext springIOC = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2.从容器中根据id拿对象
Student stuMingTingSheng = springIOC.getBean("StuMingTingSheng", Student.class);
System.out.println(stuMingTingSheng);

第四章 关于注解开发

注解是Spring中极为重要的特点, 如果要在Spring中使用注解就行开发的话, 需要告诉Spring需要在哪些文件中扫描注解;
①XML配置注解扫描, 需要配置applicationContext.xml文件;
②完全注解开发需要写一个Config类告诉Spring在哪写文件中开启了注解;

第一节 XML配置注解扫描

4.1.1 默认注解扫描

1
2
3
<!--applicationContext.xml-->
<!--扫描该包, 及其子包中的所有注解, 支持,指定多个包-->
<context:component-scan base-package="com.mts,com.nb"/>

4.1.2 包含扫描<context:include-filter>

  • 语法: 可以设置只扫描什么包(assignable), 或只扫描什么注解(annotation)

  • 注意⚠️: 使用包含扫描之前,必须设置use-default-filters=”false”【关闭当前包及其子包的扫描】

  • 示例代码:

1
2
3
4
5
6
<!--applicationContext.xml-->
<!--包含扫描-->
<context:component-scan base-package="com.mts" use-default-filters="false">
<context:include-filter
type="annotation" expression="org.springframework.stereotype.Component"/>
</context:component-scan>

4.1.3 排除扫描<context:exclude-filter>

  • 语法: 可以设置==不==扫描什么包(assignable), 或只扫描什么注解(annotation)
  • 注意⚠️: 这个不用use-default-filters=”false”【易错】
  • 示例代码:
1
2
3
4
5
6
<!--applicationContext.xml-->
<!--排除扫描-->
<context:component-scan base-package="com.mts" >
<context:exclude-filter
type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>

第二节 完全注解开发的使用步骤

4.2.1 STEP1: 创建配置类 (例如名字叫做SpringConfig)

4.2.2 STEP2: 在class上面添加注解

  • @Configuration:标识当前类是一个配置类,作用:代替XML配置文件
  • @ComponentScan:设置组件扫描当前包及其子包
1
2
3
4
/*SpringConfig.java*/
@Configuration
@ComponentScan(basePackages = "com.mts")
public class SpringConfig {}

4.2.3 STEP3: 测试

  • 测试时不再使用ClassPathXmlApplicationContext("applicationContext.xml");

    改为 AnnotationConfigApplicationContext(SpringConfig.class);

1
2
3
/*test.java*/
ApplicationContext ioc = new AnnotationConfigApplicationContext(SpringConfig.class);
DeptDaoImpl deptDao = ioc.getBean("deptDao", DeptDaoImpl.class);

第三节 完全注解开发如何扫描包

4.3.1 包含扫描

  • 示例代码: 需求: 只扫描@Repository注解
1
2
3
4
5
6
7
/*SpringConfig.java*/
@Configuration
@ComponentScan(basePackages = "com.mts",
useDefaultFilters = false,
includeFilters =
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Repository.class))
public class SpringConfig {}

4.3.2 排除扫描

  • 示例代码: 需求: 不扫描DeptDaoImpl这个实现类中的注解
1
2
3
4
5
/*SpringConfig.java*/
@Configuration
@ComponentScan(basePackages = "com.mts",
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = DeptDaoImpl.class))
public class SpringConfig {}

第五章 IOC

在容器(applicationContext.xml)中创建对象; 开发人员可以在开发中从配置文件中获取。
bean既然是对象, 那么就有作用域和生命周期等

第一节 如何在容器中创建对象

5.1.1 XML创建对象

  1. 普通创建

    • 标签

      <bean>
      集合/映射<util:list> <util:map>
    • 属性

      属性作用
      idbean的唯一标识
      class定义bean的类型【class全类名】
    • 注意⚠️: 只要调用了这个标签, 绝对会调用这个对象的构造方法, 也只有这个标签可以调用构造方法

      1
      <bean id="StuMingTingSheng" class="com.mts.pojo.Student"></bean>
  2. 干预创建(FactoryBean)

    • 使用场景: bean不是只针对于pojo对象, 是针对所有对象, 有些对象可能不需要get和set方法, 只是想创建出来; 又或者我创建的时候想打印一句话, 之类的; 此时我们就可以干预对象的创建

    • 干预创建的步骤如下:

      1. STEP1: 创建一个类继承FactoryBean<Object> Object是你想对哪个对象的创建进行干预, 并重写其中的getObject()getObjectType()方法

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        /*DeptFactoryBean.java*/
        package com.mts.factory;

        import com.mts.pojo.Dept;
        import org.springframework.beans.factory.FactoryBean;

        public class DeptFactoryBean implements FactoryBean<Dept> {

        @Override
        public Dept getObject() throws Exception {
        Dept dept = new Dept(1, "校长部", null, null);
        System.out.println("这里是通过FactoryBean干扰创建的Dept类");
        return dept;
        }

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

        }
      2. STEP2: 在applicationcontext.xml中, 正常调用<bean>标签, 只不过class写刚刚继承的类

        1
        <bean id="factoryBean" class="com.mts.factory.DeptFactoryBean"/>
      3. STEP3: 测试

        1
        2
        3
        4
        /*FactoryBeanTest.java*/
        ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext_factory.xml");
        Dept factoryBean = ioc.getBean("factoryBean", Dept.class); // 注意这里的对象是Dept
        System.out.println(factoryBean);

5.2.1 注解创建对象

不完全注解开发,需要写一个applicationContext.xml文件当中开启组件扫描

1
2
<!--开启组件扫描 base-package:设置扫描注解包名【当前包及其子包】-->
<context:component-scan base-package="com.atguigu"></context:component-scan>
  • 注解写的位置: 在类上面标识

  • 装配对象四个注解

    • @Component:装配普通组件(pojo等)到IOC容器
    • @Repository:装配持久化层组件(Dao等)到IOC容器
    • @Service:装配业务逻辑层组件(Service等)到IOC容器
    • @Controller:装配控制层|表示层组件(Servlet等)到IOC容器
  • 对应关系:

    1. beanIId:
      • 可以用value值指定; @Component(value="deptMing")
      • 当注解中只使用一个value属性时,value关键字可省略; @Component("deptMing")
      • 不指定默认为类名首字母小写 @Component
    2. class: 注解写的位置: 在类上面标识.
  • 注意⚠️:

    1. Spring本身不区分四个注解【四个注解本质是一样的@Component】,提供四个注解的目的只有一个:提高代码的可读性
    2. 这样装配的对象, 都在同一个applicationContext.xml里面, 注意不要重名

第二节 如何从容器中获取对象

  1. STEP1: 获取容器

    接口:

    • BeanFactory:①IOC容器的基本实现,是Spring内部的使用接口 ②是面向Spring本身的,不是提供给开发人员使用的。
    • **ApplicationContext:**①BeanFactory的子接口,提供了更多高级特性。面向开发人员
    • ConfigurableApplicationContext:①提供了手动关闭容器的方法

    方法:

    • **ClassPathXmlApplicationContext:**基于类路径检索Spring配置文件(.xml)
    • FileSystemXmlApplicationContext:基于文件系统检索
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
        ApplicationContext springIOC = new ClassPathXmlApplicationContext("applicationContext.xml");

    9. **STEP2: 获取容器中的对象**

    1. **getBean(String beanId,Clazz clazz):通过beanId和Class获取对象**推荐使用

    ```java
    Student stuMingTingSheng = springIOC.getBean("StuMingTingSheng", Student.class);

    ### 第三节 bean的作用域

    * 语法: 在`<bean>`标签中, 添加`scope`属性(Singleton, prototype, request, session)
    * 四个作用域
    * **singleton【default】:单例**【在容器中无论getBean("idName")多少次, 都是获取同一个对象】
    - 对象创建时机:**创建容器对象时**,创建对象执行
    * **prototype:多例**【在容器中getBean("idName")一次, 就获取一个对象, 每个对象之间不同】
    - 对象创建时机:**getBean()方法被调用时**,创建对象执行
    * **request:请求域**【Spring Framework中没有】
    - 当前请求有效,离开请求域失效
    - 当前请求:**URL不变即为当前请求**
    * **session:会话域**【Spring Framework中没有】
    - 当前会话有效,离开当前会话失效
    - 当前会话:**当前浏览不关闭不更换即为当前会话**

    ### 第四节 bean的生命周期

    > bean的完整生命周期如图表格**列**所示, 其中如果不重写, 会调用spring默认的方法

    ![](https://20040424.xyz/PicList/20250215161617748.png)

    #### 5.4.1 重写init/destroy方法

    * ①使用`<bean>`标签, 调用构造方法, 实例化对象
    * ②为bean的属性设置值和对其他bean的引用 `<property name="stuName" value="Tom"/>`
    * ③调用 `init-method()`方法, 必须在POJO中写这个方法, 同时在`<bean>`中init-method属性指定该方法.
    * ④使用该`<bean>`对象
    * ⑤**当容器关闭时**,调用bean的销毁方法 `destroy-method()`, 同③

    ```java
    /*Student.java*/
    public class Student() {
    ...
    public void init_method(){ System.out.println("2.调用初始化方法");
    public void destroy_method() { System.out.println("3.调用摧毁方法"); }
    ...
    }
1
2
3
4
5
6
7
8
<!--applicationContext_lifeCycle.xml-->
<!--需要指定init和destroy方法是哪一个-->
<bean id="lifeCycle" class="com.mts.pojo.Student"
init-method="init_method"
destroy-method="destroy_method">
<property name="stuName" value="Tom"/>
<property name="stuNum" value="101"/>
</bean>
1
2
3
4
5
/*test.java*/
/*使用ConfigurableApplicationContext才能关闭容器*/
ConfigurableApplicationContext iocConfigurable = new ClassPathXmlApplicationContext("applicationContext_lifeCycle.xml");
Student lifeCycle = iocConfigurable.getBean("lifeCycle", Student.class);
iocConfigurable.close();

5.4.2 重写初始化前后方法(Post Processor)

  • 作用: 在调用初始化方法前后对bean进行额外的处理。
  • 实现步骤:
    • ①新写一个类, 实现BeanPostProcessor接口
    • ②重写postProcessBeforeInitialization(Object, String)postProcessAfterInitialization(Object, String)方法
    • 在容器中装配后置处理器
  • 注意: 装配后置处理器会为*当前容器(yyy.xml)中所有*<bean>**均装配,不能为局部bean装配后置处理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*PostProcessor*/
public class PostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("==>在初始化之前");
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("==>在初始化之后");
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
1
2
3
4
<!--applicationContext_lifeCycle.xml-->
...
<bean class="com.mts.processor.PostProcessor"/>
...

第六章 DI

给对象赋值

第一节 XML为对象的属性赋值

6.1.1 对象入参的两种方式

  1. 通过构造方法入参 <constructor-arg>
属性作用
name给这个对象的哪个属性赋值
value值是什么
ref引用(当这个属性, 不是字面量值的时候用到)
  • 注意⚠️: ①该标签是通过有参构造注入的(没有对应的有参构造会报错)

  • 示例代码: 涉及有参构造重载时的操作

1
2
3
4
5
6
7
8
9
10
11
12
/*Student.java*/
public Student(Integer stuNumber, String stuName) {
System.out.println("第一种构造方法 Integer stuNumber, String stuName");
StuNumber = stuNumber;
StuName = stuName;
}

public Student(String stuName, Integer stuNumber) {
System.out.println("第二种构造方法 String stuName, Integer stuNumber");
StuNumber = stuNumber;
StuName = stuName;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--applictaionContext.xml-->
<!--情况一:没有重载(用name指定入参)-->
<bean id="StuYouCan" class="com.mts.pojo.Student">
<constructor-arg name="stuName" value="YouCan"/>
<constructor-arg name="stuNumber" value="1000"/>
</bean>

<!--情况二:有重载(写的顺序决定)-->
<bean id="StuYouCan1" class="com.mts.pojo.Student"> <!--第一种构造-->
<constructor-arg value="1"/>
<constructor-arg value="YouCan1" />
</bean>
<bean id="StuYouCan1" class="com.mts.pojo.Student"> <!--第二种构造-->
<constructor-arg value="YouCan2"/>
<constructor-arg value="2" />
</bean>
  1. 通过xxxSet方法入参 <property>
  • 属性: 同上

  • 注意⚠️: 此时Student这个类一定要有无差构造方法+对应的set方法, 否则报错

  • 示例代码:

1
2
3
4
<bean id="StuMingTingSheng" class="com.mts.pojo.Student">
<property name="stuName" value="明廷盛"/>
<property name="stuNumber" value="2240110323"/>
</bean>

6.1.2 字面量值入参

  • 字面量数值: 基本数据类型(int), 包装类(Integer), String

  • 语法: 使用<property>标签的value属性或value标签

  • 示例代码:

1
2
3
4
5
6
<!--value属性-->
<property name="stuName" value="明廷盛"/>
<!--value标签: 有特殊字符(像'<','>)需要用到`<![CDATA[]]>`区域-->
<property name="stuName">
<value><![CDATA[<明廷盛>🐂🍺]]></value>
</property>

6.1.3 非字面量值入参

  • 非字面量值: 除了字面量都是非字面量

  • 语法: ①使用<property>标签的ref属性 ②使用级联 ③使用内部bean

  • 注意⚠️: 所有的对象想要实例化, 必须使用<bean>标签;所以就算级联也得先引用一个空bean

  • 示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    <!--applicationContext.xml-->
    <!--方式一: 使用`ref`属性-->
    <bean id="deptMing" class="com.mts.pojo.Dept">
    <property name="deptId" value="3"/>
    <property name="deptName" value="开发部"/>
    </bean>

    <bean id="employeeMing" class="com.mts.pojo.Employee">
    <property name="id" value="1"/>
    <property name="lastName" value="Ming"/>
    <property name="email" value="[email protected]"/>
    <property name="deptId" value="3"/>
    <property name="salary" value="32.32"/>
    <property name="dept" ref="deptMing"/>
    </bean>

    <!--方式二: 使用级联-->
    <bean id="deptCaiGou" class="com.mts.pojo.Dept"/>

    <bean id="employeeLv" class="com.mts.pojo.Employee">
    <property name="id" value="2"/>
    <property name="lastName" value="Lv"/>
    <property name="email" value="[email protected]"/>
    <property name="deptId" value="3"/>
    <property name="salary" value="569.4"/>
    <property name="dept" ref="deptCaiGou"/>
    <!--级联修改-->
    <property name="dept.deptName" value="开发部" />
    <property name="dept.deptId" value="3"/>
    </bean>

    <!--方式三: 使用内部bean-->
    <bean id="employeePan" class="com.mts.pojo.Employee">
    <property name="id" value="3"/>
    <property name="lastName" value="Pan"/>
    <property name="email" value="[email protected]"/>
    <property name="deptId" value="3"/>
    <property name="salary" value="569.4"/>
    <!--内部bean-->
    <property name="dept">
    <bean class="com.mts.pojo.Dept">
    <property name="deptName" value="秘书部"/>
    <property name="deptId" value="3"/>
    </bean>
    </property>
    </bean>

6.1.4 List集合

  • 语法: ①内部list(使用<list>标签) ②外部引用list(ref一个<util:list>标签)

  • 注意⚠️: 使用<util:>标签需要导包xmlns:util="http://www.springframework.org/schema/util"

  • 示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    <!--applicationContext.xml-->
    <!--方式一: 内部List-->
    <bean id="deptQian" class="com.mts.pojo.Dept">
    <property name="deptId" value="10"/>
    <property name="deptName" value="前台部"/>
    <property name="employeeList">
    <list>
    <ref bean="employeeMing"/>
    <ref bean="employeeLv"/>
    <ref bean="employeePan"/>
    </list>
    </property>
    </bean>


    <!--方式二: 外部引用list-->
    <util:list id="deptQianList"> <!--需要导util的包-->
    <ref bean="employeeMing"/>
    <ref bean="employeeLv"/>
    <ref bean="employeePan"/>
    </util:list>

    <bean id="deptQian2" class="com.mts.pojo.Dept">
    <property name="deptId" value="10"/>
    <property name="deptName" value="前台部"/>
    <property name="employeeList" ref="deptQianList">
    </property>
    </bean>

6.1.5 Map映射

  • 语法: ①内部map(使用<map><entry>标签) ②外部引用list(ref一个<util:map>标签)
  • 示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!--applicationContext.xml-->
<!--方式一: 内部Map-->
<bean id="deptHou" class="com.mts.pojo.Dept">
<property name="deptId" value="10"/>
<property name="deptName" value="前台部"/>
<property name="employeeMap">
<map>
<entry key="01" value-ref="employeeMing"/>
<entry key="02" value-ref="employeeLv"/>
</map>
</property>
</bean>

<!--方式二: 外部Map-->
<util:map id="deptHouMap">
<entry key="01" value-ref="employeeMing"/>
<entry key="02" value-ref="employeeLv"/>
</util:map>

<bean id="deptHou2" class="com.mts.pojo.Dept">
<property name="deptId" value="10"/>
<property name="deptName" value="前台部"/>
<property name="employeeMap" ref="deptHouMap">
</property>
</bean>

6.1.6 第三方的Bean

和上面的一样, 只是需要注意**①需要第三方的Bean这个class是什么** ②property的name属性是什么

  • 语法: 导入property文件, 使用<context:property-placeholder>标签, 用${key}引入值
  • 示例代码: 引入外部properties文件, 装配德鲁伊的数据库连接池
1
2
3
4
5
6
7
8
9
<!--applicationcontext.xml-->
<context:property-placeholder location="classpath:db.properties"/>

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${db.driverClassName}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</bean>

6.4.7 通过XML自动装配非字面量值

  • 语法: 在<bean>标签中指定auto-wire属性
auto-wire属性规则
byName如果属性名标签的bean ld属性唯一匹配,则自动装配
byType对象中属性类型与bean标签的class属性唯一匹配,则自动装配
  • 注意⚠️:

    1. 注意是唯一匹配, 如果匹配0个或多个都会报错
    2. 基于XML方式的自动装配,只能装配非字面量数值
    3. 基于xml自动装配,底层使用set注入
    4. 不建议使用XML的自动装配, 建议使用注解
  • 示例代码: 模拟三层, 在serviceImpl中要创建daoImpl对象, 我们不new, 使用自动装配来完成

1
2
3
4
5
6
7
/*DeptServiceImpl.java*/
public class DeptServiceImpl implements DeptService {
//以前都是new出来,但现在学了spring不允许在这new了,去xml自动装配
private DeptDao deptDao;
//XML的自动装配也是需要set方法的
//省略 ...DeptDao的get和set方法
}
1
2
3
4
5
6
7
8
9
<!--applicationContext.xml-->
<bean id="deptDao" class="com.mts.dao.impl.DeptDaoImpl"/>
<!--自动装配-->
<bean id="DeptService" class="com.mts.service.impl.DeptServiceImpl" autowire="byName"/>

<!--手动装配-->
<bean id="DeptService" class="com.mts.service.impl.DeptServiceImpl">
<property name="deptDao" ref="deptDao"/> <!--如果不用自动装配, 需要像上面一样手动ref-->
</bean>

第二节 注解为对象的属性赋值

6.2.1 非字面量值自动装配 @Autowired

  • 语法: 在需要自动装配的成员变量上方加上@Autowired注解
  • 底层装配原理: 反射机制
  • 底层装配规则: 先byType后byName (byType唯一匹配装配成功; 多个匹配通过byName筛选; 没有匹配报错)
  • 注意⚠️: 成功装配的大前提是容器中有这个Bean
  • 示例代码:
1
2
3
/*DeptServiceImpl.java*/
@Autowired
private DeptDao deptDao;

6.2.2 非字面量值指定装配@Qualifier

  • 作用: 配合@Autowired一起使用,将指定beanId名称装配到属性中

  • 注意⚠️: 不能单独使用, 需要与@Autowired一起使用

  • 示例代码:

1
2
3
4
5
/*DeptServiceImpl.java*/

@Autowired
@Qualifier(value="deptDao") /*名字不匹配,用Qualifier手动指定*/
private DeptDao deptDao2;

6.2.3 字面量值 @Value

  • 作用:装配对象中属性【字面量数值】
  • 示例代码:
1
2
3
4
5
6
7
8
/*Dept.java*/
public class Dept {
@Value("32")
Integer deptId;

@Value("Tom")
String deptName;
}

第七章 AOP

OOP:Object-Oriented Programming,面向对象编程
AOP:Aspect-Oriented Programming,面向切面编程【面向对象一种补充】>

  • 优势:解决代码分散问题, 解决代码混乱问题

第一节 JDK、Cglib实现动态代理

7.1.1 jdk动态代理

  • 适用: 存在接口, 为其实现类进行代理
  • 底层: 代理类实现接口
  • 示例代码: 需求: 给Calclator这个类中的每个方法运算前后增加日志(使用代理)

image-20240825175245644

1
2
3
4
5
6
7
/*Calc*/
public interface Calc {
int add(int a, int b);
int sub(int a, int b);
int mul(int a, int b);
double div(int a, int b);
}
1
2
3
4
5
6
7
8
9
10
11
/*CalcImpl*/
public class CalcImpl implements Calc {
@Override
public int add(int a, int b) { return a + b;}
@Override
public int sub(int a, int b) {return a - b;}
@Override
public int mul(int a, int b) {return a * b;}
@Override
public double div(int a, int b) {return (double) a / (double) b;}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*JdkProxy*/
public class JdkProxy {
private Object target;

// 创建这个类必须要初始化目标类
public JdkProxy(Object target) {
this.target = target;
}

// 获取代理类
public Object getTargetProxy() {
Object resProxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),// 目标类的类加载器
target.getClass().getInterfaces(), // 目标类实现的接口
new InvocationHandler() { // 如何代理目标类
/**
*
* @param o: 代理类
* @param method: 目标类中哪个方法
* @param objects: 目标类中该方法的参数
* @return 该方法的返回值
* @throws Throwable 异常抛出
*/
@Override
public Object invoke(Object o, Method method, Object[] objects)
throws Throwable {
Logging.beforeCalculate(method.getName(), objects); // 执行方法前
Object res = method.invoke(target, objects); // 执行方法
Logging.afterCalculate(method.getName(), res); // 执行方法后
return res;
}
});
return resProxy;
}
}
1
2
3
4
5
6
7
8
9
10
11
/*测试代理类*/
public class JdkTest {
public static void main(String[] args) {
// 生成JDK底层代理类 放在main下才生效
// System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
JdkProxy jdkProxy = new JdkProxy(new CalcImpl());
Calc CalcProxy = (Calc) jdkProxy.getTargetProxy();
double add = CalcProxy.add(1, 2);
}
}

7.1.2 cglib动态代理

  • 适用: 是否实现接口都可以用
  • 底层: 继承目标类, 完成代理
  • 示例代码: 需求: 给Calclator这个类中的每个方法运算前后增加日志(使用代理)

image-20240825175235097

1
2
3
4
5
6
7
<!--pom.xml-->
<!--cglib-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
/*Calclator*/
public class Calclator{
public int add(int a, int b) {
int res = a + b;
mul(a,b); // 测试调用本类是否有增强
return res;
}
public int sub(int a, int b) {return a - b;}
public int mul(int a, int b) {return a * b;}
public double div(int a, int b) {return (double) a / (double) b;}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class CglibProxy {
private Object target;

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

public Object getTargetProxy() {
Enhancer enhancer = new Enhancer(); // Enhancer用于生成代理类
enhancer.setSuperclass(target.getClass()); // 告知目标类
// 告知需求
enhancer.setCallback(new MethodInterceptor() {
/**
* @param o: 代理类
* @param method: 目标类中哪个方法
* @param objects: 目标类中该方法的参数
* @param methodProxy: cglib给我们的执行函数的对象
* @return 该方法的返回值
* @throws Throwable 异常抛出
*/
@Override
public Object intercept(Object o, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
Logging.beforeCalculate(method.getName(), objects); // 执行方法前

// 目标类中方法内部调用 会有增强
// Object res = methodProxy.invokeSuper(o, objects);
// 目标类中方法内部调用 不会有增强
Object res = methodProxy.invoke(target, objects); // 执行方法

Logging.afterCalculate(method.getName(), res); // 执行方法后
return res;
}
});
Object targetProxy = enhancer.create(); // 生成代理类
return targetProxy;
}
}
1
2
3
4
5
6
7
8
9
10
11
/*CglibTest*/
public class CglibTest {
public static void main(String[] args) {
// 生成Cglib底层代理类 放在main下才生效
// System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./");

CglibProxy cglibProxy = new CglibProxy(new Calclator());
Calclator targetProxy = (Calclator) cglibProxy.getTargetProxy();
int add = targetProxy.add(1, 2);
}
}

第二节 Spring Framework实现动态代理

①Spring AOP是用的AspectJ框架实现的
②Spring 实现AOP用的底层就是JDK(当目标类实现接口时)和Cglib(目标类没有实现接口时)

7.2.1 Spring中AOP相关术语

  1. 横切关注点:非核心业务代码【日志】,称之为横切关注点
  2. 切面(Aspect):将横切关注点提取到类中,这个类称之为切面类就是像日志这样的工具类
  3. 通知(Advice):将横切关注点提取到类中之后,横切关注点更名为:通知
  4. ==目标(Target)== :目标对象,指的是需要被代理的对象【实现类(CalcImpl)】
  5. 代理(Proxy):代理对象可以理解为:中介
  6. 连接点(Joinpoint):通知方法需要指定通知位置,这个位置称之为:连接点【通知之前】
  7. ==切入点(pointcut)==:通知方法需要指定通知位置,这个位置称之为:切入点【通知之后】

第三节 Spring AOP使用步骤

7.3.1 STEP1: pom.xml配置

  • Spring的AOP是AspectJ和Spring联合整合的;
1
2
3
4
5
6
7
<!--pom.xml-->
<!--spring-aspects的jar包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>

7.3.2 STEP2: applicationContext.xml编写

1
2
3
4
5
<!--applicationContext.xml-->
<!--开启组件扫描-->
<context:component-scan base-package="com.atguigu"/>
<!--开启AspectJ注解支持-->
<aop:aspectj-autoproxy/>

7.3.3 STEP3: 为切面类添加注解 @Aspect

  1. 必要的
    • @Component: 添加为bean
    • @Aspect: 标注当前类是一个切面类
  2. 可选的
    • @EnableAspectJAutoProxySpring AOP补档: 使用场景: ①想用cglib ②想调用代理类的方法而不是目标类的
属性默认值作用
proxyTargetClassfalse是否强制使用cglib动态代理
exposeProxyfalse是否暴露代理类
  • @Order: 使用场景: 当目标类有多个增强时, 切面类中这个注解中value越小的,越先通知
1
2
3
4
5
6
7
8
@Component
@Aspect
@EnableAspectJAutoProxy(proxyTargetClass = false, exposeProxy = true)
@Order(2)
public class Logging {

}

7.3.4 STEP4: 在切面类中编写通知注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Component  // 添加bean
@Aspect // 标注当前类是一个切面类
public class Logging {

@Pointcut("execution(* com.mts.CalcImpl.*(..))")
public void pointCut() {}

@Around(value = "pointCut()")
public static Object around(ProceedingJoinPoint proceedingJoinPoint) {
Object res=null;
try {
System.out.println("before");
res = proceedingJoinPoint.proceed();
System.out.println("after");
}catch (Throwable e){
System.out.println("error"+e);
}finally {
System.out.println("complete!");
}
return res;
}
}

第四节 通知注解

7.4.1 通知注解—切入点表达式 execution

  • 语法: "execution(权限修饰符 返回值类型 包名.类名.方法名(参数类型) )")
  • 通配符:
通配符可以代替
*可以代表任意 ①权限修饰符 ②返回值类型 ③包名 ④类名 ⑤方法名 ⚠️需要空格分开
..可以代表任意参数类型及参数个数
  • 示例代码: 以下举例切入点表达具体切入的点
1
2
3
4
5
6
7
8
// ①com.mts.aop包中的CalcImpl类的pulic的入参顺序为int,int的add方法 
@Pointcut("execution(public com.mts.aop.CalcImpl.add(int, int))")

// ②com.mts.aop包中的CalcImpl类的所有方法 [常用]
@Pointcut("execution(* com.mts.aop.CalcImpl.*(..) )")

// ③ com.example.service 包中除了以 Service 结尾的类的所有方法。
@Pointcut("execution(* com.example.service.*.*(..)) && !execution(* com.example.service.*Service.*(..))")

7.4.2 通知注解—重用切入点表达式 @PointCut

  • 语法: 使用 @PointCut 注解定义一个方法,该方法将包含一个切入点表达式。这个方法通常不包含实现,因为它只是用来声明切入点。
  • 示例代码:
1
2
3
4
5
6
7
// 定义切入点
@Pointcut("execution(* com.mts.aop.CalcImpl.*(..) )")
public void myPointCut(){}

// 引用切入点
@Before(value = "myPointCut()")
public void beforeMethod(JoinPoint joinPoint){}

7.4.3 通知注解—切入点对象 JoinPoint

  • 语法: 在使用以下四大注解时(前置通知, 返回通知, 异常通知, 后置通知), 方法必须入参JoinPoint对象
JoinPoint对象常用API作用
joinPoint.getSignature(); String获取方法签名【方法签名=方法名+参数列表】
joinPoint.getSignature().getName(); String通过方法签名, 获取方法名称
joinPoint.getArgs(); Object[]获取参数列表

7.4.4 通知注解—前置通知 @Before

  • 语法:@Before 目标方法执行之前执行特定的逻辑【如目标方法中有异常,会执行】
  • 示例代码: 需求: 在执行目标方法前, 打印参数列表
1
2
3
4
5
6
@Before(value = "myPointCut()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();//获取方法名称
Object[] args = joinPoint.getArgs(); //获取参数
System.out.println("==>Calc中"+methodName+"方法(),参数:"+ Arrays.toString(args));
}

7.4.5 通知注解—返回通知 @AfterReturnning

  • 语法:@AfterReturnning 目标方法执行之后执行特定的逻辑【如目标方法中有异常,不执行】
属性作用
returning String目标方法的返回值
  • 注意⚠️: ①returning属性与入参中参数名一致 (该属性并不是一定要有)
  • 示例代码: 需求: 在执行目标方法后, 打印执行结果
1
2
3
4
5
@AfterReturning(value = "myPointCut()",returning = "rs")
public void afterReturnning(JoinPoint joinPoint,Object rs){
String methodName = joinPoint.getSignature().getName();//获取方法名称
System.out.println("【返回通知】==>Calc中"+methodName+"方法,返回结果执行!结果:"+rs);
}

7.4.6 通知注解—异常通知 @AfterThrowing

  • 语法:@AfterThrowing 目标方法出现异常时执行【如目标方法中无异常,不执行】
属性作用
throwing String目标方法执行过程中出现的异常
  • 注意⚠️: ①throwing属性值与入参参数名一致 (该属性并不是一定要有)
  • 示例代码: 需求: 有bug, 打印异常信息
1
2
3
4
5
@AfterThrowing(value = "myPointCut()",throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Exception ex){
String methodName = joinPoint.getSignature().getName();//获取方法名称
System.out.println("【异常通知】==>Calc中"+methodName+"方法,出现异常时执行!异常:"+ex);
}

7.4.7 通知注解—后置通知@After

  • 语法:@After 目标方法所有通知执行之后执行【如目标方法中有异常,会执行】
  • 示例代码: 需求: 无论如何, 打印执行结果
1
2
3
4
5
6
@After("myPointCut()")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();//获取方法名称
Object[] args = joinPoint.getArgs(); //获取参数
System.out.println("==>Calc中"+methodName+"方法,之后执行!"+Arrays.toString(args));
}

7.4.8 通知注解—环绕通知@Around

  • 语法:@Around 前面四个通知的整合
  • 注意⚠️: ①参数中必须使用ProceedingJoinPoint ②必须将返回结果, 作为返回值(void🔙null)
  • 总结: 前置通知–>(异常通知/返回通知)–>后置通知【中间执行哪个看是否有异常】
  • 示例代码: 需求:整合前四个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Around(value = "myPointCut()")
public Object aroundMethod(ProceedingJoinPoint pjp){
String methodName = pjp.getSignature().getName(); //获取方法名称
Object[] args = pjp.getArgs(); //获取参数
Object rs = null; //定义返回值
try {
System.out.println("【前置通知】");
rs = pjp.proceed(); //调用目标方法
System.out.println("【返回通知】"); //返回通知【有异常不执行】
} catch (Throwable throwable) {
System.out.println("【异常通知】"+ throwable);
} finally {
System.out.println("【后置通知】");
}
return rs;
}

第八章 JDBCTemplate

Spring提供的JdbcTemplate是一个小型持久化层框架,简化Jdbc代码。之前学的Mybatis是一个半自动化的ORM持久化层框架

第一节 JdbcTemplate使用步骤

8.1.1 STEP1: pom.xml配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--spring-jdbc∈spring-orm-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.1</version>
</dependency>
<!--导入druid的jar包-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!--导入mysql的jar包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>

8.1.2 STEP2: applicationContext.xml配置

  1. 装配DruidDataSource的Bean
  2. 装配JdbcTemplate 的Bean
  • 注意: url后面的serverTimZone=UTC必须要写!!!
1
2
3
4
5
6
#db.properties
#key=value
db.driverClassName=com.mysql.cj.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/test_mybatis?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
db.username=root
db.password=root

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--applicationContext.xml-->
<!--引入properties文件-->
<context:property-placeholder location="classpath:db.properties"/>
<!--装配DruidDataSource-->
<bean id="dateSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${db.driverClassName}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</bean>
<!--装配JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dateSource"/>
</bean>

第二节 JdbcTemplate的常用API

常用API作用
update (String sql, Object... args) int增删改
batchUpdate (String sql,List<Object[]> args) int批量增删改
queryForObject (String sql,Class<T> clazz,Object... args) <T>查询单个数值
queryForObject (String sql,RowMapper<T> rm,Object... args) <T>查询单个对象
query (String sql,RowMapper<T> rm,Obejct... args) List<T>查询多个对象

8.2.1 增删改

  • 语法: jdbcTemplate.update(String sql, Object... args)
  • 注意⚠️:①JdbcTmplate默认是自动提交事务的
  • 示例代码:
1
2
3
4
5
6
7
8
9
10
11
// 增(返回值是操作的行数)
String sql = "insert into tbl_dept(dept_name,dept_id) values(?, ?)";
jdbcTemplate.update(sql, "后勤部",2);

// 删
sql = "delete from tbl_dept where dept_id = ?";
jdbcTemplate.update(sql, 4);

// 改
sql = "update tbl_dept set dept_name = ? where dept_id=?";
jdbcTemplate.update(sql, "后勤部", 2);

8.2.2 批量 增删改

  • 语法: jdbcTemplate.batchUpdate(String sql,List<Object[]> args)
  • 示例代码:
1
2
3
4
5
6
7
8
// 批量增删改 (批量插入员工信息)
sql = "insert into tbl_employee(last_name, email, salary) values (?,?,?)";
List<Object[]> employees = new ArrayList<>();
employees.add(new Object[]{"zhangSan", "[email protected]", 23.3});
employees.add(new Object[]{"liSi", "[email protected]", 33.3});
employees.add(new Object[]{"wangWu", "[email protected]", 383.3});

jdbcTemplate.batchUpdate(sql, employees);

8.2.3 查询单个数值

  • 语法: jdbcTemplate.queryForObject(String sql,Class clazz,Object... args)
  • 示例代码:
1
2
3
4
5
6
// 查询单个数值(查询员工数量)
public Integer getNumberOfEmployees() {
sql = "select count(*) from tbl_employee";
Integer employeeNum = jdbcTemplate.queryForObject(sql, Integer.class);
return employeeNum;
}

8.2.4 查询单个对象

  • 语法: jdbcTemplate.queryForObject(String sql,RowMapper<T> rm,Object... args)
  • 示例代码:
1
2
3
4
5
6
7
8
// 查询单个对象(查询id为1的员工信息)
public Employee findEmployeeById(Integer id) {
sql = "select id, last_name, email, salary from tbl_employee where id = ?";
// 创建RowMapper<T>
RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
Employee employee = jdbcTemplate.queryForObject(sql, rowMapper, 1);
System.out.println(employee);
}

8.2.5 查询多个对象

  • 语法: jdbcTemplate.query(String sql,RowMapper<T> rm,Obejct... args)
  • 示例代码:
1
2
3
4
5
6
7
// 查询多个对象(查询所有员工信息)
public List<Employee> findAll() {
sql = "select * from tbl_employee";
RowMapper<Employee> rowMapper2 = new BeanPropertyRowMapper<>(Employee.class);
List<Employee> employeeList = jdbcTemplate.query(sql, rowMapper2);
return employeeList;
}

第九章 Spring声明式事务

Spring的声明式事务是使用AOP管理事务, 解决了传统编程式事务的①事务代码分散②事务代码混乱 的问题

第一节 Spring声明式事务使用步骤

9.1.1 STEP1: pom.xml配置

  • 导的包就是Spring AOP的包
1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>

9.1.2 STEP2: applicationContext.xml配置

  • 装配事务管理器
  • 开启事务注解支持
1
2
3
4
5
6
7
<!--装配事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启事务注解支持
transaction默认值是transactionManager, 如果上面的id改了, 需要指定否则可以省略-->
<tx:annotation-driven transaction-manager="transactionManager"/>

9.1.3 STEP3: 为需要事务的方法添加注解 @Transactional

  • 语法: 使用@Transactional注解指明当前方法是开启事务的
  • 注意: 因为是基于AOP实现, 所以本质上是”代理类”帮我们完成的, 分清楚是cglib还是jdk, 在创建对象时是实现类还是接口
  • 示例代码: 需求: 买书: 检查book价格->修改库存->修改余额
1
2
3
4
5
6
@Transactional
public void purchase(String username, String isbn) {
Integer price = bookShopDao.findBookPriceByIsbn(isbn); // 1.查询book价格
bookShopDao.updateBookStock(isbn); // 2.修改库存
bookShopDao.updateUserAccount(username, price); // 3.修改余额
}

第二节 @Transactional注解的属性

属性作用
Propagation事务传播行为
Isolation事务的隔离等级
timeout事务超时时间(单位:s)
readOnly事务只读
rollbackFor/noRollbackFor事务的回滚

9.2.1 事务传播行为【Propagation】

  • 概念: 当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
  • 人话: 在事务A中的子事务B开启时, 是否重新挂起事务, 还是在原有的事务中继续运行
  • 注意⚠️:①比如你希望, 当运行到事务A中的子事务B时, 重新挂起事务, 是在事务B上面的@Transactional中写REQUIRES_NEW属性, 而不是A事务
  • 7种事务传播行为
传播属性描述
REQUIRED如果有事务在运行,当前的方法就在这个事务内运行;否则就启动一个新的事务,并在自己的事务内运行。
REQUIRES_NEW当前的方法*必须*启动新事务,并在自己的事务内运行;如果有事务正在运行,应该将它挂起。
SUPPORTS如果有事务在运行,当前的方法就在这个事务内运行,否则可以不运行在事务中。
NOT_SUPPORTED当前的方法不应该运行在事务中,如果有运行的事务将它挂起
MANDATORY当前的方法必须运行在事务中,如果没有正在运行的事务就抛出异常。
NEVER当前的方法不应该运行在事务中,如果有正在运行的事务就抛出异常。
NESTED如果有事务正在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则就启动一个新的事务,并在它自己的事务内运行。
  • 图解:

  • 示例代码: 需求: 两种使用场景, 对应两种事务的传播行为
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
1. 去结账时判断余额是否充足,余额不足:一本书都不能卖
@Transactional(propagation=Propagation.REQUIRED)
2. 去结账时判断余额是否充足,余额不足:最后导致余额不足的那本书,不让购买
@Transactional(propagation=Propagation.REQUIRES_NEW)
*/
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void purchase(String username, String isbn) {
Integer price = bookShopDao.findBookPriceByIsbn(isbn); // 1.查询book价格
bookShopDao.updateBookStock(isbn); // 2.修改库存
bookShopDao.updateUserAccount(username, price); // 3.修改余额
}

9.2.2 事务隔离级别【Isolation】(MySql中)

  • MySql 默认是READ_COMMITTED
    image-20240721111707324
Isolation名称级别脏读不可重复读幻读
READ_UNCOMMITTED读未提交1
READ_COMMITTED读已提交2
REPEATEABLE_READ可重复读4(字段锁)
SERIALIZABLE串行化8(表锁)
  1. 脏读(Dirty Read)
    • 场景:事务A读取了事务B未提交的数据。
    • 例子
      1. 事务A读取账户余额为1000元。
      2. 同时,事务B开始修改账户余额,将1000元改为2000元,但尚未提交。
      3. 事务A读取到账户余额为2000元。
      4. 如果事务B回滚,事务A读取到的2000元就是错误的。
  2. 不可重复读(Non-repeatable Read)
    • 场景:在一个事务中,多次读取同一数据集合时,由于其他事务的更新,导致两次读取的结果不一致。
    • 例子
      1. 事务A读取账户余额为1000元。
      2. 同时,事务B将账户余额从1000元改为2000元,并提交了更改。
      3. 事务A再次读取账户余额,发现变为2000元。
  3. 幻读(Phantom Read)
    • 场景:在一个事务中,读取某个范围内的记录时,由于其他事务插入了新的记录,导致再次读取时发现有之 前未见过的新记录。
    • 例子
      1. 事务A读取账户余额大于1000元的所有账户。
      2. 事务B插入一个新的账户,余额为1500元,并提交了更改。
      3. 事务A再次读取账户余额大于1000元的所有账户,发现新增了一个账户,余额为1500元。

9.2.3 事务超时【timeout】

  • 概念: 由于事务可以字在行或表上获得锁🔒, 因此会占用过多资源, 对性能产生影响
  • 作用: 设置事务的超时时间, 到达指定时间后会强制回滚
  • 注意⚠️: 单位为秒(s)

9.2.4 事务的只读【readOnly】

  • 概念: 当 readOnly 属性设置为 true 时,这表明被注解的方法或类中的所有方法只涉及数据的读取操作,不涉及数据的修改。

9.2.5 事务的回滚【rollbackFor/noRollbackFor】

  • 概念: 输入出现哪种错/不出现那种错时回滚or不回滚
  • Title: 1.SpringFramework学习
  • Author: 明廷盛
  • Created at : 2025-02-15 16:20:32
  • Updated at : 2025-02-15 16:20:00
  • Link: https://blog.20040424.xyz/2025/02/15/😼Java全栈工程师/第三部分 SSM/1.SpringFramework学习/
  • License: All Rights Reserved © 明廷盛