Spring(侧重注解开发)

目录

1.什么是Spring

Spring是一款主流的Java EE轻量级开源框架,目的在于简化Java企业级应用的开发难度和开发周期。由于教程中用的是Spring6,但是我本地用的是JAVA8,所以有些地方可能不一样。我使用的是idea2025。

2.简单实例

操作步骤:

1.创建Maven父工程

2.在Maven父工程中创建Maven子工程

3.创建实体类User

4.创建Bean配置文件

5.在Bean的配置文件中配置实体类,配置如下

6.测试,测试代码如下

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.example</groupId>
        <artifactId>spring6</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>spring-first</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!--spring基础依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.30.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.3.1</version>
        </dependency>
    </dependencies>

</project>
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">
    <bean id="user" class="com.yancey.User"></bean>
</beans>
java 复制代码
import com.yancey.User;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test1 {

    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        User user = (User) context.getBean("user");
        System.out.println("User对象:" + user);
        user.add();
    }
}

测试结果如下:

十二月 复制代码
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@be64738: startup date [Wed Dec 24 22:16:24 CST 2025]; root of context hierarchy
十二月 24, 2025 10:16:24 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [bean.xml]
User对象:com.yancey.User@8462f31
add

紧接着,就出现了两个疑问,Spring是通过什么来创建对象的,创建的对象又放到哪里的呢?
Spring是通过读取xml文件中class的定义,然后通过反射创建对象的,创建的对象是放beanDefinitionMap(此容器是放在DefaultListableBeanFactory中)

3.添加Log4j2

3.1日志级别

日志信息的优先级,日志信息的优先级从高到低TRACE<DEBUG<INFO<WARN<ERROR<FATAL

  • TRACE:追踪,是最低的日志级别,相当于追踪程序的执行
  • DEBUG:调试,一般在开发中,都将其设置为最低的日志级别
  • INFO:信息,输出重要的信息,使用较多
  • WARN:警告,输出警告的信息
  • ERROR:错误,输出错误信息
  • FATAL:严重错误

3.2引入Log4j2

添加依赖

xml 复制代码
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.19.0</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j2-impl</artifactId>
    <version>2.19.0</version>
</dependency>

3.3使用Log4j2

在类的根路径下提供log4j2.xml配置文件(文件名固定为:log4j2.xml,文件必须放到(resources)根路径下。),然后在方法中使用。

java 复制代码
package com.yancey;


import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class User {
    private static final Logger log = LogManager.getLogger(User.class);

    public void add() {
        log.info(() -> "我错误了");
        System.out.println("add");
    }

    public static void main(String[] args) {
        User user = new User();
        user.add();
    }
}

由于我是学过Spring之后,又回来复习的,所以接下来的内容就按照Spring的重点来复习了

4.IOC

IOC = 控制反转(Inversion of Control),在传统的软件开发方式中,对象之间的依赖关系由开发者手动管理和注入。而IOC(Inversion of Control)控制反转则是一种设计原则,它通过将对象的创建和依赖注入的责任交给容器(Spring)来管理,从而实现了对象之间的解耦。

4.1IOC容器在Spring的实现

  • BeanFactory:IOC容器基本实现,是Spring里面一个内部使用的接口,不提供给开发人员使用。(BeanFactory 是懒加载(Lazy Loading) 的,配置文件被加载时,只是注册了Bean的定义,但不会立即实例化Bean。只有当程序第一次请求(getBean)某个Bean时,才会真正创建它。)。
  • 常用的类
    • ApplicationContext:BeanFactory接口的子接口,提供更多更强大的功能,一般是由开发人员使用的。(在加载配置文件时就会创建对象)
      • FileSystemXmlApplicationContext:盘符路径(全路径)
      • ClassPathXmlApplicationContext:类路径(src下)

4.2基于XML管理bean

4.2.1依赖注入

xml 复制代码
/**
set
**/
//创建类,定义属性,生成属性set方法 
//在spring配置文件配置
 <bean id="book" class="com.atguigu.spring6.iocxml.di.Book">
        <property name="bname" value="Java"></property>
        <property name="author" value="尚硅谷"></property>
 </bean>

/**
构造器
**/
//创建类,定义属性,生成有参构造方法
 
//在spring配置文件配置
<bean id="bookCon" class="com.atguigu.spring6.iocxml.di.Book">
        <constructor-arg name="bname" value="C++"></constructor-arg>
        <constructor-arg name="author" value="尚硅谷"></constructor-arg>
</bean>

/**
使用null值
**/
<bean id="user" class="com.example.User">
    <!-- 显式设置为null -->
    <property name="email">
        <null/>
    </property>
    
    <!-- 嵌套属性 -->
    <property name="address">
        <null/>
    </property>
</bean>
/**
CDATA节
**/
//包含特殊值,视为纯文本
<property name="others">
    <value><![CDATA[a<b]]></value>
</property>
/**
引用外部bean
**/
//包含特殊值,视为纯文本
<bean id="dept" class="com.atguigu.spring6.iocxml.ditest.Dept">
    <property name="dname" value="安保部"></property>
</bean>
 
<bean id="emp" class="com.atguigu.spring6.iocxml.ditest.Emp">
    <!--普通属性注入-->
    <property name="ename" value="lucy"></property>
    <property name="age" value="50"></property>
    
    <!--特殊属性注入-->
    <property name="dept" ref="dept"></property>  																	 //ref = "dept"
</bean>
/**
引用内部bean
**/
 <bean id="dept2" class="com.atguigu.spring6.iocxml.ditest.Dept">
        <property name="dname" value="财务部"></property>
  </bean>
  <bean id="emp2" class="com.atguigu.spring6.iocxml.ditest.Emp">
        <!--普通属性注入-->
        <property name="ename" value="mary"></property>
        <property name="age" value="30"></property>
        <!--内部bean-->
        <property name="dept">
            <bean id="dept2" class="com.atguigu.spring6.iocxml.ditest.Dept"> 													//
                <property name="dname" value="财务部"></property>																//
            </bean>																										  //
        </property>
  </bean>

4.2.2p命名空间

xml 复制代码
xmlns:p="http://www.springframework.org/schema/p"
    
<bean id="studentp" class="com.atguigu.spring6.iocxml.dimap.Student"
    p:sid="100" p:sname="lucy" p:lessonList-ref="students" p:teacherMap-ref="teacheMap">
</bean>

4.2.3.Bean的作用域

xml 复制代码
<!--通过scope属性配置单实例--> //多次使用同一地址的bean
<bean id="orders" class="com.atguigu.spring6.iocxml.scope.Orders"
     scope="singleton"></bean>
<!--通过scope属性配置多实例--> //多次使用不同地址的bean
<bean id="orders" class="com.atguigu.spring6.iocxml.scope.Orders"
     scope="prototype"></bean>

4.3注解开发

上面使用XML的开发方式在企业开发中较少 了,现在都是使用注解开发,所以注解开发是重点!!

4.3.1使用注解在Spring中创建第一个Bean

  • 在XML文件中添加包扫描路径
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 https://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="com.yancey"/>
</beans>
  • 使用@Component注册该bean,并使用
java 复制代码
package com.yancey;


import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component;

@Component
public class User {
    private static final Logger log = LogManager.getLogger(User.class);

    public void add() {
        log.info(() -> "我错误了");
        System.out.println("add");
    }

    public static void main(String[] args) {
        User user = new User();
        user.add();
    }
}

4.3.2常用的注解

注解 作用
@Component 通用的组件注解,标记的类会被Spring容器管理
@Service 标注业务逻辑层(Service层)组件
@Repository 标注数据访问层(DAO层)组件
@Controller 标注Web控制层组件,处理HTTP请求
@RestControlle @Controller和@ResponseBody的组合,用于RESTful API
@Autowired 自动按类型注入依赖
@Qualifier @Autowired配合,按名称指定要注入的Bean
@Value 注入属性值,支持配置文件、环境变量等
@Configuration 声明一个类为配置类,该类内部可以使用@Bean注解定义Bean
@Bean 在配置类的方法上使用,声明该方法返回的对象是一个Spring Bean
@ComponentScan 指定Spring要扫描的包路径,自动注册带有@Component等注解的类
@Scope 定义Bean的作用域,如单例(singleton)、原型(prototype)等
@PostConstruct与@PreDestroy 分别标记Bean初始化后和销毁前执行的方法,用于资源初始化和清理

5.AOP

AOP(Aspect Oriented Programming),意为面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。AOP是一个概念,并没有设定具体语言的实现,它能克服那些只有单继承特性语言的缺点,spring2.0之后整合AspectJ第三方AOP技术 。就是能够对类进行功能的加强,AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。
Spring是使用了代理模式完成了AOP的实现,那什么是代理模式呢?
代理模式是指一个对象提供一个替身,以控制对这个对象的访问,即通过代理对象访问目标对象,这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。分为静态代理和动态代理两种。下图是两者的区别,简单来说JDK代理是基于接口的,cglib是基于父类的。

5.1术语

  • 目标对象(Target):指的是需要被增强的对象,由于spring aop是通过代理模式实现,从而这个对象永远是被代理对象。
  • 连接点(Join Point):程序执行过程中明确的点,通常是方法的调用或异常抛出。Spring AOP中,连接点总是代表允许使用通知的执行点。
  • 切入点(Point Cut):一组连接点的集合,通过表达式或逻辑定义哪些连接点需要被拦截。切入点决定了"在什么地方"进行增强。
  • 通知(Notify):在切入点处执行的增强逻辑。定义了"在什么时候"执行"什么操作"。常见的有前置通知、返回通知、异常通知、后置通知、环绕通知
  • 引介:一种特殊的通知,允许在运行时为类动态添加新的方法或属性,修改类的结构。
  • 切面:切入点和通知的组合。它封装了横切关注点的完整处理逻辑。
  • 织入:将切面应用到目标对象创建代理对象的过程。根据时机不同分为编译期、类加载期、运行期织入。
  • 代理对象:一个类被AOP织入增强后,就产生一个结果代理类

5.2执行流程

java 复制代码
1. 定义切面(Aspect)
   ↓
2. 指定切入点(Pointcut):哪些方法需要增强
   ↓
3. 编写通知(Advice):增强的具体逻辑
   ↓
4. Spring运行时进行织入(Weaving)
   ↓
5. 创建代理对象,包装原始目标对象
   ↓
6. 调用方法时,代理对象执行:
   a. 前置通知
   b. 原始方法
   c. 后置/异常通知
   ↓
7. 返回给调用者

5.3写一个切面

  • 加入依赖
xml 复制代码
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>4.3.30.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>4.3.30.RELEASE</version>
</dependency>
  • 编写切面类,并设置通知类型和切入点
java 复制代码
package com.yancey;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TestAspect {

    @Before("execution(public void com.yancey.WorkCompute.*(..))")
    public void before(JoinPoint joinPoint) {
        System.out.println("前置方法...");
    }
}

5.4切入点表达式

切入点表达式用于定义哪些方法应该被AOP拦截。它是AOP的核心,Spring使用AspectJ的切入点表达式语言。

java 复制代码
execution(修饰符? 返回类型 包名.类名.方法名(参数) 异常?)
// 例如
// && (and) - 与
execution(* com.yancey.service.*.*(..)) && execution(* *..*.save*(..))

// || (or) - 或
execution(* com.yancey.service.*.*(..)) || execution(* com.yancey.dao.*.*(..))

// ! (not) - 非
execution(* com.yancey.service.*.*(..)) && !execution(* *..*.get*(..))

5.5通知类型

通知类型 功能
@Before 在方法前执行
@AfterReturning 方法成功返回后执行,可以获取返回值
@AfterThrowing 方法抛异常后执行,可以获取异常
@After 方法结束后执行
@Around 包含以上四种通知
java 复制代码
package com.example.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

@Component
@Aspect
public class LoggingAspect {
    
    // 统一的切入点定义
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void servicePointcut() {}
    
    // ================ 1. 前置通知 ================
    @Before("servicePointcut()")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println("【前置通知】方法执行前: " + joinPoint.getSignature().getName());
    }
    
    // ================ 2. 后置通知 ================
    @After("servicePointcut()")
    public void afterAdvice(JoinPoint joinPoint) {
        System.out.println("【后置通知】方法执行后: " + joinPoint.getSignature().getName());
    }
    
    // ================ 3. 返回通知 ================
    @AfterReturning(
        pointcut = "servicePointcut()",
        returning = "result"
    )
    public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
        System.out.println("【返回通知】方法返回值: " + result);
    }
    
    // ================ 4. 异常通知 ================
    @AfterThrowing(
        pointcut = "servicePointcut()",
        throwing = "ex"
    )
    public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {
        System.out.println("【异常通知】方法抛出异常: " + ex.getMessage());
    }
    
    // ================ 5. 环绕通知 ================
    @Around("servicePointcut()")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("【环绕通知】方法执行前");
        
        long startTime = System.currentTimeMillis();
        Object result = null;
        
        try {
            result = joinPoint.proceed();  // 执行目标方法
            long endTime = System.currentTimeMillis();
            System.out.println("【环绕通知】方法执行成功,耗时: " + (endTime - startTime) + "ms");
            
        } catch (Exception e) {
            long endTime = System.currentTimeMillis();
            System.out.println("【环绕通知】方法执行异常,耗时: " + (endTime - startTime) + "ms");
            throw e;  // 重新抛出异常
        }
        
        return result;
    }
}

5.6切面的优先级

目标方法 → 多个切面同时作用 → 用 @Order注解指定优先级 → 优先级高的切面放外层(大圈),优先级低的放内层(小圈)→ 最终形成"外层切面包裹内层切面,共同作用于目标方法"的嵌套结构。

5.7基于XML配置AOP(了解)

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: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/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 1. 定义目标Bean -->
    <bean id="userService" class="com.example.service.UserService"/>
    
    <!-- 2. 定义切面Bean -->
    <bean id="logAspect" class="com.example.aspect.LogAspect"/>
    
    <!-- 3. 配置AOP -->
    <aop:config>
        <!-- 3.1 定义切面 -->
        <aop:aspect id="logAspectConfig" ref="logAspect">
            
            <!-- 定义切入点表达式 -->
            <aop:pointcut id="servicePointcut" 
                expression="execution(* com.example.service.*.*(..))"/>
            
            <aop:pointcut id="savePointcut" 
                expression="execution(* com.example.service.*.save*(..))"/>
            
            <aop:pointcut id="deletePointcut" 
                expression="execution(* com.example.service.*.delete*(..))"/>
            
            <!-- 1. 前置通知 -->
            <aop:before method="beforeAdvice" 
                       pointcut-ref="servicePointcut"/>
            
            <!-- 2. 后置通知 -->
            <aop:after method="afterAdvice" 
                      pointcut-ref="servicePointcut"/>
            
            <!-- 3. 返回通知 -->
            <aop:after-returning method="afterReturningAdvice" 
                                pointcut-ref="savePointcut"
                                returning="result"/>
            
            <!-- 4. 异常通知 -->
            <aop:after-throwing method="afterThrowingAdvice" 
                               pointcut-ref="deletePointcut"
                               throwing="ex"/>
            
            <!-- 5. 环绕通知 -->
            <aop:around method="aroundAdvice" 
                       pointcut-ref="servicePointcut"/>
            
        </aop:aspect>
    </aop:config>
    
</beans>

五种通知的详细配置

java 复制代码
<aop:config>
    <aop:aspect id="myAspect" ref="aspectBean">
        
        <!-- 切入点定义 -->
        <aop:pointcut id="serviceMethods" 
            expression="execution(* com.example.service.*.*(..))"/>
        
        <aop:pointcut id="saveMethods" 
            expression="execution(* com.example.service.*.save*(..)) and args(name,age)"/>
        
        <!-- 1. 前置通知 -->
        <aop:before method="beforeMethod" 
                   pointcut-ref="serviceMethods"/>
        
        <!-- 带参数绑定 -->
        <aop:before method="beforeSave" 
                   pointcut="execution(* com.example.service.*.save*(..)) and args(name,age)"/>
        
        <!-- 2. 后置通知 -->
        <aop:after method="afterMethod" 
                  pointcut-ref="serviceMethods"/>
        
        <!-- 3. 返回通知 -->
        <aop:after-returning method="afterReturningMethod" 
                            pointcut-ref="serviceMethods"
                            returning="returnValue"/>
        
        <!-- 4. 异常通知 -->
        <aop:after-throwing method="afterThrowingMethod" 
                          pointcut-ref="serviceMethods"
                          throwing="exception"/>
        
        <!-- 5. 环绕通知 -->
        <aop:around method="aroundMethod" 
                   pointcut-ref="serviceMethods"/>
        
    </aop:aspect>
</aop:config>

6.数据库操作(以Postgresql数据库为例)

要学习事务,必须了解Spring如何操作数据库,由于后续的Springboot会替代Spring,所以我这边没有做太多了解,只是使用了其中一种,即注解方式操作数据库。

  • 加入依赖
xml 复制代码
<!-- PostgreSQL驱动 -->
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.2.27</version>
</dependency>
<!-- Spring JDBC -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>4.3.30.RELEASE</version>
</dependency>
  • 写配置类
java 复制代码
package com.yancey;

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.SimpleDriverDataSource;

import javax.sql.DataSource;

@Configuration
@ComponentScan("com.yancey")
public class AppConfig {

    @Bean
    public DataSource dataSource() {
        SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
        dataSource.setDriverClass(org.postgresql.Driver.class);
        dataSource.setUrl("jdbc:postgresql://172.29.234.114:5432/plm-liansen-wys");
        dataSource.setUsername("postgres");
        dataSource.setPassword("123456");
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}
  • 创建测试表和测试数据
sql 复制代码
-- 创建最简单的用户表
CREATE TABLE users (
    id SERIAL PRIMARY KEY,                     -- 自增主键
    username VARCHAR(50) UNIQUE NOT NULL,     -- 用户名
    email VARCHAR(100) UNIQUE NOT NULL,       -- 邮箱
    age INTEGER,                               -- 年龄
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP  -- 创建时间
);
INSERT INTO users (username, email, age) VALUES
('zhangsan', 'zhangsan@example.com', 25),
('lisi', 'lisi@example.com', 30),
('wangwu', 'wangwu@example.com', 28),
('zhaoliu', 'zhaoliu@example.com', 35),
('sunqi', 'sunqi@example.com', 22);
  • 使用JdbcTemplate操作数据库
java 复制代码
import com.yancey.AppConfig;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.Map;

public class Test1 {

    @Test
    public void test1() {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class);
        Map<String, Object> result = jdbcTemplate.queryForMap("select * from users where id = ?", 1);
        System.out.println(result);
    }
}

但是在这里面我产生了一个疑问,问什么向IOC容器中放一个DataSource对象,就可以操作JdbcTemplate了呢?
这就是@Bean注解的作用,使用这个注解spring会自动去IOC容器中找bean名字为dataSource对象。

7.事务

数据库事务是指作为单个逻辑工作单元执行的一系列操作,这些操作要么一起成功,要么一起失败,是一个不可分割的工作单元。

7.1事务的四大特性(ACID)

  • Atomicity:原子性,事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
  • Consistency:一致性,事务的执行不能破坏数据库数据的完整性和一致性。也就是事务执行的过程中要保证数据的有效性。
  • Isolation:隔离性,并发执行的各个事务之间相互隔离,一个事务的执行不应影响其他事务的执行。
  • Durability:持久性,一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。

7.2事务的使用方式

7.2.1声明式事务

java 复制代码
@Configuration
@EnableTransactionManagement  // 启用声明式事务
public class AppConfig {
    
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}
java 复制代码
@Service
@Transactional  // 类级别:所有公共方法都有事务
public class UserServiceImpl implements UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    // 继承类级别的事务配置
    public User createUser(User user) {
        return userRepository.save(user);
    }
    
    // 方法级别覆盖类级别配置
    @Transactional(
        propagation = Propagation.REQUIRES_NEW,
        timeout = 30,
        readOnly = false
    )
    public User updateUser(User user) {
        return userRepository.save(user);
    }
    
    // 只读查询
    @Transactional(readOnly = true)
    public User findById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
}

7.2.2编程式依赖

java 复制代码
@Service
public class UserService {
    
    @Autowired
    private PlatformTransactionManager transactionManager;
    
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    public void updateUserProgrammatic(User user) {
        // 方式1:使用 TransactionTemplate
        transactionTemplate.execute(status -> {
            try {
                // 业务逻辑
                userRepository.update(user);
                return null;
            } catch (Exception e) {
                status.setRollbackOnly();  // 标记回滚
                throw e;
            }
        });
        
        // 方式2:使用 PlatformTransactionManager
        TransactionStatus status = transactionManager.getTransaction(
            new DefaultTransactionDefinition()
        );
        try {
            userRepository.update(user);
            transactionManager.commit(status);
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }
}

7.3事务的传播行为

事务的传播行为值得是当一个事务方法调用另一个事务方法时,事务如何传递

传播行为 描述
REQUIRED 有就用,没有就新建
REQUIRES_NEW 必须新建,将当前事务挂起
NESTED 嵌套在现有事务中
SUPPORTS 有就用,没有就不用是事务
NOT_SUPPORTED 非事务运行
NEVER 有事务就报错
MANDATORY 必须有事务,否则就报错
java 复制代码
@Service
@Transactional
public class PropagationService {
    
    // 1. REQUIRED(默认):有事务加入,无事务新建
    @Transactional(propagation = Propagation.REQUIRED)
    public void requiredMethod() {
        // 如果当前有事务,就加入
        // 如果当前没事务,就新建事务
    }
    
    // 2. REQUIRES_NEW:总是新建事务,挂起当前事务(这里的挂起是什么意思
挂起 = 先暂停,等会继续)
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void requiresNewMethod() {
        // 总是新建独立事务
        // 挂起当前事务(如果存在)
        // 适合日志记录、消息发送等独立操作
    }
    
    // 3. NESTED:嵌套事务(保存点)
    @Transactional(propagation = Propagation.NESTED)
    public void nestedMethod() {
        // 在现有事务中创建保存点
        // 可以部分回滚(回滚到保存点)
        // 外部事务回滚会导致嵌套事务回滚
    }
    
    // 4. SUPPORTS:有事务加入,无事务非事务运行
    @Transactional(propagation = Propagation.SUPPORTS)
    public void supportsMethod() {
        // 有事务就加入事务
        // 没事务就以非事务方式运行
        // 适合查询方法
    }
    
    // 5. NOT_SUPPORTED:非事务运行,挂起当前事务
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void notSupportedMethod() {
        // 总是以非事务方式运行
        // 挂起当前事务(如果存在)
        // 适合不要求事务的操作
    }
    
    // 6. MANDATORY:必须有事务,否则抛异常
    @Transactional(propagation = Propagation.MANDATORY)
    public void mandatoryMethod() {
        // 必须在事务中调用
        // 如果没有事务,抛 IllegalTransactionStateException
    }
    
    // 7. NEVER:必须无事务,否则抛异常
    @Transactional(propagation = Propagation.NEVER)
    public void neverMethod() {
        // 必须在无事务环境中调用
        // 如果有事务,抛 IllegalTransactionStateException
    }
}

7.4事务的隔离级别

java 复制代码
@Service
@Transactional
public class IsolationService {
    
    // 1. DEFAULT:使用数据库默认隔离级别
    @Transactional(isolation = Isolation.DEFAULT)
    public void defaultIsolation() {
        // MySQL: REPEATABLE_READ
        // Oracle: READ_COMMITTED
    }
    
    // 2. READ_UNCOMMITTED:读未提交(最低)
    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    public void readUncommitted() {
        // 可能读到其他事务未提交的数据(脏读)
        // 性能最好,一致性最差
    }
    
    // 3. READ_COMMITTED:读已提交
    @Transactional(isolation = Isolation.READ_COMMITTED)  
    public void readCommitted() {
        // 只能读到已提交的数据
        // 解决脏读,但可能不可重复读
        // Oracle默认级别
    }
    
    // 4. REPEATABLE_READ:可重复读
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public void repeatableRead() {
        // 同一事务中多次读取结果一致
        // 解决脏读、不可重复读,但可能幻读
        // MySQL默认级别
    }
    
    // 5. SERIALIZABLE:串行化(最高)
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public void serializable() {
        // 完全串行执行,解决所有并发问题
        // 性能最差,一致性最好
    }
}

7.5只读事务

只读事务是声明该事务只读取数据,不修改数据的特殊事务。所以我就有个疑问,尽然只读取数据库,那事务的意义是什么呢?这时候AI给我了好的答案,用只读事务是"跑得更好、更安全"。事务用回到一个数据库连接,减少了连接池压力,保证了数据的一致性,可以实现读写分离。所以我建议只要是查询方法,尽量都加上只读事务。

  • 延迟加载必须要用只读事务
java 复制代码
@Transactional(readOnly = true)

7.6事务超时

事务超时就是给你的查询设置一个倒计时,超时就自动回滚。常被用在当某个数据库操作可能耗时太久时,用事务超时来强制中断它,防止拖垮整个系统。

java 复制代码
// 普通查询:可能卡死
public void query() {
    // 万一SQL很慢,一直卡住...
    List<User> users = userMapper.selectAll();  // 如果10分钟还没完?
}

// 加超时:5秒不完成就报错
@Transactional(timeout = 5)  // 最多等5秒
public void query() {
    List<User> users = userMapper.selectAll();  // 5秒不完就抛异常
}
相关推荐
早点睡觉好了2 小时前
JAVA中基本类型和包装类型的区别
java·开发语言
爱喝水的鱼丶2 小时前
SAP-ABAP:在SAP世界里与特殊字符“斗智斗勇”:一份来自实战的避坑指南
运维·服务器·数据库·学习·sap·abap·特殊字符
雅俗共赏zyyyyyy2 小时前
SpringBoot集成配置文件加解密
java·spring boot·后端
科技林总2 小时前
【系统分析师】认证介绍
学习
计算机学姐2 小时前
基于SpringBoot的送货上门系统【2026最新】
java·vue.js·spring boot·后端·mysql·spring·tomcat
码农水水2 小时前
国家电网Java面试被问:二叉树的前序、中序、后序遍历
java·开发语言·面试
Yana.nice3 小时前
JMS与JDBC
java
小湘西3 小时前
Elasticsearch 的一些默认配置上下限
java·大数据·elasticsearch
不吃橘子的橘猫3 小时前
NVIDIA DLI 《Build a Deep Research Agent》学习笔记
开发语言·数据库·笔记·python·学习·算法·ai