本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:这是一个基于SpringBoot、Mybatis和MySQL的完整学习型Web开发项目,专为Java初学者设计,涵盖从环境搭建到功能实现的全流程实践。项目集成源代码、数据库文件及说明文档,并提供创建教程,帮助开发者快速掌握三大核心技术的整合应用。通过使用IntelliJ IDEA开发工具,学习者可深入理解SpringBoot的自动配置机制、Mybatis的SQL映射与数据持久化操作,以及MySQL数据库的设计与管理,全面掌握Web应用的前后端交互、事务控制和CRUD实现,是提升Java全栈开发能力的理想练习项目。

SpringBoot 与 MyBatis 架构深度整合实战

在现代 Java 后端开发中,我们早已告别了“手写 JDBC 模板代码 + 配置 XML 文件满天飞”的时代。取而代之的是一套高度自动化、模块化、约定驱动的技术体系——SpringBoot 正是这套体系的核心引擎。

你有没有经历过这样的场景?一个新项目启动,光是配置 web.xml applicationContext.xml 、数据源、事务管理器就花掉整整两天,还没开始写业务逻辑……🤯

别担心,今天我们要聊的,就是如何用 SpringBoot + MyBatis 快速构建企业级应用,不仅“能跑”,更要“懂它为啥能跑”、“怎么让它跑得更快更稳”。

咱们不搞浮于表面的“Hello World”教程,而是从底层机制入手,层层拆解:自动配置是怎么玩的?起步依赖背后藏着什么秘密?MyBatis 的动态代理到底是怎么把接口变成 SQL 执行器的?

准备好了吗?🚀 咱们这就出发!


自动配置不是魔法,是精密设计的艺术 🧩

很多人初识 SpringBoot 时都会惊叹:“哇,加个注解就能启动 Web 服务?”但其实这背后并没有魔法,只有一套极其聪明的设计哲学 —— “ 约定优于配置 ”。

什么意思呢?简单说就是:

“如果你不做特别说明,我就按大家最常用的习惯来帮你搞定一切。”

比如你引入了 spring-boot-starter-web ,框架就知道你要做 Web 开发,于是它会自动:

  • 内嵌 Tomcat
  • 注册 DispatcherServlet
  • 配置视图解析器
  • 加载 Jackson 支持 JSON 序列化

这一切都不需要你手动去写一行 XML 或 JavaConfig。

@SpringBootApplication:三合一的超级开关 🔌

每个 SpringBoot 项目的入口类上都有这么一行代码:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

看起来只是一个注解,但它其实是三个关键注解的组合体:

注解 作用
@SpringBootConfiguration 标明这是一个配置类(本质是 @Configuration
@ComponentScan 自动扫描当前包及其子包下的组件
@EnableAutoConfiguration 启动自动配置机制

我们可以把它想象成一辆车的点火钥匙:
🔑 插进去 → 扫描组件发现有哪些零件可用
🔥 转动 → 触发自动配置加载合适的 Bean
🚗 发动 → 整个应用就跑起来了!

小技巧:排除不必要的自动配置

有时候某些 Starter 引入了你不想要的功能,比如 Redis、Security 等,会导致启动变慢甚至冲突。

这时候可以这样排除:

@SpringBootApplication(exclude = {
    RedisAutoConfiguration.class,
    SecurityAutoConfiguration.class
})

这对微服务架构尤其重要——避免加载无用 Bean,节省内存和启动时间 ⏱️。


起步依赖(Starter):Maven 的“智能套餐” 🍱

还记得以前我们要做一个 Web 项目,得一个个添加依赖吗?

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.21</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.4</version>
</dependency>
<!-- 还有 servlet-api、tomcat-embed-core…… -->

版本还容易冲突,头疼 😣

现在?一行搞定:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

这就是所谓的“起步依赖”(Starter),它本质上是一个 依赖聚合器 + 自动配置触发器

它是怎么工作的?

SpringBoot 通过 META-INF/spring.factories 文件实现了一种叫 Spring Factories 的服务发现机制。

这个文件长这样:

# META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration

@EnableAutoConfiguration 被激活时,SpringBoot 会扫描所有 JAR 包中的 spring.factories ,合并这些配置类并按条件加载。

🎯 流程如下:

flowchart TD
    A[启动 SpringApplication] --> B{读取 @EnableAutoConfiguration}
    B --> C[扫描所有 JAR 中的 META-INF/spring.factories]
    C --> D[合并所有 EnableAutoConfiguration 条目]
    D --> E[去重并排序(支持 @AutoConfigureOrder)]
    E --> F[按顺序创建 ConfigurationClassParser 解析]
    F --> G[根据 @Conditional 条件决定是否注册 Bean]
    G --> H[完成自动配置注入]

看到没?整个过程就像一场精心编排的交响乐,每个 Starter 都是演奏者,由指挥家(SpringBoot)统一调度。


条件化装配:只有满足条件才生效 ✅

你以为自动配置是“一锅端”?错!它是有条件的。

SpringBoot 大量使用 @Conditional 系列注解来判断某个配置是否应该生效。

注解 判断条件 典型用途
@ConditionalOnClass 类路径存在指定类 如存在 DispatcherServlet 才启用 WebMvc 配置
@ConditionalOnMissingBean 容器中不存在该类型 Bean 防止重复注册数据源
@ConditionalOnProperty 配置文件中存在某属性且值匹配 控制开关特性
@ConditionalOnWebApplication 当前为 Web 环境 区分 Web/非Web 场景

举个例子,我们来自定义一个日志自动配置:

@Configuration
@ConditionalOnClass(LoggingService.class)
@ConditionalOnMissingBean(LoggingService.class)
@ConditionalOnProperty(name = "logging.enabled", havingValue = "true", matchIfMissing = true)
public class LoggingAutoConfiguration {

    @Bean
    public LoggingService loggingService() {
        return new DefaultLoggingService();
    }
}

解读一下:

  • 只有类路径里有 LoggingService 才加载;
  • 如果你自己已经定义了一个 LoggingService ,那就不覆盖;
  • 默认开启,可通过 logging.enabled=false 关闭;

💡 这种模式非常适合做功能开关(Feature Toggle),比如灰度发布新支付流程:

feature:
  payment-v2: true

对应配置类:

@Configuration
@ConditionalOnProperty("feature.payment-v2")
public class PaymentV2Config { ... }

上线时只需改个配置,无需重新打包部署,香不香?😎


版本管理的艺术:Parent vs BOM 🤝

你可能会问:这么多 Starter,版本不会乱吗?

当然不会!SpringBoot 提供了两种方式锁定依赖版本:

方式一:继承 parent POM

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.0</version>
    <relativePath/>
</parent>

好处:开箱即用,连编译插件都帮你配好了。
缺点:必须作为父模块,灵活性差。

方式二:导入 BOM(推荐)

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>3.2.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

优点:不影响原有父模块,适合多模块项目或已有公司基础 POM 的场景。

📌 推荐大型项目使用 BOM 方式,自由度更高!


自定义 Starter:打造团队专属工具包 🛠️

当你发现多个项目都在重复引入相同的配置和工具类时,是时候封装自己的 Starter 了!

标准做法:两个模块

模块名 作用
my-starter-autoconfigure 放自动配置类、 spring.factories
my-starter 空 jar,只依赖 autoconfigure,方便别人引入

示例:自定义数据库健康检查 Starter

@Configuration
@ConditionalOnClass(DataSource.class)
@ConditionalOnProperty(name = "health.check.database.enabled", matchIfMissing = true)
public class DatabaseHealthCheckAutoConfiguration {

    @Bean
    public HealthIndicator databaseHealthIndicator(DataSource dataSource) {
        return new DataSourceHealthIndicator(dataSource);
    }
}

然后注册到 spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.mycompany.starter.autoconfigure.DatabaseHealthCheckAutoConfiguration

发布后,其他项目只需引入:

<dependency>
    <groupId>com.mycompany</groupId>
    <artifactId>my-starter</artifactId>
    <version>1.0.0</version>
</dependency>

立刻拥有数据库健康检查能力 💪

命名建议:

  • 自动配置模块: xxx-spring-boot-autoconfigure
  • 起步依赖模块: xxx-spring-boot-starter
  • 第三方集成: spring-boot-starter-xxx

比如阿里云短信服务就可以叫: aliyun-sms-spring-boot-starter


外部化配置:一次编写,多环境运行 🌐

随着系统复杂度上升,配置管理变得越来越重要。SpringBoot 提供了强大的外部化配置体系。

application.yml vs application.properties

两者都能用,但风格不同:

application.properties

server.port=8081
spring.datasource.url=jdbc:mysql://localhost:3306/demo
spring.datasource.username=root

application.yml(更推荐)

server:
  port: 8081

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/demo
    username: root
    password: secret

YAML 更适合嵌套结构,可读性更强 👀

加载优先级(从高到低)

来源 说明
命令行参数 --server.port=9090
SPRING_APPLICATION_JSON 环境变量中定义 JSON 格式配置
application-{profile}.yml application-prod.yml
application.yml 主配置文件
@PropertySource 手动加载指定文件

✅ 实践建议:生产环境用命令行动态传参,避免敏感信息硬编码!


类型安全配置绑定:告别 @Value(“${}”) ❌

传统的 @Value("${db.url}") 有几个痛点:

  • 类型转换麻烦
  • 不支持校验
  • 重构困难
  • IDE 提示弱

更好的方式是使用 @ConfigurationProperties

@Component
@ConfigurationProperties(prefix = "custom.datasource")
@Validated
public class CustomDataSourceProperties {

    @NotBlank
    private String url;

    @NotBlank
    private String username;

    @DurationUnit(ChronoUnit.SECONDS)
    private Duration connectionTimeout = Duration.ofSeconds(30);

    // getter/setter
}

配合配置文件:

custom:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb
    username: admin
    password: strongpass
    connection-timeout: 20s

优势明显:

  • 支持 JSR-303 校验(需加 @Validated
  • 支持 Duration , DataSize 等丰富类型
  • IDE 友好,支持提示与重构

⚠️ 注意:必须加上 @Component 或启用 @EnableConfigurationProperties 才能生效。


多环境支持:dev/test/prod 自由切换 🔄

SpringBoot 支持基于 Profile 的多环境配置。

文件命名规则

  • application.yml —— 公共配置
  • application-dev.yml —— 开发环境
  • application-test.yml —— 测试环境
  • application-prod.yml —— 生产环境

激活方式

  1. 配置文件中指定:
    yaml spring: profiles: active: dev

  2. 命令行激活:
    bash java -jar app.jar --spring.profiles.active=prod

  3. 环境变量:
    bash export SPRING_PROFILES_ACTIVE=test

动态切换流程如下:

graph LR
    A[启动应用] --> B{检查激活 Profile}
    B --> C[读取 application.yml]
    B --> D[读取 application-{profile}.yml]
    C --> E[合并配置]
    D --> E
    E --> F[最终生效配置]

高级玩法:结合 spring.config.import 导入远程配置中心(如 Nacos、Config Server),实现集中式配置管理。


MyBatis:半自动化 ORM 的王者 👑

如果说 SpringBoot 是发动机,那 MyBatis 就是最趁手的武器之一。

它不像 JPA 那样完全屏蔽 SQL,也不像原生 JDBC 那样繁琐,而是走了一条“中间路线”—— SQL 显式可控 + 映射自动完成

SqlSessionFactory 与 SqlSession 的协作机制

MyBatis 的核心是 SqlSessionFactory ,它是线程安全的单例对象,负责生成 SqlSession

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

而在 SpringBoot 环境下,这一切都被自动化了:

  1. mybatis-spring-boot-starter 自动检测是否存在 DataSource
  2. 若存在,则创建 SqlSessionFactory 并注册为 Bean
  3. 通过 @MapperScan 扫描接口并生成代理对象
组件 生命周期 线程安全性
SqlSessionFactory 应用级单例 ✅ 安全
SqlSession 请求/事务级 ❌ 不安全
Mapper Proxy 依赖 SqlSession ✅(由 Spring 管理)

调用链路如下:

classDiagram
    class SqlSessionFactory {
        +openSession() SqlSession
    }
    class SqlSession {
        +getMapper(Class<T>) T
        +selectOne(String, Object)
        +insert(String, Object)
        +update(String, Object)
        +delete(String, Object)
    }

    class UserMapper {
        +User selectById(Long id)
        +List~User~ selectAll()
    }

    SqlSessionFactory --> SqlSession : 创建
    SqlSession --> UserMapper : getMapper()
    UserMapper ..> SqlSession : 调用底层执行

你会发现, UserMapper 其实是个代理对象,当你调用 selectById(1L) 时,它会被拦截,拼接出 SQL ID(如 com.example.mapper.UserMapper.selectById ),再交给 Executor 去执行。


Executor 执行器:性能调优的关键按钮 🎯

MyBatis 提供三种主要类型的 Executor ,直接影响性能表现。

类型 PreparedStatement 重用 批量支持 适用场景
SimpleExecutor(默认) 开发调试
ReuseExecutor 查询密集型
BatchExecutor 批量插入/更新

例如,在批量导入日志数据时,使用 BatchExecutor 可提升 5~10 倍速度:

try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
    UserMapper mapper = session.getMapper(UserMapper.class);
    for (int i = 0; i < 1000; i++) {
        User user = new User("user" + i);
        mapper.insert(user); // 添加到批处理队列
    }
    session.commit(); // 触发 executeBatch()
}

⚠️ 记住:一定要 commit() 才真正提交,否则不会生效!

在 SpringBoot 中可通过配置启用:

mybatis:
  configuration:
    default-executor-type: batch

动态代理揭秘:接口是如何变成 SQL 执行器的?🔍

MyBatis 最神奇的地方在于: 你只写了接口,没有实现类,却能正常调用方法执行 SQL

靠的是 JDK 动态代理 + 反射机制。

当调用 sqlSession.getMapper(UserMapper.class) 时,MyBatis 会:

  1. 检查缓存是否有该接口的代理工厂;
  2. 若无,则创建 MapperProxyFactory
  3. 返回一个实现了 InvocationHandler MapperProxy
  4. 每次方法调用都会被 invoke() 拦截;
  5. 提取方法签名,定位到对应的 MappedStatement
  6. 执行 SQL 并返回结果。

完整调用流程:

sequenceDiagram
    participant App as 应用代码
    participant Session as SqlSession
    participant Proxy as MapperProxy
    participant Method as MapperMethod
    participant Executor as Executor
    participant DB as 数据库

    App->>Session: getMapper(UserMapper.class)
    Session->>Proxy: 创建动态代理
    App->>Proxy: selectById(1L)
    Proxy->>Method: execute(session, args)
    Method->>Executor: query(mappedStmt, params)
    Executor->>DB: 执行SQL并返回ResultSet
    DB-->>Executor: 结果集
    Executor-->>Method: 处理结果
    Method-->>Proxy: 返回User对象
    Proxy-->>App: 返回结果

清晰吧?每一层都有明确职责,彻底解耦业务逻辑与数据库操作。


接口与 XML 分离:关注点分离的最佳实践 🧩

MyBatis 推崇“Java 接口 + XML 映射文件”的分离模式。

好处显而易见:

  • SQL 与 Java 代码分离,便于 DBA 审核优化
  • 支持复杂动态 SQL 拼接
  • 更容易做 SQL 性能分析
  • 团队协作更顺畅

命名空间绑定规则

XML 中的 namespace 必须等于接口的全限定名,否则会报错:

Invalid bound statement (not found)

正确示例:

<mapper namespace="com.example.mapper.UserMapper">
    <select id="findById" resultType="User">
        SELECT * FROM users WHERE id = #{id}
    </select>
</select>

参数传递技巧

多个参数必须加 @Param ,否则无法识别:

List<User> findByUsernameAndStatus(
    @Param("username") String username,
    @Param("status") Integer status);

否则会报错:

Parameter ‘username’ not found. Available parameters are [arg0, arg1, param1, param2]

虽然可以用 #{arg0} ,但强烈不推荐,因为一旦参数顺序改变就会出错。


CRUD 实战:从插入到事务控制全流程演示 💼

以“用户-订单”模型为例,展示完整 CRUD 实现。

插入并获取主键

<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO user(username, email) VALUES(#{username}, #{email})
</insert>

执行后, user.getId() 会自动赋值,无需额外查询。

流程图如下:

sequenceDiagram
    participant Service
    participant Mapper
    participant MyBatis
    participant Database

    Service->>Mapper: insertUser(user)
    Mapper->>MyBatis: 执行 insertUser 映射
    MyBatis->>Database: INSERT INTO user(...) VALUES(...)
    Database-->>MyBatis: 返回 generated key = 1001
    MyBatis->>MyBatis: 反射设置 user.id = 1001
    MyBatis-->>Mapper: 返回影响行数=1
    Mapper-->>Service: 成功插入,user.id=1001

更新/删除的返回值判断

这两个操作都返回受影响行数,务必判断!

int rows = userMapper.updateUser(user);
if (rows == 0) {
    throw new IllegalArgumentException("用户不存在");
}

忽略返回值可能导致“伪成功”问题,非常危险 ⚠️


事务管理:@Transactional 的正确打开方式 🔐

Spring 的声明式事务极大简化了事务控制,但也容易误用。

传播行为选择指南

行为 说明 使用场景
REQUIRED(默认) 有则加入,无则新建 普通业务方法
REQUIRES_NEW 新建事务,挂起当前 日志记录
SUPPORTS 支持事务,无也可 查询方法
MANDATORY 必须运行在事务中 强制上下文

示例:

@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Order order) {
    orderMapper.insert(order);
    inventoryService.reduceStock(order.getItemId());
}

若减库存失败,订单也会回滚,保证一致性。

自调用导致事务失效的问题

常见错误:

@Service
public class UserService {
    public void register() {
        addUser();     // ❌ 自调用,事务失效!
        bindRole();    // ❌ 即使方法上有 @Transactional
    }

    @Transactional
    public void addUser() { ... }
}

解决办法:

  1. 提取到另一个 Service
  2. 使用 AOP 上下文获取代理对象(不推荐)

完整 Web 应用开发流程:从建表到部署 🚀

最后,我们搭建一个完整的用户管理系统。

数据库建模

三张核心表:

CREATE TABLE user (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) UNIQUE,
    password VARCHAR(100),
    email VARCHAR(100),
    status TINYINT DEFAULT 1
);

CREATE TABLE role (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) UNIQUE
);

CREATE TABLE user_role (
    user_id BIGINT,
    role_id BIGINT,
    PRIMARY KEY (user_id, role_id),
    FOREIGN KEY (user_id) REFERENCES user(id),
    FOREIGN KEY (role_id) REFERENCES role(id)
);

分层架构实现

实体类(Lombok 减少样板代码)

@Data
public class User {
    private Long id;
    private String username;
    private String email;
    private List<Role> roles;
}

Mapper 接口 + XML 映射

@Mapper
public interface UserMapper {
    int insert(User user);
    User findByIdWithRoles(Long id);
    List<User> findAll();
}

Service 层封装业务逻辑

@Service
@Transactional
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public User createUser(User user) {
        user.setPassword(BCrypt.hashpw(user.getPassword(), BCrypt.gensalt()));
        userMapper.insert(user);
        userRoleService.bindDefaultRole(user.getId());
        return user;
    }
}

Controller 层暴露 REST API

@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserService userService;

    @PostMapping
    public ResponseEntity<?> createUser(@RequestBody @Valid UserCreateRequest request) {
        User user = User.builder()
                .username(request.getUsername())
                .password(request.getPassword())
                .email(request.getEmail())
                .status(1)
                .build();
        User saved = userService.createUser(user);
        return ResponseEntity.ok(Result.success(saved));
    }
}

全局异常处理

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Result> handleValidationException(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors()
          .forEach(e -> errors.put(e.getField(), e.getDefaultMessage()));
        return ResponseEntity.badRequest().body(Result.error("参数校验失败", errors));
    }
}

部署与运行机制解析 🚦

打包运行:

mvn clean package
java -jar target/app.jar --spring.profiles.active=prod

请求流程如下:

sequenceDiagram
    participant Client
    participant Controller
    participant Service
    participant Mapper
    participant DB

    Client->>Controller: HTTP POST /api/users
    Controller->>Service: userService.createUser(user)
    Service->>Mapper: userMapper.insert(user)
    Mapper->>DB: INSERT INTO user(...)
    DB-->>Mapper: 返回影响行数
    Mapper-->>Service: 插入结果
    Service->>Service: userRoleService.bindRole()
    Service-->>Controller: 返回User对象
    Controller-->>Client: 200 OK + JSON数据

整个链路清晰明了,每一层各司其职。


SpringBoot + MyBatis 的组合之所以强大,不仅仅是因为它们简化了开发,更是因为它们的设计思想深刻影响了现代 Java 应用的构建方式。

从“约定优于配置”到“条件化装配”,从“起步依赖”到“动态代理”,每一个细节都体现了对开发者体验的极致追求。

掌握了这些底层原理,你就不再只是“会用”,而是真正“懂得”。未来的架构演进、性能调优、故障排查,都将变得更加从容自信 💪

所以,下次当你按下 SpringApplication.run() 的那一刻,不妨想一想:背后有多少智慧正在默默运转?🤖✨

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:这是一个基于SpringBoot、Mybatis和MySQL的完整学习型Web开发项目,专为Java初学者设计,涵盖从环境搭建到功能实现的全流程实践。项目集成源代码、数据库文件及说明文档,并提供创建教程,帮助开发者快速掌握三大核心技术的整合应用。通过使用IntelliJ IDEA开发工具,学习者可深入理解SpringBoot的自动配置机制、Mybatis的SQL映射与数据持久化操作,以及MySQL数据库的设计与管理,全面掌握Web应用的前后端交互、事务控制和CRUD实现,是提升Java全栈开发能力的理想练习项目。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐