Spring Boot - 数据访问 (MyBatis)
Spring Boot 核心 —— 数据访问 (MyBatis)
1. 核心理论:什么是 MyBatis?
MyBatis 是一个优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码和手动设置参数以及获取结果集的工作。
- 核心思想: 将 SQL 语句从 Java 代码中解耦出来,写在单独的 XML 文件或注解中。开发者只需要关注 SQL 本身,而无需关心连接、Statement、ResultSet 等繁琐的 JDBC 操作。
- 与 Hibernate/JPA 的区别:
- JPA (Java Persistence API): 是一个 ORM (Object Relational Mapping) 规范,它的实现(如 Hibernate)会自动生成 SQL,将 Java 对象完整地映射到数据库表。开发者通常不需要手写 SQL。
- MyBatis: 是一个“半自动”的 ORM 框架。它不完全屏蔽 SQL,而是让开发者自己编写和优化 SQL,然后由框架负责将 SQL 的输入参数和输出结果映射到 Java 对象上。
- 生活比喻:
- JPA/Hibernate: 就像一个全自动翻译机。你给它一句中文(操作 Java 对象),它自动帮你翻译成英文(生成 SQL)。很方便,但有时翻译得可能不那么地道或高效。
- MyBatis: 就像一个高级词典和语法助手。你需要自己写出英文句子(手写 SQL),但它可以帮你快速查单词(参数映射)、检查语法(结果集映射),并帮你把句子工整地写在纸上(执行 SQL)。你对最终的句子有完全的控制权。
2. Spring Boot 集成 MyBatis
Spring Boot 集成 MyBatis 非常简单,主要得益于 mybatis-spring-boot-starter。
2.1 引入依赖
在 pom.xml 中,我们需要两个核心的 Starter:
mybatis-spring-boot-starter: 提供 MyBatis 的自动配置。- 数据库驱动: 例如
mysql-connector-java。
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
2.2 添加配置
在 application.yml 中,配置数据源和 MyBatis 的相关信息。
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydatabase?useSSL=false
username: root
password: yourpassword
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
# 指定 Mapper XML 文件的位置
mapper-locations: classpath:mapper/*.xml
# 为实体类指定别名,方便在 XML 中使用
type-aliases-package: com.study.springboot.data.entity
2.3 自动配置原理
- 引入
mybatis-spring-boot-starter后,MybatisAutoConfiguration会生效。 - 它会检测到
spring.datasource的配置,并自动为你创建一个DataSourceBean。 - 然后,它会自动创建
SqlSessionFactory和SqlSessionTemplate这两个 MyBatis 的核心 Bean。 - 最后,它会扫描所有被
@Mapper注解标记的接口,并为它们创建代理实现类,注入到 Spring 容器中。
3. 深度剖析:MyBatis 核心知识点
3.1 #{} vs ${} 的区别
这是 MyBatis 面试中的必考题。
-
#{}(推荐):- 本质: 预编译处理,参数占位符。
- 工作方式: MyBatis 在处理
#{}时,会将其替换为?占位符,并调用PreparedStatement的set方法来安全地设置参数。 - 优点: 可以有效防止 SQL 注入,非常安全。
-
${}(慎用):- 本质: 字符串直接替换。
- 工作方式: MyBatis 在处理
${}时,就是简单的字符串拼接,直接将变量的值插入到 SQL 语句中。 - 缺点: 存在严重的安全风险,极易导致 SQL 注入。例如,如果
id的值是1 OR 1=1,SQL 就会被恶意篡改。 - 适用场景: 仅在少数需要动态拼接 SQL 关键字的场景下使用,例如动态指定表名 (
SELECT * FROM ${tableName}) 或动态排序字段 (ORDER BY ${columnName}) 。但即便是这些场景,也必须对传入的参数做严格的校验和过滤。
3.2 一级缓存 vs 二级缓存
MyBatis 内置了两级缓存来提高查询性能。
-
一级缓存 (SqlSession 级别)
- 作用域: 缓存只在当前
SqlSession的生命周期内有效。不同的SqlSession之间不共享。 - 状态: 默认开启,且无法关闭。
- 工作原理: 在同一个
SqlSession中,当执行完全相同的 SQL 查询时(相同的语句、相同的参数),第一次查询后,结果会被存入SqlSession内部的一个Map中。后续的查询会直接从这个Map中获取数据,而不会再次访问数据库。 - 失效时机: 当
SqlSession关闭、提交事务、或执行了任何 CUD (Create, Update, Delete) 操作时,该SqlSession的一级缓存就会被清空。
- 作用域: 缓存只在当前
-
二级缓存 (Mapper 命名空间级别)
- 作用域: 缓存可以被多个
SqlSession共享,它的作用范围是整个 Mapper 的命名空间。 - 状态: 默认关闭,需要手动开启。开启步骤:
- 在
application.yml中设置mybatis.configuration.cache-enabled: true。 - 在 Mapper XML 文件中添加
<cache/>标签。
- 在
- 工作原理: 当一个
SqlSession执行查询并关闭或提交后,其查询结果会存入二级缓存。之后,任何其他SqlSession来执行相同的查询时,都可以从二级缓存中获取数据。 - 失效时机: 当执行了与该命名空间相关的任何 CUD 操作时,二级缓存就会被清空,以保证数据的一致性。
- 作用域: 缓存可以被多个
4. 代码示例:实现一个简单的用户查询
3.1 创建实体类 (User.java)
package com.study.springboot.data.entity;
public class User {
private Long id;
private String name;
private Integer age;
// Getters and Setters
}
3.2 创建 Mapper 接口 (UserMapper.java)
Mapper 接口是 MyBatis 的核心,它定义了与数据库交互的方法。
package com.study.springboot.data.mapper;
import com.study.springboot.data.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper // 告诉 Spring Boot 这是一个 Mapper 接口
public interface UserMapper {
// 方法一:使用注解编写 SQL (适用于简单 SQL)
@Select("SELECT * FROM user WHERE id = #{id}")
User findById(Long id);
// 方法二:使用 XML 编写 SQL (适用于复杂 SQL)
int insertUser(User user);
}
3.3 创建 Mapper XML 文件 (UserMapper.xml)
在 src/main/resources/mapper/ 目录下创建 UserMapper.xml。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.study.springboot.data.mapper.UserMapper"> <!-- 命名空间必须是 Mapper 接口的全限定名 -->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (name, age) VALUES (#{name}, #{age})
</insert>
</mapper>
3.4 创建 Service 和 Controller
// UserService.java
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User getUserById(Long id) {
return userMapper.findById(id);
}
}
// UserController.java
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUserById(id);
}
}
3.5 在主启动类上开启 Mapper 扫描
虽然 @Mapper 注解可以被自动扫描,但更推荐的方式是在主启动类上使用 @MapperScan 来指定一个统一的扫描包路径。
@SpringBootApplication
@MapperScan("com.study.springboot.data.mapper") // 推荐使用
public class DataAccessApplication {
public static void main(String[] args) {
SpringApplication.run(DataAccessApplication.class, args);
}
}
- 效果: 当你访问
/user/1时,UserController会调用UserService,UserService再调用UserMapper的findById方法。MyBatis 会找到对应的@Select注解或 XML 中的 SQL,执行查询,并将结果自动映射为User对象返回。

浙公网安备 33010602011771号