尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

SpringBoot启动流程解析与MyBatis整合实战指南

SpringBoot启动流程解析与MyBatis整合实战指南 1. 项目概述SpringBoot学习第四天的核心任务今天是我们“全网最详细SpringBoot学习”系列的第四天。如果你已经跟上了前三天的节奏那么恭喜你你已经搭建好了基础环境创建了第一个项目并且对SpringBoot的核心配置和自动装配有了初步的理解。今天我们将进入一个承上启下的关键阶段深入理解SpringBoot的启动流程并开始动手整合第一个外部组件。很多初学者在学完基础配置后会感到迷茫不知道下一步该做什么。Day04的目标就是解决这个问题。我们将不再停留在“Hello World”的层面而是去探究SpringBoot这个“黑盒子”在按下“运行”按钮后内部究竟发生了什么。同时我们会选择一个极具代表性的组件——MyBatis进行整合实战。选择MyBatis是因为它是Java领域使用最广泛的数据持久层框架与SpringBoot的整合是开发Web应用几乎必过的坎。通过今天的实践你将掌握从原理到实操的完整链路为后续构建复杂的业务应用打下坚实基础。简单来说Day04就是带你从“会用”走向“懂原理、能实战”的转折点。我们会拆解启动过程让你明白每一行日志背后的意义然后手把手教你连接数据库操作数据并在这个过程中穿插讲解那些面试常问的注解和配置。准备好了吗我们开始深入。2. 核心原理深潜SpringBoot启动流程全解析理解SpringBoot的启动流程是摆脱“配置工程师”标签迈向高级开发的关键一步。这个过程远不止是执行一个main方法那么简单它是一个精密的、事件驱动的初始化链条。2.1 启动入口与SpringApplication初始化一切的起点是那个标注了SpringBootApplication的类中的main方法。当我们运行SpringApplication.run(Application.class, args)时SpringBoot的引擎就启动了。首先SpringBoot会创建一个SpringApplication实例。在这个过程中它会进行几项关键操作推断应用类型根据类路径下存在的类判断这是一个普通的Java应用、Web应用Servlet还是响应式Web应用Reactive。这决定了后续如何创建和配置应用上下文ApplicationContext。加载初始化器从META-INF/spring.factories文件中加载所有配置的ApplicationContextInitializer。这些初始化器允许我们在应用上下文被刷新refresh之前对其进行编程式的配置。这是一个非常重要的扩展点。加载监听器同样从spring.factories中加载ApplicationListener。监听器是Spring事件驱动编程模型的核心它们会监听在启动过程中发布的各种事件例如ApplicationStartingEvent、ApplicationPreparedEvent等。我们常用的CommandLineRunner和ApplicationRunner也是基于此机制实现的。注意很多同学对spring.factories文件感到神秘。在SpringBoot 2.7之前它是自动配置、初始化器、监听器的主要注册方式。从2.7版本开始SpringBoot鼓励使用META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件来注册自动配置类但为了向后兼容spring.factories机制依然有效。理解这个文件你就理解了SpringBoot“约定大于配置”和SPI服务发现机制的精髓。2.2 运行阶段与事件发布SpringApplication实例初始化完成后便进入run方法。这是一个标准模板方法其核心是ConfigurableApplicationContext的创建与刷新。发布ApplicationStartingEvent这是第一个发布的事件此时应用上下文还未创建但监听器已就位。一些非常底层的监听器如日志系统初始化监听器会在此事件中工作。准备环境创建并配置应用的环境Environment。它会加载所有的属性源包括application.properties、application.yml、命令行参数、系统环境变量等并完成属性的解析和占位符替换。这就是为什么你可以在代码中通过Value注解注入配置值的原因。创建应用上下文根据第一步推断出的应用类型实例化对应的ApplicationContext。对于最常见的Web应用就是AnnotationConfigServletWebServerApplicationContext。准备上下文这是一个关键步骤。在这里SpringBoot会做几件大事调用初始化器执行之前加载的所有ApplicationContextInitializer的initialize方法。发布ApplicationContextInitializedEvent。加载源将我们的主配置类即标注了SpringBootApplication的类注册为Bean定义的来源。发布ApplicationPreparedEvent此时Bean定义已加载但Bean还未实例化。这是进行一些动态Bean注册或修改的最后时机。刷新应用上下文调用上下文的refresh()方法。这是整个Spring IoC容器启动的核心也是SpringBoot自动装配魔法发生的地方。它包含了Bean工厂的后置处理、Bean的实例化、依赖注入、初始化等完整生命周期。EnableAutoConfiguration注解的核心功能就是在此阶段通过AutoConfigurationImportSelector加载所有符合条件的自动配置类。发布ApplicationStartedEvent上下文刷新完成应用已启动但此时CommandLineRunner和ApplicationRunner还未执行。调用Runner执行所有实现了CommandLineRunner或ApplicationRunner接口的Bean。这是执行启动后特定代码的标准位置比如初始化缓存、加载基础数据等。发布ApplicationReadyEvent这是一个重要的信号表明应用已完全准备就绪可以开始对外提供服务了。健康检查、监控探针通常在此事件之后才返回成功状态。返回上下文最终run方法返回已经启动好的ApplicationContext。理解这个流程你就能清晰地定位如果我的配置不生效是在环境准备阶段的问题如果我的Bean没创建是在刷新阶段的哪个环节被过滤了我的启动代码应该放在Initializer、Listener还是Runner里这为排查复杂的启动问题提供了清晰的路线图。3. 实战整合SpringBoot与MyBatis的完美协作理论需要实践来巩固。现在我们开始整合SpringBoot与MyBatis这是开发现实业务系统的基石。我们将采用目前最主流、最推荐的方式MyBatis-Spring-Boot-Starter。3.1 依赖引入与基础配置首先在项目的pom.xml文件中添加依赖。这里有一个版本选择的小技巧通常我们不需要显式指定MyBatis-Spring-Boot-Starter的版本因为SpringBoot的父工程或依赖管理spring-boot-dependencies已经为我们管理了兼容的版本。这能极大避免依赖冲突。dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version3.0.3/version !-- 通常可由SpringBoot管理此处为清晰展示 -- /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency接下来是application.yml的配置。我强烈推荐使用YAML格式因为它结构更清晰特别适合多环境配置。spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/your_database?useUnicodetruecharacterEncodingutf-8serverTimezoneAsia/Shanghai username: root password: your_password hikari: # 使用HikariCP连接池SpringBoot 2.x默认 connection-timeout: 30000 maximum-pool-size: 20 mybatis: # 重要指定Mapper接口对应的XML映射文件的位置 mapper-locations: classpath:mapper/*.xml # 配置实体类别名包这样在XML中就可以直接用类名而不用全限定名 type-aliases-package: com.yourdomain.demo.entity configuration: # 开启驼峰命名自动映射数据库user_name - 实体类userName map-underscore-to-camel-case: true # 打印执行的SQL到控制台调试利器 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl实操心得关于mapper-locations的路径很多人会踩坑。classpath:指的是编译后的target/classes目录或源码的resources目录。确保你的XML文件最终在这个路径下。另一个常见问题是type-aliases-package配置后在IDEA里XML中写别名还是报红这是IDEA的识别问题不影响运行可以通过安装MyBatisX插件来获得更好的支持。3.2 实体、Mapper与XML映射编写我们以一个简单的User实体为例。// User.java Data // 使用Lombok简化getter/setter public class User { private Long id; private String username; private String email; // 数据库字段可能是 user_email靠 map-underscore-to-camel-case 映射 }MyBatis的Mapper接口是核心它定义了数据操作的方法。// UserMapper.java Mapper // 关键注解声明这是一个MyBatis的MapperSpring会自动为其创建代理实现 public interface UserMapper { User selectById(Param(id) Long id); ListUser selectAll(); int insert(User user); int updateById(User user); int deleteById(Param(id) Long id); }对应的XML映射文件UserMapper.xml需要放在resources/mapper/目录下。?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.yourdomain.demo.mapper.UserMapper resultMap idBaseResultMap typeUser id columnid propertyid/ result columnusername propertyusername/ result columnuser_email propertyemail/ /resultMap select idselectById resultMapBaseResultMap SELECT * FROM user WHERE id #{id} /select select idselectAll resultMapBaseResultMap SELECT * FROM user /select insert idinsert useGeneratedKeystrue keyPropertyid INSERT INTO user (username, user_email) VALUES (#{username}, #{email}) /insert /mapper3.3 Service层与控制层调用在Service中注入Mapper完成业务逻辑封装。// UserService.java Service public class UserService { Autowired private UserMapper userMapper; public User getUserById(Long id) { return userMapper.selectById(id); } public ListUser getAllUsers() { return userMapper.selectAll(); } }最后通过一个简单的REST控制器暴露接口。// UserController.java RestController RequestMapping(/users) public class UserController { Autowired private UserService userService; GetMapping(/{id}) public User getUser(PathVariable Long id) { return userService.getUserById(id); } GetMapping public ListUser getUsers() { return userService.getAllUsers(); } }启动应用访问http://localhost:8080/users如果一切配置正确你将看到数据库中的用户列表以JSON格式返回。观察控制台你应该能看到MyBatis打印出的SQL语句这验证了整合是成功的。4. 进阶技巧与深度配置解析基础整合完成后我们来看看如何让这个组合更加强大和优雅解决一些实际开发中必然会遇到的问题。4.1 使用注解替代XML映射对于简单的SQLMyBatis支持使用注解直接写在Mapper接口上避免编写XML文件。这可以使代码更紧凑。Mapper public interface UserMapper { Select(SELECT * FROM user WHERE id #{id}) Results(id userMap, value { Result(property id, column id, id true), Result(property email, column user_email) }) User selectById(Long id); Insert(INSERT INTO user(username, user_email) VALUES(#{username}, #{email})) Options(useGeneratedKeys true, keyProperty id) int insert(User user); }注意事项注解方式虽然简洁但在处理动态SQL如多条件查询时远不如XML灵活和强大。XML可以方便地使用if,choose,foreach等标签。我个人的经验是简单的、固定的CURD操作可以用注解复杂的、动态的查询强烈建议使用XML结构更清晰也更易于维护。4.2 配置多数据源与事务管理在微服务架构下一个服务连接多个数据库的场景越来越常见。SpringBoot整合MyBatis实现多数据源需要一些手动配置。首先在配置文件中定义两个数据源。spring: datasource: primary: jdbc-url: jdbc:mysql://localhost:3306/db_primary username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver secondary: jdbc-url: jdbc:mysql://localhost:3306/db_secondary username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver然后你需要为每个数据源创建独立的配置类。这里以主数据源为例Configuration MapperScan(basePackages com.demo.mapper.primary, sqlSessionFactoryRef primarySqlSessionFactory) public class PrimaryDataSourceConfig { Bean(name primaryDataSource) ConfigurationProperties(prefix spring.datasource.primary) Primary // 标记为主数据源 public DataSource primaryDataSource() { return DataSourceBuilder.create().build(); } Bean(name primarySqlSessionFactory) Primary public SqlSessionFactory primarySqlSessionFactory(Qualifier(primaryDataSource) DataSource dataSource) throws Exception { SqlSessionFactoryBean bean new SqlSessionFactoryBean(); bean.setDataSource(dataSource); // 设置mapper.xml位置如果和主数据源路径不同的话 bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(classpath:mapper/primary/*.xml)); return bean.getObject(); } Bean(name primaryTransactionManager) Primary public DataSourceTransactionManager primaryTransactionManager(Qualifier(primaryDataSource) DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }对于次数据源创建类似的SecondaryDataSourceConfig类去掉Primary注解并修改对应的Bean名称和路径即可。事务管理在Service方法上使用Transactional注解时默认会使用标记了Primary的事务管理器。如果你需要在同一个方法中跨数据源操作并保持事务性这就涉及分布式事务的范畴SpringBoot默认的单一数据源事务管理器无法满足需要引入如Seata这样的分布式事务解决方案这超出了本篇基础整合的范围。4.3 整合分页插件PageHelper在Web应用中分页查询是刚需。MyBatis有一个非常流行的分页插件PageHelper。在SpringBoot中整合它异常简单。添加依赖dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version2.1.0/version /dependency在application.yml中增加配置pagehelper: helper-dialect: mysql # 指定数据库方言 reasonable: true # 分页参数合理化。当pageNum0时设为1pageNum总页数时设为最后一页 support-methods-arguments: true # 支持通过Mapper接口参数来传递分页参数使用示例// 在Service中 public PageInfoUser getUsersByPage(int pageNum, int pageSize) { // 关键紧跟在查询方法前的PageHelper.startPage静态方法调用 PageHelper.startPage(pageNum, pageSize); // 接下来执行的第一个MyBatis查询方法会自动进行分页 ListUser userList userMapper.selectAll(); // 用PageInfo包装结果它包含了非常丰富的分页信息总条数、总页数、当前页等 return new PageInfo(userList); }踩坑记录PageHelper.startPage(pageNum, pageSize)这行代码必须紧贴在执行数据库查询的代码之前中间不能有任何其他可能会执行SQL的代码否则分页会失效。这是一个非常高频的踩坑点。5. 生产环境部署与运维考量开发完成的应用最终要部署到服务器上。SpringBoot应用最常见的部署方式是打包成可执行的JAR文件通过java -jar命令运行。5.1 打包与运行SpringBoot的Maven插件默认会打包成可执行JAR包含所有依赖和嵌入式Web服务器。# 在项目根目录下执行打包 mvn clean package # 打包后会在target目录下生成一个名为 your-project-0.0.1-SNAPSHOT.jar 的文件 # 运行应用 java -jar target/your-project-0.0.1-SNAPSHOT.jar你可以通过命令行参数覆盖配置文件中的属性这在多环境部署时非常有用java -jar your-app.jar --server.port8081 --spring.datasource.urljdbc:mysql://prod-db:3306/prod_db5.2 外部化配置与多环境切换生产环境的配置数据库地址、密码、Redis连接等绝不能写在代码或打包进JAR的配置文件中。SpringBoot提供了强大的外部化配置机制优先级从高到低如下命令行参数。SPRING_APPLICATION_JSON中的属性内嵌在环境变量或系统属性中的JSON。ServletConfig初始化参数。ServletContext初始化参数。JNDI属性来自java:comp/env。Java系统属性System.getProperties()。操作系统环境变量。仅在random.*中存在的RandomValuePropertySource。打包在JAR外的、针对特定环境的配置文件例如application-{profile}.properties或.yml。打包在JAR内的、针对特定环境的配置文件。打包在JAR外的应用配置文件application.properties或.yml。打包在JAR内的应用配置文件。最佳实践在项目中我们通常使用application.yml作为默认配置。然后为不同环境创建application-dev.yml开发、application-test.yml测试、application-prod.yml生产。在application.yml中通过spring.profiles.active指定默认激活的环境如dev。在生产服务器上我们不在JAR内放置application-prod.yml而是将生产配置放在JAR包同级目录的config子目录下或者通过命令行参数--spring.config.location指定外部配置文件路径。这样同一份JAR包配合不同的外部配置就能在任何环境运行。5.3 健康检查与监控SpringBoot Actuator模块为应用提供了完善的生产级监控端点。添加依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-actuator/artifactId /dependency默认情况下只有/actuator/health和/actuator/info端点是通过HTTP暴露的。你可以在配置中开启更多端点并配置安全比如整合Spring Security。management: endpoints: web: exposure: include: * # 暴露所有端点生产环境慎用应精确指定 endpoint: health: show-details: always # 健康检查显示详细信息访问/actuator/health可以查看应用健康状态数据库连接、磁盘空间等。这对于Kubernetes的存活探针和就绪探针非常有用。5.4 使用Docker容器化部署将SpringBoot应用Docker化是现代部署的标准做法。编写一个简单的Dockerfile# 使用官方的OpenJDK运行时作为父镜像 FROM openjdk:17-jdk-slim # 设置工作目录 WORKDIR /app # 将打包好的jar文件复制到容器中 COPY target/your-project-0.0.1-SNAPSHOT.jar app.jar # 暴露端口 EXPOSE 8080 # 运行jar文件 ENTRYPOINT [java, -jar, app.jar]构建并运行镜像docker build -t my-springboot-app . docker run -p 8080:8080 -d my-springboot-app在Docker运行中可以通过环境变量传递配置docker run -p 8080:8080 -e SPRING_DATASOURCE_URLjdbc:mysql://host.docker.internal:3306/db -d my-springboot-app6. 常见问题排查与性能调优指南即使按照最佳实践操作在实际开发和运维中你依然会遇到各种问题。这里记录了一些高频问题的排查思路和解决方案。6.1 启动类常见问题问题SpringBootApplication注解的类找不到排查检查类是否在根包或子包下。SpringBoot默认会扫描主类所在包及其所有子包下的组件。如果Mapper、Service在其他不相关的包下需要使用ComponentScan或MapperScan手动指定扫描路径。问题端口被占用Port 8080 already in use解决在application.yml中修改server.port或使用命令行启动时指定--server.port8081。在Linux/Mac上可以用lsof -i:8080找到占用进程并结束它。6.2 MyBatis整合相关故障问题Invalid bound statement (not found)这是MyBatis新手遇到最多的问题。排查步骤检查XML文件位置确认mybatis.mapper-locations配置的路径是否正确XML文件是否真的被复制到了target/classes对应的目录下。可以解压生成的JAR包查看。检查namespaceXML中mapper namespace”…”的值必须是Mapper接口的全限定名包括包名一个字符都不能错。检查方法名与idXML中SQL语句的id必须和Mapper接口中的方法名完全一致。Maven资源过滤确保pom.xml中配置了资源文件过滤否则XML文件可能不会被复制。通常SpringBoot项目不需要额外配置。问题数据库连接失败排查检查spring.datasource.url、username、password是否正确。检查数据库服务是否启动网络是否通畅telnet ip port。检查数据库驱动类名是否正确。MySQL 8通常使用com.mysql.cj.jdbc.Driver且URL需要添加时区参数serverTimezone。检查数据库用户是否有远程连接权限如果非本地连接。6.3 性能调优要点连接池配置SpringBoot 2.x默认使用HikariCP性能很好。主要调整maximum-pool-size根据数据库和服务负载设置通常10-20足够和connection-timeout。MyBatis一级/二级缓存一级缓存默认开启是SqlSession级别的。在同一个SqlSession中相同的查询只会执行一次数据库操作。但在分布式或微服务环境下需要注意其生命周期。二级缓存需要手动在XML映射文件中通过cache/标签开启是Mapper namespace级别的可以被多个SqlSession共享。使用二级缓存需要极其小心因为数据更新后缓存不会自动失效容易导致脏读。在读写频繁的场景下不建议开启或者使用更专业的集中式缓存如Redis。SQL优化这是性能问题的根本。善用MyBatis的SQL日志配置log-impl为StdOutImpl或集成SLF4J分析慢查询。结合数据库的EXPLAIN命令为查询添加合适的索引。JVM参数调优对于生产环境在启动JAR时建议设置基本的JVM参数。java -Xms512m -Xmx1024m -XX:UseG1GC -jar your-app.jar-Xms和-Xmx设置堆内存初始大小和最大值根据服务器内存设置。-XX:UseG1GC指定使用G1垃圾收集器在大多数场景下比默认的Parallel GC有更好的延迟表现。6.4 日志管理与问题排查SpringBoot默认使用Logback日志框架并通过SLF4J门面进行调用。在application.yml中可以进行详细配置logging: level: root: INFO # 根日志级别 com.yourdomain.demo.mapper: DEBUG # 将MyBatis Mapper接口的日志级别设为DEBUG可以打印出执行的SQL和参数 org.springframework.web: INFO file: name: logs/app.log # 输出到文件 pattern: console: %d{yyyy-MM-dd HH:mm:ss} - %msg%n # 控制台输出格式当应用出现异常时不要只看最后一行的错误信息。从日志的最顶端开始看找到第一个Caused by那往往是问题的根源。对于复杂的依赖冲突问题可以使用Maven命令mvn dependency:tree生成依赖树进行分析。经过Day04的深入学习你应该对SpringBoot不再感到陌生和畏惧。你知道了它如何启动知道了如何与MyBatis这样的核心组件协作也了解了如何将它部署到生产环境并保持健康。这些知识构成了一个SpringBoot开发者能力模型的中坚部分。接下来的学习无论是集成缓存Redis、消息队列RabbitMQ还是构建更复杂的微服务都是在这个坚实的基础上添砖加瓦。记住多动手多思考遇到问题善用搜索引擎和官方文档你会在SpringBoot的世界里越走越顺。
返回列表