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:

  1. mybatis-spring-boot-starter: 提供 MyBatis 的自动配置。
  2. 数据库驱动: 例如 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 自动配置原理

  1. 引入 mybatis-spring-boot-starter 后,MybatisAutoConfiguration 会生效。
  2. 它会检测到 spring.datasource 的配置,并自动为你创建一个 DataSource Bean。
  3. 然后,它会自动创建 SqlSessionFactorySqlSessionTemplate 这两个 MyBatis 的核心 Bean。
  4. 最后,它会扫描所有被 @Mapper 注解标记的接口,并为它们创建代理实现类,注入到 Spring 容器中。

3. 深度剖析:MyBatis 核心知识点

3.1 #{} vs ${} 的区别

这是 MyBatis 面试中的必考题。

  • #{} (推荐):

    • 本质: 预编译处理,参数占位符。
    • 工作方式: MyBatis 在处理 #{} 时,会将其替换为 ? 占位符,并调用 PreparedStatementset 方法来安全地设置参数。
    • 优点: 可以有效防止 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 的命名空间。
    • 状态: 默认关闭,需要手动开启。开启步骤:
      1. application.yml 中设置 mybatis.configuration.cache-enabled: true
      2. 在 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 会调用 UserServiceUserService 再调用 UserMapperfindById 方法。MyBatis 会找到对应的 @Select 注解或 XML 中的 SQL,执行查询,并将结果自动映射为 User 对象返回。
posted @ 2026-01-21 16:22  我是刘瘦瘦  阅读(2)  评论(0)    收藏  举报