Spring Boot 工程化核心详解:Parent、Starter、热部署、事务与多数据源

Spring Boot 的便利不只来自自动配置。项目中的父 POM、Starter、DevTools、事务代理和数据源配置共同构成了一套完整的工程化体系。本文基于 Spring Boot 3.x,讲清这些能力如何使用,以及它们背后的工作原理。

一、为什么使用 spring-boot-starter-parent

一个典型 Maven 项目会继承:

复制代码
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.x.x</version>
    <relativePath/>
</parent>

它的主要价值不是引入业务依赖,而是提供统一的构建约定。

1. 管理依赖版本

Spring Boot 为 Spring Framework、Jackson、Logback、数据库驱动等常见组件维护了一套经过兼容性测试的版本组合。因此,引入受管理依赖时通常不用单独写版本:

复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

升级 Spring Boot 版本时,相关依赖也会按统一组合升级,可以减少版本冲突。

2. 提供插件与构建默认值

Parent 还提供 Maven 插件管理、Java 编译参数、资源过滤等默认配置,并配合 spring-boot-maven-plugin 生成可执行 JAR。团队仍可在自己的 POM 中覆盖需要调整的属性,但不建议无理由覆盖 Spring Framework 等核心版本。

3. Parent 与 BOM 的区别

有些公司项目必须继承自己的父 POM,而 Maven 一个项目只能直接继承一个 Parent。此时可以在 dependencyManagement 中导入 Spring Boot BOM:

复制代码
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>3.x.x</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

二、Spring Boot Starter 的工作原理

Starter 可以理解为一个"依赖集合入口"。它通常没有大量业务代码,而是通过 Maven 传递依赖,把实现某项能力所需的组件一次性引入。

spring-boot-starter-web 为例,它会带入 Spring MVC、JSON 转换、日志以及默认内嵌 Tomcat 等依赖。开发者不需要逐个查找并协调版本。

但要注意:Starter 主要负责聚合依赖,自动配置负责创建 Bean。 两者配合后才形成"引入依赖即可使用"的体验。

Spring Boot 3 的自动配置候选通常登记在:

复制代码
META-INF/spring/
org.springframework.boot.autoconfigure.AutoConfiguration.imports

自动配置常使用:

  • @ConditionalOnClass:某个类存在时生效;

  • @ConditionalOnMissingBean:用户没有自定义 Bean 时提供默认 Bean;

  • @ConditionalOnProperty:配置满足条件时生效。

因此,Starter 让依赖到位,自动配置根据 classpath、已有 Bean 和配置属性决定具体创建什么。用户自定义同类型 Bean 时,默认配置通常会退让。

三、Spring Boot 如何实现热部署?

开发阶段可以引入 DevTools:

复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

当 classpath 中的文件发生变化时,DevTools 会触发应用快速重启。代码修改后必须先由 IDE 或构建工具编译,只有 .class 等 classpath 内容更新,DevTools 才能检测到。

双 ClassLoader 原理

DevTools 使用两个类加载器:

  • Base ClassLoader:加载基本不变的第三方依赖;

  • Restart ClassLoader:加载正在开发的项目代码。

重启时只丢弃并重建 Restart ClassLoader,第三方依赖无需重新加载,因此比完整冷启动更快。

四、如何在 Spring Boot 中使用事务管理?

项目引入 JDBC、MyBatis 或 JPA Starter 并配置数据源后,Spring Boot 通常会自动配置合适的 PlatformTransactionManager。业务方法上使用 @Transactional 即可声明事务:

复制代码
@Service
public class TransferService {

    private final AccountRepository accountRepository;

    public TransferService(AccountRepository accountRepository) {
        this.accountRepository = accountRepository;
    }

    @Transactional(rollbackFor = Exception.class)
    public void transfer(Long from, Long to, BigDecimal amount) {
        accountRepository.decrease(from, amount);
        accountRepository.increase(to, amount);
    }
}

事务原理

Spring 通常为 Bean 创建 AOP 代理。外部调用事务方法时,代理在方法执行前通过事务管理器开启事务,成功后提交,异常时按回滚规则回滚,并在结束后释放连接等资源。

默认情况下:

  • 传播行为是 REQUIRED

  • 隔离级别使用数据库默认值;

  • RuntimeExceptionError 触发回滚;

  • 受检异常默认不回滚。

如果业务希望任何 Exception 都回滚,可以明确配置 rollbackFor = Exception.class

常见传播行为

REQUIRED 是默认值:外部已有事务就加入,没有就新建,适合一个业务入口统一操作多个 Repository。REQUIRES_NEW 会挂起外部事务并创建独立事务,内外可以分别提交或回滚,但会额外占用数据库连接。NESTED 通常基于 JDBC Savepoint 实现局部回滚,并不等同于真正独立事务。

传播行为只有在调用经过 Spring 代理时才会生效。同一个对象内部直接调用带 REQUIRES_NEW 的方法,仍会绕过代理,无法创建预期的新事务。实际项目中通常把需要不同事务边界的方法拆到另一个 Service。

常见事务失效场景

  1. 同类方法自调用,实际是 this.method(),没有经过代理;

  2. 异常被 catch 后吞掉,代理感知不到异常;

  3. 方法未通过 Spring Bean 调用;

  4. 使用错误的事务管理器;

  5. 数据库表或存储引擎本身不支持事务。

在默认代理模式下,建议把事务放在 Service 的 public 业务入口,并由其他 Bean 调用。不要在事务中执行耗时远程请求,否则会长时间占用连接和数据库锁。

五、Spring Boot 如何配置多数据源?

假设系统同时访问订单库和用户库。

1. 配置文件

复制代码
app:
  datasource:
    order:
      url: jdbc:mysql://localhost:3306/order_db
      username: root
      password: password
      driver-class-name: com.mysql.cj.jdbc.Driver
    user:
      url: jdbc:mysql://localhost:3306/user_db
      username: root
      password: password
      driver-class-name: com.mysql.cj.jdbc.Driver

真实项目应通过环境变量或密钥服务提供密码,不要把生产凭据提交到代码仓库。

2. 创建两个数据源

复制代码
@Configuration
public class DataSourceConfig {

    @Bean
    @Primary
    @ConfigurationProperties("app.datasource.order")
    public DataSourceProperties orderDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean
    @Primary
    public DataSource orderDataSource(
            @Qualifier("orderDataSourceProperties")
            DataSourceProperties properties) {
        return properties.initializeDataSourceBuilder().build();
    }

    @Bean
    @ConfigurationProperties("app.datasource.user")
    public DataSourceProperties userDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean
    public DataSource userDataSource(
            @Qualifier("userDataSourceProperties")
            DataSourceProperties properties) {
        return properties.initializeDataSourceBuilder().build();
    }
}

@Primary 用于存在多个同类型 Bean 时指定默认选择。使用 DataSourceProperties 可以处理通用 url 与具体连接池属性之间的差异。

3. 配置 JdbcTemplate 和事务管理器

复制代码
@Bean
@Primary
public JdbcTemplate orderJdbcTemplate(
        @Qualifier("orderDataSource") DataSource dataSource) {
    return new JdbcTemplate(dataSource);
}

@Bean
public JdbcTemplate userJdbcTemplate(
        @Qualifier("userDataSource") DataSource dataSource) {
    return new JdbcTemplate(dataSource);
}

@Bean
@Primary
public JdbcTransactionManager orderTransactionManager(
        @Qualifier("orderDataSource") DataSource dataSource) {
    return new JdbcTransactionManager(dataSource);
}

@Bean
public JdbcTransactionManager userTransactionManager(
        @Qualifier("userDataSource") DataSource dataSource) {
    return new JdbcTransactionManager(dataSource);
}

业务方法可以指定事务管理器:

复制代码
@Transactional(transactionManager = "userTransactionManager")
public void updateUser() {
    // 只操作用户库
}

MyBatis 项目还需为每个数据源分别配置 SqlSessionFactorySqlSessionTemplate 和 Mapper 扫描路径;JPA 项目则通常需要独立的 EntityManagerFactory、Repository 包和事务管理器。

4. 跨数据源事务问题

一个 JdbcTransactionManager 只管理对应数据源的一次本地事务。如果同一个业务方法同时写订单库和用户库,给它指定其中一个事务管理器,并不能让两个数据库共同提交或回滚。

跨库强一致需要评估 JTA/XA 或分布式事务方案;很多业务也会使用本地事务配合消息、事务表、幂等和补偿实现最终一致性。不能把两个 @Transactional 简单叠加就当作跨库原子事务。

固定多数据源与动态路由

本文配置的是固定多数据源:业务代码通过 @Qualifier、不同 Mapper 包或不同 Repository 明确选择数据库。另一种方式是继承 AbstractRoutingDataSource,根据租户、读写标记等上下文动态选择目标数据源。

动态路由减少了显式注入,但要特别注意切换时机。事务开始后,连接往往已经从当前数据源取得,此时再修改路由标记通常不会让同一事务切换到另一数据库。路由上下文还需要在请求结束后清理,避免线程池复用导致数据源串用。业务数据库固定且数量较少时,显式配置通常更容易理解和排查。

参考资料

相关推荐
卓怡学长1 小时前
w181springboot机场乘客服务系统
java·数据库·spring boot·spring·intellij-idea
战略性的菠萝1 小时前
学习笔记-Numpy知识
python·numpy
宫水三叶的刷题日记1 小时前
馋猫外卖:AI 让字节式试错没有天花板
后端
总有刁民想爱朕ha2 小时前
零基础Python开发「图片批量转MP4视频」工具,本地离线、免费无水印
开发语言·python·音视频
捧 花2 小时前
FastAPI 基础语法:从一个完整接口理解 Web API 的设计
前端·python·fastapi·middleware
Ming_studying2 小时前
Python批量压缩图片:支持JPG_PNG_WebP、尺寸限制与CSV报告
开发语言·图像处理·python·pillow·图片压缩
大熊背2 小时前
ISP图像处理中大数乘法溢出处理(一)
人工智能·python·算法·溢出处理
何以解忧,唯有..2 小时前
Python协程详解:从生成器到async/await的完整指南
开发语言·python
hhzz2 小时前
智慧校园13类视频异常检测:数据集与预训练模型全攻略
人工智能·pytorch·python·深度学习·目标检测·机器学习