轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • JavaScript
  • TypeScript
  • Node.js
  • Vue.js
  • 前端工程化
  • 浏览器与Web API
  • 架构设计与模式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • JavaScript
  • TypeScript
  • Node.js
  • Vue.js
  • 前端工程化
  • 浏览器与Web API
  • 架构设计与模式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java

    • 核心

    • 并发

    • 经验

    • JVM

    • 企业应用

      • Freemarker模板实战指南
      • Servlet与JSP指南
      • Java日志系统
      • Java JSON处理
      • Java XML处理
      • Java对象池技术
      • Java程序的单元测试(Junit)
      • Thymeleaf官方文档(中文版)
      • Mockito应用
      • Java中的空安全
      • MyBatis核心指南
        • 一、概述
          • 1. 什么是 MyBatis
          • 2. MyBatis 的优势
          • 3. MyBatis vs Hibernate vs JPA
        • 二、快速入门
          • 1. 添加依赖
          • Maven 配置
          • Gradle 配置
          • 2. 核心配置文件
          • 3. 创建实体类
          • 4. 创建 Mapper 接口
          • 5. 创建 Mapper XML 文件
          • 6. 使用 MyBatis
        • 三、核心配置详解
          • 1. configuration 配置结构
          • 2. properties 属性配置
          • 3. settings 全局设置
          • 4. typeAliases 类型别名
          • 5. environments 环境配置
          • 6. mappers 映射器配置
        • 四、XML 映射文件详解
          • 1. 参数传递
          • 单个参数
          • 多个参数
          • 2. #{} 与 ${} 的区别
          • 3. resultMap 结果映射
          • 基本映射
          • 关联映射(一对一)
          • 集合映射(一对多)
          • 4. 自动映射
        • 五、动态 SQL
          • 1. if 条件判断
          • 2. choose/when/otherwise
          • 3. where 标签
          • 4. set 标签
          • 5. trim 标签
          • 6. foreach 遍历
          • 7. sql 片段复用
        • 六、注解开发
          • 1. 基本 CRUD 注解
          • 2. @Results 结果映射
          • 3. @Provider 动态 SQL
          • 4. 注解与 XML 混合使用
        • 七、缓存机制
          • 1. 一级缓存(本地缓存)
          • 2. 二级缓存(全局缓存)
          • 3. 自定义缓存
        • 八、Spring Boot 集成
          • 1. 添加依赖
          • 2. 配置文件
          • 3. 启动类配置
          • 4. 使用示例
        • 九、MyBatis-Plus 简介
          • 1. 核心特性
          • 2. 快速开始
        • 十、最佳实践
          • 1. SQL 优化建议
          • 2. 防止 SQL 注入
          • 3. 日志配置
          • 4. 事务管理
          • 5. 常见问题排查
        • 十一、总结
  • Spring

  • 其他语言

  • 工具

  • 后端
  • Java
  • 企业应用
轩辕李
2024-11-06
目录

MyBatis核心指南

# 一、概述

# 1. 什么是 MyBatis

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

与全自动 ORM 框架(如 Hibernate)不同,MyBatis 是一个半自动化的 ORM 框架,它将 SQL 语句的编写权交给开发者,提供了更大的灵活性和控制力。

# 2. MyBatis 的优势

  • 简单易学:本身就很小且简单,没有任何第三方依赖,最简单安装只要两个 jar 文件+配置几个 SQL 映射文件
  • 灵活性高:不会对应用程序或者数据库的现有设计强加任何影响,SQL 写在 XML 里,便于统一管理和优化
  • SQL 可控:提供 XML 标签,支持编写动态 SQL 语句
  • 解耦合:SQL 和代码的分离,提高了可维护性
  • 提供映射标签:支持对象与数据库的 ORM 字段关系映射
  • 提供对象关系映射标签:支持对象关系组建维护
  • 提供 XML 标签:支持编写动态 SQL

# 3. MyBatis vs Hibernate vs JPA

特性 MyBatis Hibernate JPA
类型 半自动 ORM 全自动 ORM 规范/标准
SQL 控制 完全控制 自动生成 自动生成
学习曲线 较低 较高 中等
性能优化 容易 较难 中等
移植性 较差 好 好
适用场景 复杂查询、性能要求高 快速开发、简单 CRUD 标准化项目

# 二、快速入门

# 1. 添加依赖

# Maven 配置

<dependencies>
    <!-- MyBatis 核心依赖 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.16</version>
    </dependency>
    
    <!-- MySQL 驱动 -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <version>8.3.0</version>
    </dependency>
</dependencies>

# Gradle 配置

dependencies {
    implementation 'org.mybatis:mybatis:3.5.16'
    implementation 'com.mysql:mysql-connector-j:8.3.0'
}

# 2. 核心配置文件

创建 mybatis-config.xml 配置文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 环境配置 -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&amp;serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="password"/>
            </dataSource>
        </environment>
    </environments>
    
    <!-- 映射器配置 -->
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>
</configuration>

# 3. 创建实体类

public class User {
    private Long id;
    private String username;
    private String email;
    private Integer age;
    private LocalDateTime createTime;
    
    // 构造方法
    public User() {}
    
    public User(String username, String email, Integer age) {
        this.username = username;
        this.email = email;
        this.age = age;
    }
    
    // Getter 和 Setter 方法
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    
    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }
    
    public LocalDateTime getCreateTime() { return createTime; }
    public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }
    
    @Override
    public String toString() {
        return "User{id=" + id + ", username='" + username + "', email='" + email + 
               "', age=" + age + ", createTime=" + createTime + "}";
    }
}

# 4. 创建 Mapper 接口

public interface UserMapper {
    
    // 根据 ID 查询用户
    User selectById(Long id);
    
    // 查询所有用户
    List<User> selectAll();
    
    // 插入用户
    int insert(User user);
    
    // 更新用户
    int update(User user);
    
    // 删除用户
    int deleteById(Long id);
}

# 5. 创建 Mapper XML 文件

创建 mapper/UserMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
    
    <!-- 结果映射 -->
    <resultMap id="UserResultMap" type="com.example.entity.User">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="email" column="email"/>
        <result property="age" column="age"/>
        <result property="createTime" column="create_time"/>
    </resultMap>
    
    <!-- 根据 ID 查询 -->
    <select id="selectById" resultMap="UserResultMap">
        SELECT id, username, email, age, create_time
        FROM user
        WHERE id = #{id}
    </select>
    
    <!-- 查询所有 -->
    <select id="selectAll" resultMap="UserResultMap">
        SELECT id, username, email, age, create_time
        FROM user
    </select>
    
    <!-- 插入 -->
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO user (username, email, age, create_time)
        VALUES (#{username}, #{email}, #{age}, #{createTime})
    </insert>
    
    <!-- 更新 -->
    <update id="update">
        UPDATE user
        SET username = #{username}, email = #{email}, age = #{age}
        WHERE id = #{id}
    </update>
    
    <!-- 删除 -->
    <delete id="deleteById">
        DELETE FROM user WHERE id = #{id}
    </delete>
    
</mapper>

# 6. 使用 MyBatis

public class MyBatisDemo {
    public static void main(String[] args) throws IOException {
        // 1. 读取配置文件
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        
        // 2. 创建 SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        
        // 3. 获取 SqlSession(使用 try-with-resources 自动关闭)
        try (SqlSession session = sqlSessionFactory.openSession()) {
            // 4. 获取 Mapper 接口的代理对象
            UserMapper mapper = session.getMapper(UserMapper.class);
            
            // 5. 执行查询
            User user = mapper.selectById(1L);
            System.out.println(user);
            
            // 6. 执行插入
            User newUser = new User("张三", "zhangsan@example.com", 25);
            mapper.insert(newUser);
            
            // 7. 提交事务
            session.commit();
        }
    }
}

# 三、核心配置详解

# 1. configuration 配置结构

MyBatis 的配置文件包含以下顶级元素(按顺序):

<configuration>
    <properties/>      <!-- 属性配置 -->
    <settings/>        <!-- 全局设置 -->
    <typeAliases/>     <!-- 类型别名 -->
    <typeHandlers/>    <!-- 类型处理器 -->
    <objectFactory/>   <!-- 对象工厂 -->
    <plugins/>         <!-- 插件 -->
    <environments/>    <!-- 环境配置 -->
    <databaseIdProvider/> <!-- 数据库厂商标识 -->
    <mappers/>         <!-- 映射器 -->
</configuration>

# 2. properties 属性配置

可以通过外部属性文件或内部属性来配置:

<!-- 引入外部属性文件 -->
<properties resource="db.properties">
    <!-- 也可以在这里定义属性 -->
    <property name="username" value="root"/>
</properties>

<!-- 使用属性 -->
<dataSource type="POOLED">
    <property name="driver" value="${driver}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>
</dataSource>

db.properties 文件:

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis_demo
username=root
password=password

# 3. settings 全局设置

常用的设置项:

<settings>
    <!-- 开启驼峰命名自动映射 -->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    
    <!-- 开启延迟加载 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    
    <!-- 开启二级缓存 -->
    <setting name="cacheEnabled" value="true"/>
    
    <!-- 指定日志实现 -->
    <setting name="logImpl" value="SLF4J"/>
    
    <!-- 允许 JDBC 支持自动生成主键 -->
    <setting name="useGeneratedKeys" value="true"/>
    
    <!-- 设置超时时间 -->
    <setting name="defaultStatementTimeout" value="25"/>
    
    <!-- 设置默认的执行器类型 -->
    <setting name="defaultExecutorType" value="SIMPLE"/>
</settings>

# 4. typeAliases 类型别名

类型别名可以为 Java 类型设置一个缩写名字,减少类完全限定名的冗余:

<typeAliases>
    <!-- 单个类型别名 -->
    <typeAlias alias="User" type="com.example.entity.User"/>

    <!-- 包扫描方式(推荐) -->
    <package name="com.example.entity"/>
</typeAliases>

使用 @Alias 注解自定义别名:

@Alias("user")
public class User {
    // ...
}

MyBatis 内置的常用类型别名:

别名 映射的类型
_int int
_long long
int Integer
long Long
string String
date Date
map Map
list List

# 5. environments 环境配置

MyBatis 支持配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境:

<environments default="development">
    <!-- 开发环境 -->
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${dev.driver}"/>
            <property name="url" value="${dev.url}"/>
            <property name="username" value="${dev.username}"/>
            <property name="password" value="${dev.password}"/>
        </dataSource>
    </environment>

    <!-- 生产环境 -->
    <environment id="production">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${prod.driver}"/>
            <property name="url" value="${prod.url}"/>
            <property name="username" value="${prod.username}"/>
            <property name="password" value="${prod.password}"/>
        </dataSource>
    </environment>
</environments>

事务管理器类型:

  • JDBC:直接使用 JDBC 的提交和回滚功能
  • MANAGED:让容器来管理事务的整个生命周期

数据源类型:

  • UNPOOLED:每次请求时打开和关闭连接
  • POOLED:使用连接池(推荐)
  • JNDI:使用 JNDI 数据源

# 6. mappers 映射器配置

四种配置方式:

<mappers>
    <!-- 方式一:使用相对于类路径的资源引用 -->
    <mapper resource="mapper/UserMapper.xml"/>

    <!-- 方式二:使用完全限定资源定位符(URL) -->
    <mapper url="file:///var/mappers/UserMapper.xml"/>

    <!-- 方式三:使用映射器接口实现类的完全限定类名 -->
    <mapper class="com.example.mapper.UserMapper"/>

    <!-- 方式四:将包内的映射器接口全部注册(推荐) -->
    <package name="com.example.mapper"/>
</mappers>

# 四、XML 映射文件详解

# 1. 参数传递

# 单个参数

<select id="selectById" resultType="User">
    SELECT * FROM user WHERE id = #{id}
</select>

# 多个参数

方式一:使用 @Param 注解

User selectByUsernameAndAge(@Param("username") String username, @Param("age") Integer age);
<select id="selectByUsernameAndAge" resultType="User">
    SELECT * FROM user WHERE username = #{username} AND age = #{age}
</select>

方式二:使用 Map

User selectByMap(Map<String, Object> params);
<select id="selectByMap" resultType="User">
    SELECT * FROM user WHERE username = #{username} AND age = #{age}
</select>

方式三:使用实体对象

List<User> selectByCondition(User user);
<select id="selectByCondition" resultType="User">
    SELECT * FROM user WHERE username = #{username} AND age = #{age}
</select>

# 2. #{} 与 ${} 的区别

特性 #{} ${}
处理方式 预编译处理 字符串替换
SQL 注入 安全,防止 SQL 注入 不安全,存在 SQL 注入风险
使用场景 参数值 表名、列名等动态 SQL 片段
<!-- 使用 #{} 传递参数值(推荐) -->
<select id="selectById" resultType="User">
    SELECT * FROM user WHERE id = #{id}
</select>

<!-- 使用 ${} 动态表名(谨慎使用) -->
<select id="selectFromTable" resultType="User">
    SELECT * FROM ${tableName} WHERE id = #{id}
</select>

# 3. resultMap 结果映射

# 基本映射

<resultMap id="UserResultMap" type="User">
    <id property="id" column="id"/>
    <result property="username" column="user_name"/>
    <result property="email" column="email"/>
    <result property="createTime" column="create_time"/>
</resultMap>

# 关联映射(一对一)

public class User {
    private Long id;
    private String username;
    private UserDetail detail;  // 一对一关联
}
<resultMap id="UserWithDetailMap" type="User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <!-- 一对一关联 -->
    <association property="detail" javaType="UserDetail">
        <id property="id" column="detail_id"/>
        <result property="address" column="address"/>
        <result property="phone" column="phone"/>
    </association>
</resultMap>

<select id="selectUserWithDetail" resultMap="UserWithDetailMap">
    SELECT u.id, u.username, d.id as detail_id, d.address, d.phone
    FROM user u
    LEFT JOIN user_detail d ON u.id = d.user_id
    WHERE u.id = #{id}
</select>

# 集合映射(一对多)

public class User {
    private Long id;
    private String username;
    private List<Order> orders;  // 一对多关联
}
<resultMap id="UserWithOrdersMap" type="User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <!-- 一对多关联 -->
    <collection property="orders" ofType="Order">
        <id property="id" column="order_id"/>
        <result property="orderNo" column="order_no"/>
        <result property="amount" column="amount"/>
    </collection>
</resultMap>

<select id="selectUserWithOrders" resultMap="UserWithOrdersMap">
    SELECT u.id, u.username, o.id as order_id, o.order_no, o.amount
    FROM user u
    LEFT JOIN orders o ON u.id = o.user_id
    WHERE u.id = #{id}
</select>

# 4. 自动映射

当开启 mapUnderscoreToCamelCase 设置后,MyBatis 会自动将数据库下划线命名映射到 Java 驼峰命名:

<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

这样 create_time 会自动映射到 createTime。

# 五、动态 SQL

动态 SQL 是 MyBatis 的强大特性之一,它允许根据不同条件动态生成 SQL 语句。

# 1. if 条件判断

<select id="selectByCondition" resultType="User">
    SELECT * FROM user
    WHERE 1=1
    <if test="username != null and username != ''">
        AND username LIKE CONCAT('%', #{username}, '%')
    </if>
    <if test="email != null and email != ''">
        AND email = #{email}
    </if>
    <if test="age != null">
        AND age = #{age}
    </if>
</select>

# 2. choose/when/otherwise

类似于 Java 的 switch 语句:

<select id="selectByPriority" resultType="User">
    SELECT * FROM user
    WHERE 1=1
    <choose>
        <when test="id != null">
            AND id = #{id}
        </when>
        <when test="username != null">
            AND username = #{username}
        </when>
        <otherwise>
            AND status = 'ACTIVE'
        </otherwise>
    </choose>
</select>

# 3. where 标签

where 标签会自动处理 WHERE 关键字和多余的 AND/OR:

<select id="selectByCondition" resultType="User">
    SELECT * FROM user
    <where>
        <if test="username != null and username != ''">
            AND username LIKE CONCAT('%', #{username}, '%')
        </if>
        <if test="email != null">
            AND email = #{email}
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
    </where>
</select>

# 4. set 标签

set 标签用于动态更新语句,会自动处理多余的逗号:

<update id="updateSelective">
    UPDATE user
    <set>
        <if test="username != null">username = #{username},</if>
        <if test="email != null">email = #{email},</if>
        <if test="age != null">age = #{age},</if>
    </set>
    WHERE id = #{id}
</update>

# 5. trim 标签

trim 标签是 where 和 set 的通用版本:

<!-- 等价于 where 标签 -->
<trim prefix="WHERE" prefixOverrides="AND |OR ">
    ...
</trim>

<!-- 等价于 set 标签 -->
<trim prefix="SET" suffixOverrides=",">
    ...
</trim>

# 6. foreach 遍历

用于遍历集合,常用于 IN 查询和批量操作:

<!-- IN 查询 -->
<select id="selectByIds" resultType="User">
    SELECT * FROM user
    WHERE id IN
    <foreach collection="ids" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

<!-- 批量插入 -->
<insert id="batchInsert">
    INSERT INTO user (username, email, age) VALUES
    <foreach collection="users" item="user" separator=",">
        (#{user.username}, #{user.email}, #{user.age})
    </foreach>
</insert>

foreach 属性说明:

  • collection:要遍历的集合(List 用 list,数组用 array,Map 用 map,或使用 @Param 指定的名称)
  • item:当前遍历元素的变量名
  • index:当前遍历的索引
  • open:开始符号
  • close:结束符号
  • separator:分隔符

# 7. sql 片段复用

使用 sql 标签定义可复用的 SQL 片段:

<!-- 定义 SQL 片段 -->
<sql id="userColumns">
    id, username, email, age, create_time
</sql>

<sql id="whereCondition">
    <where>
        <if test="username != null">AND username = #{username}</if>
        <if test="email != null">AND email = #{email}</if>
    </where>
</sql>

<!-- 引用 SQL 片段 -->
<select id="selectAll" resultType="User">
    SELECT <include refid="userColumns"/>
    FROM user
    <include refid="whereCondition"/>
</select>

# 六、注解开发

除了 XML 配置,MyBatis 也支持使用注解进行开发,适合简单的 SQL 操作。

# 1. 基本 CRUD 注解

public interface UserMapper {

    @Select("SELECT * FROM user WHERE id = #{id}")
    User selectById(Long id);

    @Select("SELECT * FROM user")
    List<User> selectAll();

    @Insert("INSERT INTO user(username, email, age) VALUES(#{username}, #{email}, #{age})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(User user);

    @Update("UPDATE user SET username=#{username}, email=#{email} WHERE id=#{id}")
    int update(User user);

    @Delete("DELETE FROM user WHERE id = #{id}")
    int deleteById(Long id);
}

# 2. @Results 结果映射

@Results(id = "userResultMap", value = {
    @Result(property = "id", column = "id", id = true),
    @Result(property = "username", column = "user_name"),
    @Result(property = "email", column = "email"),
    @Result(property = "createTime", column = "create_time")
})
@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(Long id);

// 复用结果映射
@ResultMap("userResultMap")
@Select("SELECT * FROM user")
List<User> selectAll();

# 3. @Provider 动态 SQL

对于复杂的动态 SQL,可以使用 Provider 注解:

public interface UserMapper {

    @SelectProvider(type = UserSqlProvider.class, method = "selectByCondition")
    List<User> selectByCondition(User user);

    @InsertProvider(type = UserSqlProvider.class, method = "insertSelective")
    int insertSelective(User user);

    @UpdateProvider(type = UserSqlProvider.class, method = "updateSelective")
    int updateSelective(User user);
}
public class UserSqlProvider {

    public String selectByCondition(User user) {
        return new SQL() {{
            SELECT("*");
            FROM("user");
            if (user.getUsername() != null) {
                WHERE("username = #{username}");
            }
            if (user.getEmail() != null) {
                WHERE("email = #{email}");
            }
            if (user.getAge() != null) {
                WHERE("age = #{age}");
            }
        }}.toString();
    }

    public String insertSelective(User user) {
        return new SQL() {{
            INSERT_INTO("user");
            if (user.getUsername() != null) {
                VALUES("username", "#{username}");
            }
            if (user.getEmail() != null) {
                VALUES("email", "#{email}");
            }
            if (user.getAge() != null) {
                VALUES("age", "#{age}");
            }
        }}.toString();
    }

    public String updateSelective(User user) {
        return new SQL() {{
            UPDATE("user");
            if (user.getUsername() != null) {
                SET("username = #{username}");
            }
            if (user.getEmail() != null) {
                SET("email = #{email}");
            }
            if (user.getAge() != null) {
                SET("age = #{age}");
            }
            WHERE("id = #{id}");
        }}.toString();
    }
}

# 4. 注解与 XML 混合使用

可以在同一个 Mapper 中混合使用注解和 XML:

public interface UserMapper {
    // 简单查询使用注解
    @Select("SELECT * FROM user WHERE id = #{id}")
    User selectById(Long id);

    // 复杂查询在 XML 中定义
    List<User> selectByComplexCondition(Map<String, Object> params);
}

# 七、缓存机制

# 1. 一级缓存(本地缓存)

一级缓存是 SqlSession 级别的缓存,默认开启,同一个 SqlSession 中执行相同的查询会直接从缓存获取。

try (SqlSession session = sqlSessionFactory.openSession()) {
    UserMapper mapper = session.getMapper(UserMapper.class);

    // 第一次查询,执行 SQL
    User user1 = mapper.selectById(1L);

    // 第二次查询,从一级缓存获取,不执行 SQL
    User user2 = mapper.selectById(1L);

    System.out.println(user1 == user2);  // true
}

一级缓存失效的情况:

  • 不同的 SqlSession
  • 同一个 SqlSession,但查询条件不同
  • 同一个 SqlSession,两次查询之间执行了增删改操作
  • 同一个 SqlSession,手动清空了缓存(session.clearCache())

# 2. 二级缓存(全局缓存)

二级缓存是 namespace 级别的缓存,可以跨 SqlSession 共享。

开启二级缓存:

  1. 在 mybatis-config.xml 中开启全局缓存(默认已开启):
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>
  1. 在 Mapper XML 中添加 <cache/> 标签:
<mapper namespace="com.example.mapper.UserMapper">
    <!-- 开启二级缓存 -->
    <cache
        eviction="LRU"
        flushInterval="60000"
        size="512"
        readOnly="true"/>

    <!-- ... -->
</mapper>

cache 属性说明:

  • eviction:缓存回收策略
    • LRU(默认):最近最少使用
    • FIFO:先进先出
    • SOFT:软引用
    • WEAK:弱引用
  • flushInterval:刷新间隔(毫秒)
  • size:缓存对象数量
  • readOnly:是否只读
  1. 实体类需要实现 Serializable 接口:
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    // ...
}

# 3. 自定义缓存

可以集成第三方缓存(如 Redis、Ehcache):

<cache type="org.mybatis.caches.redis.RedisCache"/>

注:企业开发中一般不在ORM层做缓存,而是在业务层做缓存。参考:高性能-缓存架构设计

# 八、Spring Boot 集成

# 1. 添加依赖

<dependencies>
    <!-- MyBatis Spring Boot Starter -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>3.0.3</version>
    </dependency>

    <!-- MySQL 驱动 -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

# 2. 配置文件

application.yml:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=UTC
    username: root
    password: password
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  # Mapper XML 文件位置
  mapper-locations: classpath:mapper/*.xml
  # 实体类包路径(用于类型别名)
  type-aliases-package: com.example.entity
  configuration:
    # 开启驼峰命名映射
    map-underscore-to-camel-case: true
    # 开启二级缓存
    cache-enabled: true
    # 日志实现
    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl

# 3. 启动类配置

@SpringBootApplication
@MapperScan("com.example.mapper")  // 扫描 Mapper 接口
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

# 4. 使用示例

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public User getUserById(Long id) {
        return userMapper.selectById(id);
    }

    @Transactional
    public void createUser(User user) {
        userMapper.insert(user);
    }
}

# 九、MyBatis-Plus 简介

MyBatis-Plus(简称 MP)是 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

# 1. 核心特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响
  • 损耗小:启动即会自动注入基本 CRUD,性能基本无损耗
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件
  • 支持主键自动生成:支持多达 4 种主键策略
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper、Model、Service、Controller 层代码
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作
  • 内置性能分析插件:可输出 SQL 语句以及其执行时间

# 2. 快速开始

添加依赖:

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
    <version>3.5.9</version>
</dependency>

实体类:

@Data
@TableName("user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String username;
    private String email;
    private Integer age;
    @TableField("create_time")
    private LocalDateTime createTime;
}

Mapper 接口:

@Mapper
public interface UserMapper extends BaseMapper<User> {
    // 继承 BaseMapper 后,自动拥有基本的 CRUD 方法
}

使用示例:

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    // 基本 CRUD
    public User getById(Long id) {
        return userMapper.selectById(id);
    }

    public List<User> getAll() {
        return userMapper.selectList(null);
    }

    public void save(User user) {
        userMapper.insert(user);
    }

    // 条件构造器
    public List<User> getByCondition(String username, Integer minAge) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(StringUtils.isNotBlank(username), User::getUsername, username)
               .ge(minAge != null, User::getAge, minAge)
               .orderByDesc(User::getCreateTime);
        return userMapper.selectList(wrapper);
    }

    // 分页查询
    public IPage<User> getPage(int pageNum, int pageSize) {
        Page<User> page = new Page<>(pageNum, pageSize);
        return userMapper.selectPage(page, null);
    }
}

# 十、最佳实践

# 1. SQL 优化建议

  • **避免 SELECT ***:明确指定需要的列,减少数据传输
  • 合理使用索引:在 WHERE、ORDER BY、JOIN 条件中的列上建立索引
  • 分页查询:大数据量查询时使用分页,避免一次性加载过多数据
  • 批量操作:使用批量插入/更新代替循环单条操作
<!-- 批量插入示例 -->
<insert id="batchInsert">
    INSERT INTO user (username, email, age) VALUES
    <foreach collection="list" item="user" separator=",">
        (#{user.username}, #{user.email}, #{user.age})
    </foreach>
</insert>

# 2. 防止 SQL 注入

  • 优先使用 #{} 而不是 ${}
  • 对于必须使用 ${} 的场景(如动态表名),进行白名单校验
// 白名单校验示例
public List<User> selectFromTable(String tableName) {
    Set<String> allowedTables = Set.of("user", "admin", "guest");
    if (!allowedTables.contains(tableName)) {
        throw new IllegalArgumentException("Invalid table name");
    }
    return userMapper.selectFromTable(tableName);
}

# 3. 日志配置

开发环境建议开启 SQL 日志,便于调试:

# application.yml
logging:
  level:
    com.example.mapper: debug

mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

# 4. 事务管理

在 Spring 环境中使用 @Transactional 注解管理事务:

@Service
public class UserService {

    @Transactional(rollbackFor = Exception.class)
    public void transferBalance(Long fromId, Long toId, BigDecimal amount) {
        // 扣减余额
        userMapper.decreaseBalance(fromId, amount);
        // 增加余额
        userMapper.increaseBalance(toId, amount);
    }
}

# 5. 常见问题排查

问题 可能原因 解决方案
Mapper 接口找不到 未配置 @MapperScan 或包路径错误 检查启动类的 @MapperScan 配置
参数绑定失败 多参数未使用 @Param 添加 @Param 注解
结果映射为 null 列名与属性名不匹配 使用 resultMap 或开启驼峰映射
缓存不生效 未开启二级缓存或实体未序列化 检查缓存配置和 Serializable 接口

# 十一、总结

MyBatis 作为一款优秀的持久层框架,以其灵活性和对 SQL 的完全控制而广受欢迎。本文介绍了 MyBatis 的核心概念和使用方法:

  1. 基础配置:了解 MyBatis 的核心配置文件结构和各项配置的作用
  2. CRUD 操作:掌握基本的增删改查操作和参数传递方式
  3. 结果映射:学会使用 resultMap 处理复杂的对象关系映射
  4. 动态 SQL:灵活运用 if、choose、where、foreach 等标签构建动态查询
  5. 注解开发:了解注解方式的开发模式,适用于简单场景
  6. 缓存机制:理解一级缓存和二级缓存的工作原理
  7. Spring Boot 集成:掌握在 Spring Boot 项目中使用 MyBatis 的方法
  8. MyBatis-Plus:了解增强工具的使用,提高开发效率

在实际项目中,建议根据业务复杂度选择合适的使用方式:简单 CRUD 可以使用注解或 MyBatis-Plus,复杂查询则使用 XML 配置。同时注意 SQL 优化和安全性,编写高质量的持久层代码。

祝你变得更强!

编辑 (opens new window)
#MyBatis#ORM#持久层
上次更新: 2025/12/06
Java中的空安全
Spring体系介绍

← Java中的空安全 Spring体系介绍→

最近更新
01
AI编程时代的一些心得
09-11
02
Claude Code与Codex的协同工作
09-01
03
Claude Code 最佳实践(个人版)
08-01
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式