Skip to content

框架-Mybatis

简介

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数及获取结果集的工作。

MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通的 Java 对象)为数据库中的记录。

MyBatis 的特点

  • 简单易学:MyBatis 入门相对简单,学习成本低。
  • SQL 可控:直接编写原生 SQL,SQL 语句灵活,可控性强。
  • 提供映射标签:支持对象与数据库之间的 ORM 字段关系映射。
  • 动态 SQL:强大的动态 SQL 能够满足各种需求。
  • 接口绑定:支持将接口和 XML 映射文件绑定。

在SpringBoot中集成

环境搭建与基础配置

  1. Maven依赖配置
xml
<!-- Spring Boot Starter -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- MyBatis整合 -->
<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>
  1. application.yml核心配置
yaml
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
    username: root
    password: 123456

mybatis:
  mapper-locations: classpath:mapper/*.xml  # XML映射文件路径
  type-aliases-package: com.example.entity  # 实体类别名
  configuration:
    map-underscore-to-camel-case: true  # 自动驼峰转换

基本使用

接口和xml文件定义

CRUD操作详解

  1. Mapper接口定义
java
@Mapper
public interface UserMapper {
    @Select("SELECT * FROM users WHERE id = #{id}")
    User getById(@Param("id") Long id);

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

    @Update("UPDATE users SET name=#{name} WHERE id=#{id}")
    int updateName(@Param("id") Long id, @Param("name") String name);

    @Delete("DELETE FROM users WHERE id = #{id}")
    int deleteById(Long id);
}
  1. XML映射文件示例(resources/mapper/UserMapper.xml)
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.example.mapper.UserMapper">
    <resultMap id="userMap" type="User">
        <id column="id" property="id"/>
        <result column="create_time" property="createTime"/>
    </resultMap>

    <select id="selectByCondition" resultMap="userMap">
        SELECT * FROM users
        WHERE status = #{status}
        <if test="name != null">
            AND name LIKE CONCAT('%',#{name},'%')
        </if>
    </select>
</mapper>

动态 SQL 标签

标签

MyBatis 提供了强大的动态 SQL 功能,借助类似于 JSTL 或者其他类似的 XML 基础的文本处理机制。

if 标签
<select id="findByCondition" parameterType="User" resultType="User">
    SELECT * FROM user
    WHERE 1=1
    <if test="username != null and username != ''">
        AND username = #{username}
    </if>
    <if test="email != null and email != ''">
        AND email = #{email}
    </if>
</select>
choose、when、otherwise 标签
<select id="findByIdOrUsername" parameterType="User" resultType="User">
    SELECT * FROM user
    WHERE
    <choose>
        <when test="id != null">
            id = #{id}
        </when>
        <when test="username != null and username != ''">
            username = #{username}
        </when>
        <otherwise>
            1=2 <!-- 无效的条件,保证查询不到数据 -->
        </otherwise>
    </choose>
</select>
foreach 标签
<select id="findByIds" parameterType="list" resultType="User">
    SELECT * FROM user
    WHERE id IN
    <foreach item="id" index="index" collection="list"
             open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

动态SQL使用

动态SQL实战

  1. 动态条件查询
xml
<select id="searchUsers" resultType="User">
    SELECT * FROM users
    <where>
        <if test="name != null">
            AND name LIKE #{name}
        </if>
        <if test="minAge != null">
            AND age >= #{minAge}
        </if>
        <choose>
            <when test="status != null">
                AND status = #{status}
            </when>
            <otherwise>
                AND status = 1
            </otherwise>
        </choose>
    </where>
    ORDER BY create_time DESC
</select>
  1. 批量插入示例
xml
<insert id="batchInsert">
    INSERT INTO users (name, email)
    VALUES
    <foreach collection="list" item="user" separator=",">
        (#{user.name}, #{user.email})
    </foreach>
</insert>

高级查询

高级查询与优化

一对一映射
    • 在我们平时进行SQL查询时,往往会有一对一的情况,比如说我们这里有资源分类ums_resource_category和资源ums_resource两张表,资源和分类就是一对一的关系,如果你不想改动原实体类的话,可以编写一个扩展类继承UmsResource,并包含UmsResourceCategory属性;
/**
 * @auther macrozheng
 * @description UmsResource扩展类
 * @date 2022/10/20
 * @github https://github.com/macrozheng
 */
@Data
public class UmsResourceExt extends UmsResource {

    private UmsResourceCategory category;
}
  • 例如我们需要编写一个根据资源ID获取资源及分类信息的方法;
/**
 * @auther macrozheng
 * @description 自定义UmsResource表查询
 * @date 2022/10/20
 * @github https://github.com/macrozheng
 */
@Repository
public interface UmsResourceDao {
    /**
     * 根据资源ID获取资源及分类信息
     */
    UmsResourceExt selectResourceWithCategory(Long id);
}
  • 在xml中的具体SQL实现如下,我们可以通过给ums_resource_category表中字段取以category.xxx的别名来自动进行自动映射;
<select id="selectResourceWithCategory" resultType="com.macro.mall.tiny.domain.UmsResourceExt">
    select ur.id,
           ur.create_time  as createTime,
           ur.name,
           ur.url,
           ur.description,
           ur.category_id  as categoryId,
           urc.id          as "category.id",
           urc.name        as "category.name",
           urc.sort        as "category.sort",
           urc.create_time as "category.createTime"
    from ums_resource ur
             left join ums_resource_category urc on ur.category_id = urc.id
    where ur.id = #{id}
</select>
  • 当然除了这种方式以外,我们还可以通过ResultMap+association标签来实现,不过在此之前我们在编写xml文件的时候,一般习惯于先给当前文件写一个BaseResultMap,用于对当前表的字段和对象属性进行直接映射,例如在UmsResourceCategoryDao.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.macro.mall.tiny.dao.UmsResourceCategoryDao">

    <resultMap id="BaseResultMap" type="com.macro.mall.tiny.model.UmsResourceCategory">
        <id property="id" column="id"/>
        <result property="createTime" column="create_time"/>
        <result property="name" column="name"/>
        <result property="sort" column="sort"/>
    </resultMap>
</mapper>
  • UmsResourceDao.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.macro.mall.tiny.dao.UmsResourceDao">

    <resultMap id="BaseResultMap" type="com.macro.mall.tiny.model.UmsResource">
        <id property="id" column="id"/>
        <result property="createTime" column="create_time"/>
        <result property="name" column="name"/>
        <result property="url" column="url"/>
        <result property="description" column="description"/>
        <result property="categoryId" column="category_id"/>
    </resultMap>
</mapper>
  • 编写完成后,我们的一对一ResultMap实现就很简单了,我们可以使用association标签进行一对一管理,配置columnPrefix属性将匹配到的字段直接映射到关联对象中去; `
<resultMap id="ResourceWithCategoryMap" type="com.macro.mall.tiny.domain.UmsResourceExt" extends="BaseResultMap">
    <association property="category" resultMap="com.macro.mall.tiny.dao.UmsResourceCategoryDao.BaseResultMap" columnPrefix="category_"/>
</resultMap>
  • 然后再编写下Dao中方法对应SQL实现即可,这里直接使用上面的ResultMap,同时给ums_resource_category表中的字段指定了category_前缀以便于映射。
<select id="selectResourceWithCategory2" resultMap="ResourceWithCategoryMap">
    select ur.id,
           ur.create_time,
           ur.name,
           ur.url,
           ur.description,
           ur.category_id,
           urc.id          as category_id,
           urc.name        as category_name,
           urc.sort        as category_sort,
           urc.create_time as category_create_time
    from ums_resource ur
             left join ums_resource_category urc on ur.category_id = urc.id
    where ur.id = #{id}
</select>
一对多映射
  1. 一对多关联映射

通过collection标签建立一对多关系;

xml
<resultMap id="userWithOrdersMap" type="User">
    <id column="user_id" property="id"/>
    <collection property="orders" ofType="Order">
        <id column="order_id" property="id"/>
        <result column="order_no" property="orderNo"/>
    </collection>
</resultMap>

<select id="getUserWithOrders" resultMap="userWithOrdersMap">
    SELECT u.id as user_id, o.id as order_id, o.order_no
    FROM users u
    LEFT JOIN orders o ON u.id = o.user_id
    WHERE u.id = #{userId}
</select>
分页插件
  1. PageHelper分页实现

可以直接使用开源的PageHelper插件即可,首先在pom.xml中添加它的Starter;

<!--MyBatis分页插件-->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>${pagehelper-starter.version}</version>
</dependency>

在查询方法之前使用它的startPage方法传入分页参数即可,分页后的得到的数据可以在users中获取到。

java
// Service层实现
public PageInfo<User> getUsersByPage(int pageNum, int pageSize) {
    PageHelper.startPage(pageNum, pageSize);
    List<User> users = userMapper.selectAll();
    return new PageInfo<>(users);
}
二级缓存
  1. 二级缓存配置
xml
<!-- 开启Mapper接口级别的二级缓存 -->
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>

<!-- 在application.yml中启用全局缓存 -->
mybatis:
  configuration:
    cache-enabled: true

关键问题说明

  1. 参数传递陷阱
  • 多个参数必须使用@Param注解
  • 集合参数应使用collection="array/list"(数组/List)
  1. 动态SQL最佳实践
  • 优先使用<where>标签避免空WHERE问题
  • <set>标签用于UPDATE语句自动处理逗号
  1. 性能优化建议
  • 大数据量分页使用PageHelper物理分页
  • 关联查询N+1问题使用<collection>fetchType="lazy"

以上内容已通过MyBatis 3.5+和SpringBoot 3.x环境验证,建议结合官方文档进行扩展学习。

在实际开发中,建议使用MyBatis Generator或MyBatis-Plus进行效率提升。

其他

MyBatis 缓存机制

一级缓存

  • 作用域: SqlSession 级别。
  • 特性: 默认开启,无需配置。同一个 SqlSession 内,相同的查询会从缓存中获取数据。

二级缓存

  • 作用域: Mapper 映射文件级别,基于 namespace。

二级缓存配置

xml
<!-- 开启Mapper接口级别的二级缓存 -->
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>

<!-- 在application.yml中启用全局缓存 -->
mybatis:
  configuration:
    cache-enabled: true

插件机制

后续可以记录一下分页插件等内容,这里大概了解一下。

MyBatis 提供了强大的插件机制,可以在某些特定点拦截执行。

  • 拦截类型: Executor、ParameterHandler、ResultSetHandler、StatementHandler。
  • 实现步骤:
  1. 创建自定义插件类,实现 Interceptor 接口。
  2. mybatis-config.xml 中注册插件。

示例:

package com.example.plugin;

import org.apache.ibatis.plugin.*;
import java.util.Properties;

@Intercepts({@Signature(
    type = Executor.class,
    method = "update",
    args = {MappedStatement.class, Object.class}
)})
public class MyPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 在执行方法前后添加自定义逻辑
        System.out.println("Before method execute");
        Object result = invocation.proceed();
        System.out.println("After method execute");
        return result;
    }

    @Override
    public void setProperties(Properties properties) {
        // 读取配置的属性
    }
}

mybatis-config.xml 中注册插件:

<plugins>
    <plugin interceptor="com.example.plugin.MyPlugin">
        <!-- 配置插件属性 -->
        <property name="someProperty" value="someValue"/>
    </plugin>
</plugins>