⚡ Spring JDBC 完整体系 · 第 2 讲 · Spring 容器最小案例

相关开源地址:

  1. 核心框架源码:https://gitee.com/gao_zhenzhong/simple-dao
  2. 系统底座:https://gitee.com/gao_zhenzhong/simple-dao-starter
  3. 代码生成器:https://gitee.com/gao_zhenzhong/simple-dao-coder
  4. 实战案例(本集源码):https://gitee.com/gao_zhenzhong/simple-dao-demo

📋 前置知识

你只需要会你完全不需要会
Java 基础(类、接口、注解)Spring Boot 自动配置原理
会用 IDE 运行 Maven 项目Web 容器、Servlet 知识
第 1 讲:裸 JDBC Driver数据库驱动、连接池知识

📚 全套教程总览(20 讲从零到架构师)

篇章涵盖讲次核心目标
开篇筑基第 1-3 讲认知梳理,建立 Spring JDBC 的底层坐标系
API 精讲第 4-7 讲全面掌握 JdbcTemplate + 事务体系
工具封装第 8-9 讲手搓条件工具 + 代码生成器,从“使用者”变“创造者”
认知升华第 10 讲模板确定性 vs AI 不确定性,建立技术判断力
高阶扩展第 11-17 讲多数据源、分库分表、分布式事务
底层观测第 18-19 讲SQL 拦截、性能监控、连接池调优
终极收尾第 20 讲全链路复盘 + 手搓持久层框架

一、开篇 · 为什么要理解 Spring 容器?

上一讲体验了裸 JDBC 的繁琐资源管理,这一讲正式进入 Spring 容器。

Spring 容器本质上就是一个 Map。@Bean / @Repository 往 Map 里存,@Autowired 从 Map 里取。吃透这张 Map,就掌握了 IoC 和 DI 的本质。

本讲不引入任何数据库驱动,不涉及 Web 容器,仅围绕 Spring IoC 容器本身展开。这是整个 Spring JDBC 体系的认知基石。

二、极简依赖 · 纯 IoC 容器

本讲只依赖 spring-context,没有 Web 容器,没有数据库驱动,是最干净的纯 IoC 容器环境。

依赖作用
spring-contextSpring 核心 IoC 容器,Bean 管理
logback-classic日志输出,查看运行结果
lombok简化 POJO 的 getter / setter / 构造方法
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>[3.3.0,4.0.0)</version>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

三、核心代码演示

3.1 配置类 · 两种 Bean 注册方式

@Configuration 标记配置类,@ComponentScan 指定包扫描路径。@Bean 方法手动注册 Bean,方法名作为 Map 的 key。

@Configuration                    // 标记为配置类
@ComponentScan("com.gzz")         // 指定包扫描路径
public class AppConfig {

    @Bean                         // 手动注册,方法名 "user1" 作为 Map 的 key
    public User user1() {
        return new User("user1", 1);
    }

    @Bean                         // 手动注册,方法名 "user2" 作为 Map 的 key
    public User user2() {
        return new User("user2", 2);
    }
}

3.2 实体类

User 是简单 POJO,用 Lombok 简化代码:

@Getter
@Setter
@AllArgsConstructor
public class User {
    private String name;
    private int gender;           // 1=男,2=女
}

3.3 DAO 组件 · 包扫描自动注册

@Repository 将 UserDao 自动注册进容器,@Slf4j 支持日志打印:

@Slf4j
@Repository                       // 标记为数据访问层组件,自动注册进 Map
public class UserDao {

    public User save(User user) {
        log.info("save方法中的user={}", user);
        return user;
    }
}

3.4 启动类 · 自身也要成为 Bean

启动类有两个关键点:

  1. 自身必须标注 @Component,否则容器不会管理它,@Autowired 和 @EventListener 全部失效。
  2. main 方法创建 AnnotationConfigApplicationContext,传入配置类,触发容器启动。
@Slf4j
@Component                        // 关键:让 Application 自身也被容器管理
public class Application {

    public static void main(String[] arg) {
        new AnnotationConfigApplicationContext(AppConfig.class);
    }

    @Autowired
    @Qualifier("user1")           // 按名称注入 → Map.get("user1")
    private User user1;

    @Autowired
    @Qualifier("user2")           // 按名称注入 → Map.get("user2")
    private User user2;

    @Autowired                    // 按类型注入 UserDao
    private UserDao dao;

3.5 事件监听 · 容器启动后自动执行

@EventListener 监听 ContextRefreshedEvent,等容器完全刷新后再执行测试方法,此时所有 Bean 已就绪:

    @EventListener(ContextRefreshedEvent.class)
    public void run() {
        log.info("user1={}", user1);
        log.info("user2={}", user2);
        User user3 = dao.save(user1);
        log.info("user3={}", user3);
        log.info("dao={}", dao);
        log.info("user1.getName()={}, user1.getGender()={}", user1.getName(), user1.getGender());
        log.info("user2.getName()={}, user2.getGender()={}", user2.getName(), user2.getGender());
    }
}

3.6 日志配置 · logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d[%F:%L][%p]:[%t]%m%n</pattern>
        </encoder>
    </appender>
    <root level="info">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

四、运行结果

2026-06-26 18:54:37,278[Application.java:31][INFO]:[main]user1.getName()=user1,user1.getGender()=1
2026-06-26 18:54:37,280[Application.java:32][INFO]:[main]user2.getName()=user2,user2.getGender()=2
2026-06-26 18:54:37,280[Application.java:33][INFO]:[main]user1=com.gzz.user.User@7e5c856f
2026-06-26 18:54:37,280[Application.java:34][INFO]:[main]user2=com.gzz.user.User@413f69cc
2026-06-26 18:54:37,280[UserDao.java:12][INFO]:[main]save方法中的user=com.gzz.user.User@7e5c856f
2026-06-26 18:54:37,280[Application.java:36][INFO]:[main]user3=com.gzz.user.User@7e5c856f
2026-06-26 18:54:37,280[Application.java:37][INFO]:[main]dao=com.gzz.user.UserDao@1f53a5dc

user1 和 user3 内存地址相同(@7e5c856f),证明 dao.save(user1) 返回的就是容器中的同一个单例对象。

五、本讲核心总结

5.1 Spring 容器本质

操作注解 / 方法本质
注册 Bean@Bean / @Repositorymap.put(key, 实例)
注入依赖@Autowired + @Qualifiermap.get(key)
包扫描@ComponentScan自动抓取组件并注册
启动类管理@Component自身也存入 Map

5.2 容器结构示意

Spring 容器 = 大 Map
    │
    ├── "user1" → User@7e5c856f
    ├── "user2" → User@413f69cc
    ├── "userDao" → UserDao@1f53a5dc
    └── "application" → Application@...

5.3 易错点

容易踩的坑后果
启动类忘了加 @Component事件监听失效,@Autowired 不生效
包扫描路径写错组件注册不上,注入报错
多个同类型 Bean 不加 @Qualifier按类型注入冲突,容器启动失败

六、下一讲预告

第 3 讲将进入 Spring JDBC 最小案例,把 DataSource 和 JdbcTemplate 注册进容器,真正用 Spring 方式操作数据库。

更多推荐