SpringBoot+Mybatis+MySQL整合实战项目源码+数据库脚本+详细文档
简介:这是一个基于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—— 生产环境
激活方式
-
配置文件中指定:
yaml spring: profiles: active: dev -
命令行激活:
bash java -jar app.jar --spring.profiles.active=prod -
环境变量:
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 环境下,这一切都被自动化了:
mybatis-spring-boot-starter自动检测是否存在DataSource- 若存在,则创建
SqlSessionFactory并注册为 Bean - 通过
@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 会:
- 检查缓存是否有该接口的代理工厂;
- 若无,则创建
MapperProxyFactory; - 返回一个实现了
InvocationHandler的MapperProxy; - 每次方法调用都会被
invoke()拦截; - 提取方法签名,定位到对应的
MappedStatement; - 执行 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() { ... }
}
解决办法:
- 提取到另一个 Service
- 使用 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() 的那一刻,不妨想一想:背后有多少智慧正在默默运转?🤖✨
简介:这是一个基于SpringBoot、Mybatis和MySQL的完整学习型Web开发项目,专为Java初学者设计,涵盖从环境搭建到功能实现的全流程实践。项目集成源代码、数据库文件及说明文档,并提供创建教程,帮助开发者快速掌握三大核心技术的整合应用。通过使用IntelliJ IDEA开发工具,学习者可深入理解SpringBoot的自动配置机制、Mybatis的SQL映射与数据持久化操作,以及MySQL数据库的设计与管理,全面掌握Web应用的前后端交互、事务控制和CRUD实现,是提升Java全栈开发能力的理想练习项目。
更多推荐



所有评论(0)