Spring Boot 工程化核心详解:Parent、Starter、热部署、事务与多数据源
Spring Boot 的便利不只来自自动配置。项目中的父 POM、Starter、DevTools、事务代理和数据源配置共同构成了一套完整的工程化体系。本文基于 Spring Boot 3.x讲清这些能力如何使用以及它们背后的工作原理。一、为什么使用spring-boot-starter-parent一个典型 Maven 项目会继承parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.x.x/version relativePath/ /parent它的主要价值不是引入业务依赖而是提供统一的构建约定。1. 管理依赖版本Spring Boot 为 Spring Framework、Jackson、Logback、数据库驱动等常见组件维护了一套经过兼容性测试的版本组合。因此引入受管理依赖时通常不用单独写版本dependency groupIdorg.springframework.boot/groupId artifactIdspring-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 BOMdependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-dependencies/artifactId version3.x.x/version typepom/type scopeimport/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 时提供默认 BeanConditionalOnProperty配置满足条件时生效。因此Starter 让依赖到位自动配置根据 classpath、已有 Bean 和配置属性决定具体创建什么。用户自定义同类型 Bean 时默认配置通常会退让。三、Spring Boot 如何实现热部署开发阶段可以引入 DevToolsdependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-devtools/artifactId optionaltrue/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隔离级别使用数据库默认值RuntimeException和Error触发回滚受检异常默认不回滚。如果业务希望任何Exception都回滚可以明确配置rollbackFor Exception.class。常见传播行为REQUIRED是默认值外部已有事务就加入没有就新建适合一个业务入口统一操作多个 Repository。REQUIRES_NEW会挂起外部事务并创建独立事务内外可以分别提交或回滚但会额外占用数据库连接。NESTED通常基于 JDBC Savepoint 实现局部回滚并不等同于真正独立事务。传播行为只有在调用经过 Spring 代理时才会生效。同一个对象内部直接调用带REQUIRES_NEW的方法仍会绕过代理无法创建预期的新事务。实际项目中通常把需要不同事务边界的方法拆到另一个 Service。常见事务失效场景同类方法自调用实际是this.method()没有经过代理异常被catch后吞掉代理感知不到异常方法未通过 Spring Bean 调用使用错误的事务管理器数据库表或存储引擎本身不支持事务。在默认代理模式下建议把事务放在 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 项目还需为每个数据源分别配置SqlSessionFactory、SqlSessionTemplate和 Mapper 扫描路径JPA 项目则通常需要独立的EntityManagerFactory、Repository 包和事务管理器。4. 跨数据源事务问题一个JdbcTransactionManager只管理对应数据源的一次本地事务。如果同一个业务方法同时写订单库和用户库给它指定其中一个事务管理器并不能让两个数据库共同提交或回滚。跨库强一致需要评估 JTA/XA 或分布式事务方案很多业务也会使用本地事务配合消息、事务表、幂等和补偿实现最终一致性。不能把两个Transactional简单叠加就当作跨库原子事务。固定多数据源与动态路由本文配置的是固定多数据源业务代码通过Qualifier、不同 Mapper 包或不同 Repository 明确选择数据库。另一种方式是继承AbstractRoutingDataSource根据租户、读写标记等上下文动态选择目标数据源。动态路由减少了显式注入但要特别注意切换时机。事务开始后连接往往已经从当前数据源取得此时再修改路由标记通常不会让同一事务切换到另一数据库。路由上下文还需要在请求结束后清理避免线程池复用导致数据源串用。业务数据库固定且数量较少时显式配置通常更容易理解和排查。参考资料Spring Boot构建系统与 StarterSpring BootDeveloper ToolsSpring Boot数据访问配置Spring Framework使用Transactional