云计算百科
云计算领域专业知识百科平台

MyBatis 基础入门与 CRUD 详解

MyBatis 基础入门与 CRUD 详解

定位:MyBatis 系列第 1 篇:快速入门、全局配置、Mapper 映射、参数传递、主键回填与基础 CRUD 适用版本:MyBatis 3.5.x(JDK 8+)


目录

  • MyBatis 概述
  • 快速入门与 SqlSession 使用
  • 全局配置基础
  • Mapper 映射方式
  • 参数传递
  • #{} 与 ${}
  • 主键回填
  • 基础 CRUD 模板
  • 总结
  • 常见高频面试题

  • 一、MyBatis 概述

    1.1 历史演进

    MyBatis 的前身是 iBATIS,名称来源于 “Internet” 与 “Abatis”(鹿砦,一种防御工事)的组合,寓意「互联网的持久层防线」。

    时间事件
    2002 Clinton Begin 开源 iBATIS 项目
    2004 项目捐赠给 Apache 基金会,更名 Apache iBATIS
    2010 项目从 Apache 迁出,更名 MyBatis;同年发布 MyBatis 3,引入 Mapper 接口 + 动态代理的新架构
    2016 MyBatis 3.4.x:注解中支持 <script> 动态 SQL,原生支持 Java 8 日期时间类型(JSR-310)
    2019 MyBatis 3.5.x 发布,最低要求 JDK 8;3.5.x 系列持续维护至今

    生态版图:

    项目定位
    MyBatis 核心框架 SQL 映射引擎本体(本篇开始讲解)
    MyBatis-Spring Spring 整合(见系列 06 篇)
    MyBatis-Spring-Boot-Starter Spring Boot 自动装配(见系列 06 篇)
    MyBatis-Plus 国产增强工具:免 SQL 单表 CRUD(见系列 07 篇)
    PageHelper 第三方分页插件(原理见系列 05 篇)

    1.2 定位:半自动化 ORM

    ORM(对象关系映射)框架按「SQL 由谁控制」分为两类:

    类别代表SQL 控制权特点
    全自动 ORM Hibernate、JPA 实现(EclipseLink 等) 框架根据对象模型自动生成 SQL 开发效率高;复杂 SQL 难以精细控制
    半自动 ORM MyBatis 开发者手写 SQL,框架负责参数映射与结果映射 SQL 完全可控、便于优化;映射代码由框架托管

    MyBatis 官方文档对其定位是「支持自定义 SQL、存储过程和高级映射的持久层框架」。它消除了几乎全部 JDBC 样板代码:

    // 裸 JDBC:约 20 行样板代码
    Connection conn = dataSource.getConnection();
    PreparedStatement ps = conn.prepareStatement("SELECT id, name FROM user WHERE id = ?");
    ps.setLong(1, 1L);
    ResultSet rs = ps.executeQuery();
    User user = null;
    if (rs.next()) {
    user = new User();
    user.setId(rs.getLong("id"));
    user.setName(rs.getString("name"));
    }
    rs.close(); ps.close(); conn.close(); // 还有异常处理、事务……

    // MyBatis:一行调用 + 一份映射文件
    User user = userMapper.selectById(1L);

    1.3 同类产品对比与选型

    维度MyBatisHibernate/JPASpring JdbcTemplateSpring Data JDBC
    抽象层级 半自动 ORM 全自动 ORM JDBC 薄封装 轻量 ORM
    SQL 控制 手写,完全可控 HQL/JPQL + 自动生成 手写 部分自动生成
    对象映射 resultMap 显式/自动驼峰 注解实体全自动映射 手写 RowMapper 注解实体映射
    缓存 一级/二级缓存 一级/二级缓存 + 查询缓存
    学习曲线 平缓(会 SQL 即可入门) 较陡(需理解持久化上下文) 极低
    典型场景 复杂查询多、SQL 需 DBA 审查优化 领域模型稳定、CRUD 密集 简单项目、临时脚本 聚合根模型的轻量持久化

    选型经验:互联网业务系统中表结构变更频繁、查询复杂度高、需要精细 SQL 调优,MyBatis 是国内主流选择;若领域模型稳定且以单实体 CRUD 为主,JPA 效率更高。二者并非互斥,同一系统可混合使用。ORM 通用原理与选型方法论见《01-ORM框架知识点详解》。

    1.4 核心价值

  • SQL 与代码分离:SQL 集中在 XML/注解中管理,便于审查、优化与复用。
  • 消除 JDBC 样板:连接获取、参数绑定、结果集遍历全部由框架托管。
  • 动态 SQL:声明式标签替代手工字符串拼接,天然规避注入风险(见 02 篇)。
  • 可扩展性:插件机制提供执行链路上的拦截点(见 05 篇)。

  • 二、快速入门与 SqlSession 使用

    2.1 最小运行环境

    脱离 Spring 的原生 MyBatis 需要四样东西:依赖、全局配置、Mapper XML、实体类。

    <!– pom.xml –>
    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.16</version>
    </dependency>
    <dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.4.0</version>
    </dependency>

    实体类与表结构对应:

    public class User {
    private Long id;
    private String name;
    private String email;
    private LocalDateTime createTime;
    // getter/setter 省略
    }

    2.2 核心 API 两件套

    SqlSessionFactoryBuilder ──解析配置──→ SqlSessionFactory ──openSession──→ SqlSession
    (应用级单例、重量级) (非线程安全、用完即关)

    API生命周期线程安全使用纪律
    SqlSessionFactoryBuilder 用完即弃 构建完 Factory 后可丢弃
    SqlSessionFactory 应用级单例 全局一个,重复创建是典型反模式
    SqlSession 请求/会话级 必须 try-with-resources 关闭,禁止作为成员变量共享

    2.3 原生使用完整流程

    public class MyBatisDemo {

    private static final SqlSessionFactory FACTORY;

    static {
    try (InputStream in = Resources.getResourceAsStream("mybatis-config.xml")) {
    FACTORY = new SqlSessionFactoryBuilder().build(in);
    } catch (IOException e) {
    throw new ExceptionInInitializerError(e);
    }
    }

    public static void main(String[] args) {
    // 查询
    try (SqlSession session = FACTORY.openSession()) {
    UserMapper mapper = session.getMapper(UserMapper.class);
    User user = mapper.selectById(1L);
    System.out.println(user.getName());
    }

    // 写操作:openSession() 默认 autoCommit=false,需手动 commit
    try (SqlSession session = FACTORY.openSession()) {
    UserMapper mapper = session.getMapper(UserMapper.class);
    User u = new User();
    u.setName("张三");
    u.setEmail("zhangsan@example.com");
    mapper.insert(u);
    session.commit();
    System.out.println("新主键:" + u.getId()); // 已回填
    }
    }
    }

    要点:

    • session.getMapper(UserMapper.class) 返回的是 JDK 动态代理对象,Mapper 接口无需实现类(代理原理见系列 03 篇)。
    • 原生模式下写操作不提交不生效;Spring 整合后由事务管理器托管,无需手动 commit(见系列 06 篇)。
    • SqlSession 还提供更底层的门面 API(selectOne/selectList/insert/update/delete,以 statementId 调用),实际开发统一走 Mapper 接口。

    三、全局配置基础

    3.1 mybatis-config.xml 结构

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">

    <configuration>
    <!– 元素顺序固定:properties → settings → typeAliases → typeHandlers
    → objectFactory → plugins → environments → mappers,乱序会解析报错 –>

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

    <typeAliases>
    <package name="com.example.entity"/>
    </typeAliases>

    <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/demo"/>
    <property name="username" value="root"/>
    <property name="password" value="******"/>
    </dataSource>
    </environment>
    </environments>

    <mappers>
    <package name="com.example.mapper"/>
    </mappers>
    </configuration>

    3.2 environments 环境配置

    • default 属性决定激活哪个 environment(如 development/production)。
    • transactionManager:
      • JDBC:MyBatis 直接调用 Connection 的 commit/rollback;
      • MANAGED:事务由容器(如 Spring、应用服务器)托管,MyBatis 不提交。
    • dataSource 类型:
    类型说明适用
    UNPOOLED 每次新建连接,无池化 仅测试
    POOLED 内置连接池(poolMaximumActiveConnections 默认 10 等参数可调) 简单应用
    JNDI 从应用服务器 JNDI 查找数据源 传统 JavaEE

    生产实践:以上三种都会被 Druid/HikariCP 等专业连接池替代——通过 Spring 整合时 DataSource 由 Spring 管理,mybatis-config.xml 中的 environments 整节不再需要(见系列 06 篇)。

    3.3 常用 settings(入门必知项)

    配置项默认值说明
    mapUnderscoreToCamelCase false 下划线列名自动转驼峰属性,强烈建议开启,否则 create_time 映射不到 createTime
    useGeneratedKeys false 全局允许主键回填
    cacheEnabled true 二级缓存总开关(生产建议关闭,见系列 04 篇)
    lazyLoadingEnabled false(3.4.1+) 嵌套查询延迟加载全局开关(见 02 篇)
    logImpl 自动探测 指定日志实现;开发排障可临时设 STDOUT_LOGGING 打印 SQL
    defaultExecutorType SIMPLE SIMPLE/REUSE/BATCH(原理见 03 篇)

    3.4 typeAliases 类型别名

    <typeAliases>
    <typeAlias type="com.example.entity.User" alias="user"/>
    <package name="com.example.entity"/> <!– 批量注册:别名=类名首字母小写,@Alias 可自定义 –>
    </typeAliases>

    作用仅是简化 XML 书写:resultType="user" 等价于 resultType="com.example.entity.User"。基本类型别名(int、string、map、list 等)内置可用且大小写不敏感。

    3.5 mappers 映射注册

    <mappers>
    <mapper resource="mapper/UserMapper.xml"/> <!– 类路径文件 –>
    <mapper url="file:///opt/app/mapper/OrderMapper.xml"/><!– 绝对路径 –>
    <mapper class="com.example.mapper.UserMapper"/> <!– 注解 Mapper –>
    <package name="com.example.mapper"/> <!– 包扫描:要求 XML 与接口同名同包 –>
    </mappers>

    <package> 方式的约定:com.example.mapper.UserMapper 接口与 com/example/mapper/UserMapper.xml 同包同名,自动配对。Spring Boot Starter 下由 mybatis.mapper-locations 替代(见 06 篇)。


    四、Mapper 映射方式

    4.1 XML 映射(主流)

    约束两条:namespace 必须等于 Mapper 接口全限定名;id 必须等于接口方法名。二者共同构成 statementId(namespace.id),是框架定位 SQL 的唯一依据。

    <?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">

    <select id="selectById" resultType="com.example.entity.User">
    SELECT id, name, email, create_time
    FROM user
    WHERE id = #{id}
    </select>

    </mapper>

    对应接口:

    public interface UserMapper {
    User selectById(Long id);
    }

    4.2 注解映射

    public interface UserMapper {

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

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

    @Update("UPDATE user SET name = #{name} WHERE id = #{id}")
    int updateById(User user);

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

    注解族补充:

    • @Options:承载 useGeneratedKeys、keyProperty、flushCache、useCache、timeout 等语句级属性。
    • @Results + @Result:注解版 resultMap(复杂映射可读性差,仍推荐 XML)。
    • @SelectProvider 等 Provider 注解:SQL 由 Java 方法动态构建(见 02 篇)。

    选型建议:单表简单 CRUD 用注解,复杂 SQL、动态 SQL、嵌套映射用 XML;同一 Mapper 内风格尽量统一。

    4.3 混用规则

  • 同一 statementId 不能同时存在 XML 与注解定义,启动即报错。
  • 同一接口内可部分方法注解、部分方法 XML,框架按 statementId 各自解析。

  • 五、参数传递

    5.1 单参数

    // 基本类型:#{} 内名称不校验(仅一个参数),任意写法都能取到值
    User selectById(Long id); // XML: WHERE id = #{id}

    // POJO:#{属性名},经 getter 取值
    int updateById(User user); // XML: SET name = #{name} WHERE id = #{id}

    // Map:#{key}
    User selectByMap(Map<String, Object> params);// XML: WHERE name = #{name}

    5.2 多参数

    // ❌ 不推荐:只能用 param1/param2 或 arg0/arg1 引用,可读性差且受编译参数影响
    User selectByNameAndStatus(String name, Integer status);

    // ✅ 推荐:@Param 显式命名,参数封装为 ParamMap
    User selectByNameAndStatus(@Param("name") String name,
    @Param("status") Integer status);
    // XML: WHERE name = #{name} AND status = #{status}

    5.3 集合与数组

    // List 单参数:foreach 中 collection="list"(旧约定)或 @Param 命名
    List<User> selectByIds(@Param("ids") List<Long> ids);
    // XML: <foreach collection="ids" …>

    // 数组单参数:collection="array"
    List<User> selectByIds(Long[] ids);

    5.4 parameterType 属性

    parameterType 可省略——MyBatis 通过反射推断实际参数类型。该属性仅作可读性提示,不做强制校验,现代项目普遍省略。

    5.5 特殊类型处理

    类型默认行为定制手段
    枚举 按 name() 字符串存取(EnumTypeHandler) 换 EnumOrdinalTypeHandler(按序号)或自定义 TypeHandler
    null 参数 多数数据库直接设 null Oracle 等场景必须声明 jdbcType(如 #{status,jdbcType=NUMERIC})
    Java 8 日期时间 3.4.5+ 内置 JSR-310 TypeHandler,LocalDateTime 等直接映射 无需额外配置

    TypeHandler 的完整机制(自定义 JSON 列转换等)在 02 篇结果映射章节展开。


    六、#{} 与 ${}

    二者在编译期与运行期的行为完全不同:

    #{id} 的处理:
    XML SQL: SELECT * FROM user WHERE id = #{id}
    BoundSql: SELECT * FROM user WHERE id = ? ← 预编译占位符
    参数绑定: ps.setLong(1, 1001L) ← PreparedStatement 绑定

    ${name} 的处理:
    XML SQL: SELECT * FROM user WHERE name = '${name}'
    BoundSql: SELECT * FROM user WHERE name = '张三' ← OGNL 求值后直接拼接

    对比项#{}${}
    本质 预编译占位符 ? 字符串替换
    SQL 注入 天然防御 有风险
    执行计划缓存 同模板 SQL 可复用 每个值生成不同 SQL 文本
    适用位置 所有数据值 表名、列名、ORDER BY 字段等标识符

    ${} 的合理使用场景只有标识符拼接(占位符 ? 不能替代表名/列名),且必须做白名单校验:

    // ❌ 错误示例:直接拼接用户输入 → SQL 注入
    List<User> sortUsers(@Param("col") String col);
    // XML: ORDER BY ${col} 若 col = "id; DROP TABLE user" 后果严重

    // ✅ 正确做法:Service 层白名单校验后再传入
    private static final Set<String> SORTABLE = Set.of("id", "name", "create_time");

    public List<User> sortUsers(String col) {
    if (!SORTABLE.contains(col)) {
    throw new IllegalArgumentException("非法排序列: " + col);
    }
    return userMapper.sortUsers(col);
    }

    #{} 还支持高级属性内联声明:

    <!– 指定 jdbcType 与 typeHandler –>
    <insert id="insert">
    INSERT INTO user(name, status)
    VALUES(#{name}, #{status,jdbcType=NUMERIC,typeHandler=com.example.handler.StatusTypeHandler})
    </insert>


    七、主键回填

    7.1 自增主键:useGeneratedKeys

    底层使用 JDBC getGeneratedKeys 能力:

    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO user(name, email) VALUES(#{name}, #{email})
    </insert>
    <!– 执行后入参对象的 getId() 已被回填 –>

    注解版:

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

    3.5+ 支持复合回填:keyProperty="id,createTime"(配合支持多列生成的数据库)。批量插入的 foreach 语句同样支持,3.3.1+ 会把生成的主键依次回填到集合各元素。

    7.2 序列与前置生成:selectKey

    <!– Oracle 序列:先取主键再插入 –>
    <insert id="insert">
    <selectKey keyProperty="id" resultType="long" order="BEFORE">
    SELECT seq_user.NEXTVAL FROM dual
    </selectKey>
    INSERT INTO user(id, name) VALUES(#{id}, #{name})
    </insert>

    <!– MySQL:插入后取 LAST_INSERT_ID(useGeneratedKeys 不可用时的替代) –>
    <insert id="insert">
    <selectKey keyProperty="id" resultType="long" order="AFTER">
    SELECT LAST_INSERT_ID()
    </selectKey>
    INSERT INTO user(name, email) VALUES(#{name}, #{email})
    </insert>

    UUID 预生成同理(order="BEFORE" + Java 侧生成或数据库函数生成)。

    7.3 注意区分

    insert/update/delete 的返回值是影响行数,不是主键值;主键只通过 keyProperty 回填到入参对象。这是初学者最常见的误解。


    八、基础 CRUD 模板

    8.1 完整模板(XML)

    <mapper namespace="com.example.mapper.UserMapper">

    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO user(name, email, create_time)
    VALUES(#{name}, #{email}, #{createTime})
    </insert>

    <delete id="deleteById">
    DELETE FROM user WHERE id = #{id}
    </delete>

    <update id="updateById">
    UPDATE user
    SET name = #{name}, email = #{email}
    WHERE id = #{id}
    </update>

    <select id="selectById" resultType="com.example.entity.User">
    SELECT id, name, email, create_time FROM user WHERE id = #{id}
    </select>

    <select id="selectAll" resultType="com.example.entity.User">
    SELECT id, name, email, create_time FROM user
    </select>

    <select id="selectByIds" resultType="com.example.entity.User">
    SELECT id, name, email, create_time FROM user
    WHERE id IN
    <foreach collection="ids" item="id" open="(" separator="," close=")">
    #{id}
    </foreach>
    </select>

    <select id="count" resultType="long">
    SELECT COUNT(*) FROM user
    </select>

    </mapper>

    说明:本篇 updateById 为全字段更新;生产环境推荐选择性更新(只更新非 null 字段),需要动态 SQL 的 <set>+<if> 支持,见 02 篇。

    8.2 返回值约定

    方法类型返回值说明
    查询单条 实体或 null 命中多条时 selectOne 抛 TooManyResultsException
    查询多条 List<T> 无结果返回空集合(非 null)
    增删改 int(影响行数) 不是主键值
    无实体临时查询 resultType="map" 列名做 key,适合报表类一次性查询

    8.3 批量操作入门

    // IN 查询:foreach 拼接(调用前必须判空,防止 IN () 语法错误)
    if (ids != null && !ids.isEmpty()) {
    List<User> users = userMapper.selectByIds(ids);
    }

    // 批量写入:批量模式 Executor
    try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
    UserMapper mapper = session.getMapper(UserMapper.class);
    for (User u : userList) {
    mapper.insert(u);
    }
    session.flushStatements(); // 统一提交批次
    session.commit();
    }

    批量写入的进阶要点(分批大小、MySQL rewriteBatchedStatements 参数)在 08 篇性能调优章节展开。


    九、总结

  • 定位:MyBatis 是半自动 ORM——SQL 手写、框架托管参数与结果映射;与 Hibernate(全自动)、JdbcTemplate(薄封装)构成三个抽象层级,适合 SQL 需精细控制的系统。
  • 核心 API 纪律:SqlSessionFactory 应用级单例;SqlSession 非线程安全、用完即关(try-with-resources);写操作在原生模式下需手动 commit。
  • 全局配置:mybatis-config.xml 元素顺序固定;入门必知 settings 首推 mapUnderscoreToCamelCase;生产环境数据源交由专业连接池与 Spring 管理。
  • 映射方式:XML(namespace=接口全限定名、id=方法名)与注解二选一或混用,同一 statementId 不可重复定义。
  • 参数传递:多参数一律 @Param 显式命名;parameterType 可省略;枚举与 null 参数注意 TypeHandler 与 jdbcType。
  • 占位符铁律:#{} 预编译防注入用于所有数据值;${} 仅用于标识符且必须白名单校验。
  • 主键回填:自增用 useGeneratedKeys+keyProperty,序列/预生成用 selectKey;注意返回值是影响行数而非主键。
  • CRUD 基础:掌握标准模板与返回值约定,选择性更新、动态条件等待 02 篇动态 SQL 补全。

  • 十、常见高频面试题

    1. #{} 和 ${} 的区别?

    要点:#{} 解析为预编译占位符 ?,通过 PreparedStatement 绑定参数,防 SQL 注入且利于执行计划缓存;${} 是 OGNL 求值后的字符串直接替换,有注入风险,只应用于表名/列名/排序字段等标识符场景且必须白名单校验。

    2. MyBatis 的 insert 返回值是什么?如何拿到自增主键?

    要点:返回值是影响行数而非主键。自增主键通过 useGeneratedKeys="true" keyProperty="id"(底层 JDBC getGeneratedKeys)回填到入参对象;序列类主键用 <selectKey order="BEFORE"> 先取后插。

    3. Mapper 接口方法有多个参数时如何传递?

    要点:不加 @Param 时只能用 param1/param2(或 arg0/arg1)引用;推荐 @Param("name") 显式命名,参数被封装为 ParamMap,XML 中以名称引用。单个 POJO/Map 参数则直接引用属性/key。

    4. parameterType 是必须的吗?

    要点:不是。MyBatis 通过反射推断实际参数类型,parameterType 仅作可读性提示且不做强制校验,现代项目普遍省略。

    5. MyBatis 相比 Hibernate 的优缺点?如何选型?

    要点:MyBatis 优点——SQL 完全可控、复杂查询易优化、DBA 可介入、学习曲线平缓;缺点——SQL 与映射文件维护量大、不支持数据库无关性。选型:SQL 复杂多变、需精细调优选 MyBatis;领域模型稳定的 CRUD 密集型系统选 Hibernate/JPA;二者可共存。

    6. SqlSession 是线程安全的吗?应该如何使用?

    要点:不是线程安全的,内部持有 Executor 与一级缓存等会话状态。纪律:作为方法内局部变量,try-with-resources 用完即关,禁止作为类成员或静态变量共享;SqlSessionFactory 才是应用级单例。

    7. MyBatis 中如何实现模糊查询且防止 SQL 注入?

    要点:WHERE name LIKE CONCAT('%', #{kw}, '%') 或 <bind name="kw" value="'%' + keyword + '%'"/> 后用 #{kw};禁止 '%${kw}%' 拼接。

    8. mybatis-config.xml 中如何批量注册 Mapper?约定是什么?

    要点:<package name="com.example.mapper"/> 包扫描,约定 XML 与接口同名同包自动配对;也可 resource/url/class 逐个注册。Spring Boot 环境下由 mybatis.mapper-locations 通配符配置替代。

    在这里插入图片描述

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » MyBatis 基础入门与 CRUD 详解
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!