在 Java 后端开发中,数据库交互是核心环节之一。JDBC 作为 Java 原生的数据库操作方案,流程繁琐且代码冗余,而 MyBatis 作为优秀的持久层框架,能极大简化数据库操作。本文将从 MyBatis 基础入门到进阶应用,结合实战案例全面讲解其使用方法与核心特性。

一、MyBatis 入门:告别 JDBC 的繁琐

1.1 为什么选择 MyBatis?

回顾 JDBC 操作流程,从创建连接池、获取连接,到编写 SQL、绑定参数、处理结果集,再到释放资源,整个过程需要大量重复代码。以添加图书为例,仅基础操作就需数十行代码,且参数绑定、资源清理等逻辑在每个方法中都要重复实现。

MyBatis 的出现完美解决了这些问题:它是一款专注于简化 JDBC 开发的持久层框架,前身为 Apache 的 iBatis 项目,2010 年迁移后更名并持续迭代。其核心优势在于将 SQL 与 Java 代码解耦,自动完成参数映射与结果封装,大幅减少模板代码。

1.2 入门环境搭建

(1)项目初始化

创建 Spring Boot 工程,引入 MyBatis 起步依赖与 MySQL 驱动:

<!-- 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>

注意:Spring Boot 3.x 对应 MyBatis 3.x 版本,版本兼容关系可参考官方文档

(2)数据库与实体类准备

以图书管理系统为例,创建数据库与表结构:

-- 创建数据库
DROP DATABASE IF EXISTS book_test;
CREATE DATABASE book_test DEFAULT CHARACTER SET utf8mb4;
USE book_test;

-- 图书表
CREATE TABLE `book_info` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `book_name` VARCHAR(127) NOT NULL,
  `author` VARCHAR(127) NOT NULL,
  `count` INT NOT NULL,
  `price` DECIMAL(7,2) NOT NULL,
  `publish` VARCHAR(256) NOT NULL,
  `status` TINYINT DEFAULT 1 COMMENT '0-无效,1-可借阅,2-不可借阅',
  `create_time` DATETIME DEFAULT now(),
  `update_time` DATETIME DEFAULT now() ON UPDATE now(),
  PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;

创建对应实体类BookInfo(使用 Lombok 简化 getter/setter):

import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;

@Data
public class BookInfo {
    private Integer id;
    private String bookName; // 对应表中book_name字段
    private String author;
    private Integer count;
    private BigDecimal price;
    private String publish;
    private Integer status;
    private Date createTime; // 对应表中create_time字段
    private Date updateTime;
}
(3)数据库配置

application.yml中配置数据库连接与 MyBatis 基础参数:

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/book_test?characterEncoding=utf8&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver # MySQL 8.x驱动

mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志
    map-underscore-to-camel-case: true # 驼峰命名自动转换

1.3 基础 CRUD 实现

MyBatis 提供注解与 XML 两种开发方式,简单场景推荐注解,复杂 SQL 建议使用 XML。

(1)注解方式

创建 Mapper 接口,通过注解编写 SQL:

import org.apache.ibatis.annotations.*;
import java.util.List;

@Mapper // 标识为MyBatis映射接口
public interface BookInfoMapper {
    // 新增图书
    @Insert("insert into book_info(book_name,author,count,price,publish,status) " +
            "values(#{bookName},#{author},#{count},#{price},#{publish},#{status})")
    @Options(useGeneratedKeys = true, keyProperty = "id") // 返回自增主键
    Integer insertBook(BookInfo bookInfo);

    // 根据ID查询
    @Select("select * from book_info where id=#{id} and status<>0")
    BookInfo queryById(Integer id);

    // 更新图书
    @Update("update book_info set book_name=#{bookName},author=#{author} where id=#{id}")
    Integer updateBook(BookInfo bookInfo);

    // 删除图书(逻辑删除)
    @Delete("update book_info set status=0 where id=#{id}")
    Integer deleteBook(Integer id);
}
(2)XML 方式

当 SQL 逻辑复杂时,XML 方式更易维护。首先在配置文件中指定 XML 路径:

mybatis:
  mapper-locations: classpath:mapper/**Mapper.xml # XML文件存放路径

创建BookInfoMapper.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接口全路径 -->
<mapper namespace="com.example.demo.mapper.BookInfoMapper">
    <!-- 新增图书 -->
    <insert id="insertBook" useGeneratedKeys="true" keyProperty="id">
        insert into book_info(book_name,author,count,price,publish,status)
        values(#{bookName},#{author},#{count},#{price},#{publish},#{status})
    </insert>

    <!-- 动态更新图书 -->
    <update id="updateBook">
        update book_info
        <set>
            <if test="bookName!=null">book_name=#{bookName},</if>
            <if test="author!=null">author=#{author},</if>
            <if test="price!=null">price=#{price},</if>
        </set>
        where id=#{id}
    </update>
</mapper>
(3)单元测试

使用 Spring Boot Test 验证功能:

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class BookInfoMapperTest {
    @Autowired
    private BookInfoMapper bookInfoMapper;

    @Test
    void testInsert() {
        BookInfo book = new BookInfo();
        book.setBookName("MyBatis实战");
        book.setAuthor("张三");
        book.setCount(10);
        book.setPrice(new BigDecimal("59.9"));
        book.setPublish("机械工业出版社");
        book.setStatus(1);
        
        bookInfoMapper.insertBook(book);
        System.out.println("新增图书ID:" + book.getId()); // 自增ID已赋值
    }
}

二、MyBatis 进阶:动态 SQL 与实战技巧

2.1 动态 SQL 核心标签

动态 SQL 是 MyBatis 的强大特性,能根据条件动态拼接 SQL,常见标签如下:

(1)<if>:条件判断

用于实现可选字段的动态处理,例如注册用户时的非必填字段:

<insert id="insertUser">
    INSERT INTO user_info (username,password,
    <if test="gender!=null">gender,</if>
    phone)
    VALUES (#{username},#{password},
    <if test="gender!=null">#{gender},</if>
    #{phone})
</insert>

注意:test属性中引用的是实体类属性,而非数据库字段。

(2)<trim>:去除多余符号

解决多条件拼接时的逗号、AND 等多余符号问题,例如动态新增:

<insert id="insertUserByTrim">
    INSERT INTO user_info
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="username!=null">username,</if>
        <if test="password!=null">`password`,</if>
        <if test="phone!=null">phone,</if>
    </trim>
    VALUES
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="username!=null">#{username},</if>
        <if test="password!=null">#{password},</if>
        <if test="phone!=null">#{phone},</if>
    </trim>
</insert>
  • prefix:添加前缀
  • suffix:添加后缀
  • suffixOverrides:去除末尾多余符号
(3)<where>:动态条件拼接

自动处理 WHERE 子句开头的 AND/OR,例如多条件查询:

<select id="queryByCondition" resultType="BookInfo">
    select * from book_info
    <where>
        <if test="bookName!=null">and book_name like concat('%',#{bookName},'%')</if>
        <if test="author!=null">and author=#{author}</if>
        <if test="status!=null">and status=#{status}</if>
    </where>
</select>
(4)<foreach>:集合遍历

用于批量操作,例如批量删除:

<delete id="batchDelete">
    delete from book_info where id in
    <foreach collection="ids" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</delete>
  • collection:集合参数名
  • item:遍历元素别名
  • open/close:包裹符号
  • separator:分隔符
(5)<include>:SQL 片段复用

抽取重复 SQL 片段,提升可维护性:

<!-- 定义SQL片段 -->
<sql id="allColumns">
    id,book_name,author,count,price,publish,status,create_time,update_time
</sql>

<!-- 引用SQL片段 -->
<select id="queryAll" resultType="BookInfo">
    select <include refid="allColumns"/> from book_info
</select>

2.2 #{} 与 ${} 的核心区别

两者均用于参数注入,但存在本质差异:

特性 #{} ${}
处理方式 预编译 SQL,? 占位符 字符串直接替换
安全性 防止 SQL 注入 存在 SQL 注入风险
类型处理 自动添加引号 需手动添加引号
使用场景 普通参数注入 排序字段、表名等动态标识符

示例:排序功能实现排序字段无法使用#{}(会自动加引号导致语法错误),需使用${}

@Select("select * from book_info order by ${sortField} ${sortOrder}")
List<BookInfo> queryBySort(@Param("sortField") String sortField, @Param("sortOrder") String sortOrder);

注意:使用${}时需严格校验参数,避免 SQL 注入。

2.3 分页实现

当数据量较大时,分页查询是必备功能。MyBatis 可通过LIMIT关键字手动实现分页:

(1)分页请求与结果封装
// 分页请求参数
@Data
public class PageRequest {
    private int currentPage = 1; // 当前页码
    private int pageSize = 10;   // 每页条数

    // 计算起始索引
    public int getOffset() {
        return (currentPage - 1) * pageSize;
    }
}

// 分页结果封装
@Data
public class PageResult<T> {
    private int total;        // 总记录数
    private List<T> records;  // 当前页数据

    public PageResult(int total, List<T> records) {
        this.total = total;
        this.records = records;
    }
}
(2)分页查询实现
// Mapper接口
public interface BookInfoMapper {
    // 查询总记录数
    @Select("select count(1) from book_info where status<>0")
    Integer count();

    // 分页查询列表
    @Select("select * from book_info where status<>0 order by id desc limit #{offset},#{pageSize}")
    List<BookInfo> queryByPage(PageRequest pageRequest);
}

// Service层
@Service
public class BookService {
    @Autowired
    private BookInfoMapper bookMapper;

    public PageResult<BookInfo> getPage(PageRequest pageRequest) {
        int total = bookMapper.count();
        List<BookInfo> records = bookMapper.queryByPage(pageRequest);
        return new PageResult<>(total, records);
    }
}

2.4 数据库连接池

MyBatis 依赖数据库连接池管理连接,Spring Boot 默认使用 Hikari(性能优异),也可切换为 Druid(功能丰富)。

(1)切换为 Druid 连接池

引入依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-3-starter</artifactId>
    <version>1.2.21</version>
</dependency>

配置无需修改,Spring Boot 会自动识别并启用。

三、实战案例:图书管理系统核心功能

结合 Spring MVC 实现图书管理系统的核心功能,包含登录校验、图书 CRUD、分页查询等。

3.1 强制登录实现

通过 Session 校验用户登录状态,未登录则跳转至登录页:

// 常量定义
public class Constants {
    public static final String SESSION_USER_KEY = "session_user_key";
}

// 登录接口
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping("/login")
    public boolean login(String username, String password, HttpSession session) {
        UserInfo user = userService.queryByUsername(username);
        if (user != null && password.equals(user.getPassword())) {
            user.setPassword(""); // 清空密码
            session.setAttribute(Constants.SESSION_USER_KEY, user);
            return true;
        }
        return false;
    }
}

// 图书列表接口(登录校验)
@RestController
@RequestMapping("/book")
public class BookController {
    @Autowired
    private BookService bookService;

    @RequestMapping("/getList")
    public Result<PageResult<BookInfo>> getList(PageRequest pageRequest, HttpSession session) {
        // 登录校验
        UserInfo user = (UserInfo) session.getAttribute(Constants.SESSION_USER_KEY);
        if (user == null) {
            return Result.unlogin(); // 未登录响应
        }
        PageResult<BookInfo> pageResult = bookService.getPage(pageRequest);
        return Result.success(pageResult);
    }
}

3.2 批量删除功能

// Mapper XML
<update id="batchDelete">
    update book_info set status=0 where id in
    <foreach collection="ids" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</update>

// Controller
@RequestMapping("/batchDelete")
public boolean batchDelete(@RequestParam List<Integer> ids) {
    try {
        bookService.batchDelete(ids);
        return true;
    } catch (Exception e) {
        return false;
    }
}

四、MyBatis Generator:代码自动生成

MyBatis Generator(MBG)可根据数据库表结构自动生成实体类、Mapper 接口及 XML 文件,大幅提升开发效率。

4.1 配置 MBG 插件

pom.xml中添加插件:

<plugin>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-maven-plugin</artifactId>
    <version>1.3.6</version>
    <configuration>
        <configurationFile>src/main/resources/mybatis-generator/generatorConfig.xml</configurationFile>
        <overwrite>true</overwrite>
    </configuration>
    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>
    </dependencies>
</plugin>

4.2 编写配置文件

创建generatorConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration PUBLIC
        "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <context id="Mysql" targetRuntime="MyBatis3Simple">
        <!-- 禁用注释 -->
        <commentGenerator>
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>

        <!-- 数据库连接 -->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://127.0.0.1:3306/book_test?serverTimezone=Asia/Shanghai"
                        userId="root" password="root">
        </jdbcConnection>

        <!-- 生成实体类 -->
        <javaModelGenerator targetPackage="com.example.demo.model" targetProject="src/main/java">
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>

        <!-- 生成XML文件 -->
        <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources"/>

        <!-- 生成Mapper接口 -->
        <javaClientGenerator targetPackage="com.example.demo.mapper" targetProject="src/main/java" type="XMLMAPPER"/>

        <!-- 生成指定表 -->
        <table tableName="book_info" domainObjectName="BookInfo">
            <generatedKey column="id" sqlStatement="Mysql" identity="true"/>
        </table>
    </context>
</generatorConfiguration>

4.3 生成代码

在 Maven 插件中执行mybatis-generator:generate,即可自动生成实体类、Mapper 接口和 XML 文件。

五、总结与最佳实践

  1. 开发方式选择:简单 CRUD 用注解,复杂 SQL(动态 SQL、多表关联)用 XML。
  2. 参数注入:优先使用#{} 防止 SQL 注入,仅在排序字段、表名等场景使用${}并做好参数校验。
  3. 命名规范:数据库表 / 字段用蛇形命名(book_name),实体类用驼峰命名(bookName),开启map-underscore-to-camel-case自动转换。
  4. 代码复用:使用<sql><include>抽取重复 SQL 片段,减少冗余。
  5. 性能优化:合理使用数据库连接池,避免SELECT *查询,分页查询减少数据传输量。

MyBatis 以其轻量、灵活的特性成为 Java 持久层框架的主流选择,掌握其基础用法与进阶特性,能显著提升数据库操作效率与代码质量。建议结合实际项目多做练习,深入理解其参数映射与动态 SQL 的实现原理。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐