Spring & SpringBoot & MyBatis

1. Spring

单例Bean线程安全

详解
Spring 中的单例 bean 默认不保证线程安全。
因为 singleton 只表示容器中只有一个实例,这个实例会被多个线程共享访问。

如果 bean 中存在可变的成员变量,并且在业务方法中被多个线程同时修改,就会发生线程安全问题。
但在实际开发中,大多数 Spring bean 都会设计成无状态的,
只使用方法参数和局部变量,所以通常可以安全使用。

如果 bean 必须保存状态,可以通过去状态化、加锁、使用原子类、ThreadLocal,或者调整 bean 作用域等方式处理。

AOP

AOP详解

可以无侵入式地添加功能 & 更好实现代码的复用

  • 场景: 记录操作日志
  • 缓存处理
  • spring中内置的事务处理
Pasted image 20260314214704

事务失效

Spring事务 = AOP代理 + 方法拦截 + 异常触发回滚

  • AOP代理

    1. 自己new对象, 非spring bean
    2. 类内部自调用, 没有经过AOP代理
    3. final、private不能Override, CGLIB代理(生成子类来加强)失效
  • 方法拦截

    1. 非public不拦截 , Spring AOP默认只拦截public方法

    private 不满足 可被代理增强的方法可见性条件,外部代理调不到它。 –> 可见性
    final 不满足 可被子类重写增强 这个条件,CGLIB 没法覆盖它。 –> 子类增强
    static 不满足 基于对象实例调用代理链 这个条件,它属于类,不属于对象。–>对象调用代理链

  • 异常触发

    1. 非RuntimeException 失效 —> @Transactional(rollbackFor=Exception.class)
    2. 自行处理异常 失效 —> throw new RuntimeException(e)

Spring Bean lifeCircle

Pasted image 20260316085028

Spring Bean的生命周期

Spring 首先会解析配置生成 BeanDefinition。
然后实例化 Bean,调用构造函数创建对象。
接着进行依赖注入,完成属性填充。
如果 Bean 实现了 Aware 接口,Spring 会回调相关方法注入容器对象。
随后进入初始化阶段,包括 BeanPostProcessor 的 before 方法、初始化方法以及 after 方法。
在 after 阶段如果需要 AOP,Spring 会创建动态代理对象。
最后 Bean 初始化完成并加入容器。
容器关闭时会执行销毁方法。

bean的循环依赖

Pasted image 20260316174805
  • 只有一级缓存的情况下
Pasted image 20260316092122
  • 一级二级缓存

    可以解决一般对象的循环依赖问题, 但不能解决代理对象的循环依赖问题

Pasted image 20260316103152
  • 三级缓存
Pasted image 20260316174228
  • 构造方法产生的循环依赖, spring无法解决, 用懒加载解决
1
2
3
4
public A(@ Lazy B b){
"这是A的构造方法".sout;
this.b = b;
}

2. SpringMVC执行流程

视图版本

Pasted image 20260316210624

SpringMVC 是 Spring 提供的 Web 层框架,主要负责处理客户端请求和响应。

请求到达后会先进入 DispatcherServlet,它作为统一前端控制器负责整体调度。
接着 DispatcherServlet 会通过 HandlerMapping 根据请求路径找到对应的 Handler,
也就是 Controller 中能处理该请求的方法,同时返回包含拦截器的 HandlerExecutionChain。
之后 DispatcherServlet 再交给 HandlerAdapter,由它完成参数绑定、类型转换、方法调用和返回值处理,最终执行目标 Handler。

如果返回的是页面,则会经过 ViewResolver 进行视图解析并渲染.
如果返回的是对象,则通常通过消息转换器转成 JSON 响应给前端.
SpringMVC 的本质就是把 HTTP 请求映射成 Java 方法调用,再把结果封装成 HTTP 响应。

前后端分离阶段

没有视图解析器和视图展示

Pasted image 20260316231626

3. SpringBoot自动配置

SpringBootApplication
Pasted image 20260317081553

SpringBoot 自动配置的核心在 @EnableAutoConfiguration

项目启动时,SpringApplication.run() 会启动 Spring 容器,并解析启动类上的 @SpringBootApplication
这个注解包含 @SpringBootConfiguration@EnableAutoConfiguration@ComponentScan

其中 @EnableAutoConfiguration 会通过 AutoConfigurationImportSelector 加载候选自动配置类。
Spring Boot 2 主要从 spring.factories 中读取,Spring Boot 3 则主要从 AutoConfiguration.imports 中读取。
之后 SpringBoot 会结合 @ConditionalOnClass@ConditionalOnMissingBean@ConditionalOnProperty 等条件注解进行筛选,
满足条件的配置类才会生效,并将对应的 BeanDefinition 注册到容器中。
如果用户自己定义了同类型 Bean,默认自动配置通常会通过 @ConditionalOnMissingBean 退让。

4. Spring常见注解

belong to Spring

Pasted image 20260317083236

belong to SpringMVC

Pasted image 20260317082715

belong to SpringBoot

Pasted image 20260317082817

5. MyBatis

MyBatis 是一个持久层框架,封装了 JDBC,
能够将 Java 方法、SQL 语句和数据库结果进行映射,简化数据库访问操作。

调用 Mapper 接口方法
-> MyBatis代理对象拦截并找到对应 SQL
-> 传参数并执行 SQL (比如id, name and so on)
-> 拿结果 并 封装成对象返回

例:

1
2
3
4
5
6
7
8
@Mapper
public interface UserMapper {
@Select("select * from user where id = #{id}")
User selectById(Long id);
}

//调用
User user = userMapper.selectById(1L);

传统 MyBatis 执行流程

  1. 读取 mybatis-config.xml,加载全局配置和 Mapper 映射
  2. 构建 SqlSessionFactory
  3. 通过工厂创建 SqlSession
  4. 调用 SqlSession 或 Mapper 方法
  5. 根据 statementId/Mapper 方法定位到 MappedStatement
  6. Executor 负责 SQL 执行与缓存维护
  7. 完成参数映射、JDBC 执行、结果映射
  8. 手动提交/回滚事务并关闭 SqlSession

现代主流 MyBatis 执行流程

  1. Spring Boot 读取 application.yml 等配置
  2. 自动装配数据源、SqlSessionFactorySqlSessionTemplate
  3. 自动扫描 Mapper 接口并注册为 Spring Bean
  4. 业务层直接注入 Mapper 代理对象
  5. Mapper 代理通过 SqlSessionTemplate 执行
  6. 底层仍然定位 MappedStatement,再由 Executor 执行 SQL
  7. Spring 负责事务、提交、回滚和资源释放

延时加载原理

需要用到数据时才去加载数据
在mybatis的配置文件中打开lazyLoadingEnabled=true/false
支持一对一/一对多关联对象的延时加载

Pasted image 20260317132832 ### 1. 主查询执行 先执行主 SQL,查出主对象基础字段。

2. 结果集映射阶段识别嵌套查询属性

DefaultResultSetHandler 在处理 association / collection 且配置为嵌套 select 的属性时,发现该属性可以延迟处理。

3. 封装延迟任务

MyBatis 把后续查询所需的 MappedStatement、参数、BoundSql、结果类型、Executor 等封装为 ResultLoader,并放进 ResultLoaderMap

4. 创建代理对象

如果对象存在待加载属性,MyBatis 通过 ProxyFactory 创建代理对象。当前支持的代理工厂有 JAVASSIST 和已废弃的 CGLIB

5. 返回代理对象

业务代码拿到的看似是普通实体,实际可能是带懒加载拦截能力的代理。

6. 访问触发方法

当调用 getter,或者配置中的触发方法如 toStringhashCode 时,代理拦截方法调用。

7. 执行延迟查询

代理根据属性名从 ResultLoaderMap 取出对应 ResultLoader,调用 loadResult() 执行嵌套 SQL。

8. 回填属性并清理任务

查询结果写回目标属性,这个属性从“未加载”变成“已加载”,后续再次访问通常不再重复查。

Mybatis的一级二级缓存

一级缓存

同一个 SqlSession 里重复查相同数据,可能只发一次 SQL, 所以说其作用域为Session

Pasted image 20260317151446

二级缓存

不同 SqlSession 之间,若配置了二级缓存,可通过同一个 namespace 共享缓存
需要二级缓存的数据必须实现Serializable接口(可序列化)

只有会话提交/关闭以后, 一级缓存的数据才会转移到二级缓存

Pasted image 20260317153247

Session/Namespaces进行增删改后默认会清理缓存