Mybatis入门到精通 一

引言

Java项目开发中,只要使用了关系型数据库,几乎都会用到Mybatis框架。从本文起,我们一起来看看Mybatis的源码实现,从而深入掌握这个框架。

本文从JDBC规范说起,直接使用java.sql包编程,展示了使用底层API做实战开发不可避免的痛点;当引入Mybatis时,又是如何的高效、灵活。进而介绍了Mybatis框架的功能架构和配置解析流程。

一 什么是JDBC

JDBC(Java Database Connectivity)是Java中访问关系型数据库的一套规范化API,将Java应用中使用它,可以执行SQL语句、检索结果和更改数据等。

1.1 示例

Java 复制代码
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * JDBC 查询 MySQL 用户
 */
public class JdbcExample {

    private static final String DB_URL = "jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true";
    private static final String DB_USER = "root";       // 你的数据库用户名
    private static final String DB_PASSWORD = "123456"; // 你的数据库密码

    public static void main(String[] args) throws ClassNotFoundException {
        // 1. 加载驱动(MySQL 8.0+ 可省略,Driver 类会自动注册)
        Class.forName("com.mysql.cj.jdbc.Driver");

        try (
            // 获取数据库连接
            Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
            // 预编译 SQL(防止 SQL 注入)
            PreparedStatement pstmt = conn.prepareStatement("SELECT id, username, age, email FROM user WHERE age > ?");
        ) {
            // 3. 设置 SQL 参数(索引从 1 开始)
            pstmt.setInt(1, 20);
            // 4. 执行查询,获取结果集
            ResultSet rs = pstmt.executeQuery();
            // 5. 遍历结果集
            while (rs.next()) {
                int id = rs.getInt("id");
                String username = rs.getString("username");
                int age = rs.getInt("age");
                String email = rs.getString("email");
                System.out.printf("ID: %d, 用户名: %s, 年龄: %d, 邮箱: %s%n", id, username, age, email);
            }
        } catch (SQLException e) {
            System.err.println("JDBC 操作异常:");
        }
    }
}

上面示例中出现了JDBC中几个核心类:Driver、DriverManager、Connection、PreparedStatement、ResultSet

核心类 核心作用
Driver 注册驱动,建立通信桥梁,适配不同数据库(如MySQL/Oracle)
DriverManager 管理驱动,获取数据库连接
Connection 代表数据库物理连接,所有操作入口,实现事务管理、创建Statement
PreparedStatement Statement接口实现,预编译 SQL,能防注入、提升性能
ResultSet 存储查询结果,提供取值能力

1.2 不足之处

在项目实战中,使用示例代码操作数据库,存在很多不足。

不足点 具体表现
代码冗余且重复 ① 每次操作需重复写加载驱动、获取连接、创建 Statement、关闭资源等模板代码;② 遍历 ResultSet 封装用户对象
SQL 与 Java 代码耦合 SQL 语句硬编码在 Java 代码中,修改 SQL 需重新编译 Java 代码。
手动处理参数 / 结果集 ① 参数需手动绑定,参数索引易出错;② 结果集需手动封装为对象,字段映射繁琐且易漏。
资源管理易出问题 用 try-with-resources,在 finally 手动关闭Connection/ResultSet
事务管理繁琐 需手动调用conn.setAutoCommit(false)/commit()/rollback(),多表操作时事务控制代码分散。
无缓存 / 性能优化能力 每次查询均直接访问数据库,无 SQL 缓存、结果缓存机制;分页、批量操作需手写 SQL
数据库适配性差 切换数据库时,需修改连接 URL、驱动类、SQL 语法(如分页、函数),代码改动量大。

直接使用 JDBC 相当于 "手动搬砖",需关注所有底层细节(资源、参数、SQL、事务),开发效率低、易出错、维护成本高。

因此,Mybatis出现了,它 封装了 JDBC 所有冗余且易出错的细节 ,既解决上述痛点,又避免了全自动化 ORM(如 Hibernate)对 SQL 的过度封装,是 Java 项目中数据库操作的最优解之一。

二 初识Mybatis

MyBatis 作为 JDBC 的轻量级封装框架,有以下核心价值:

  1. 消除模板代码,提升开发效率
  • MyBatis 封装了连接获取、资源关闭、Statement 创建等冗余逻辑,通过SqlSession统一管理操作入口,无需重复编写模板代码;
  • 内置结果集自动映射:MyBatis 可自动将 ResultSet 字段映射到实体类属性,无需手动getXxx()封装。
  1. SQL 与代码解耦,便于维护
  • SQL 集中写在 XML 映射文件或注解中,修改 SQL 无需编译 Java 代码;
  • 支持动态 SQL(<if>/<where>/<foreach>),解决 JDBC 中拼接 SQL 的痛点
  1. 参数 / 结果集自动处理,降低出错率
  • 参数绑定:支持按名称(如#{age})绑定,无需关注参数索引,避免setInt(1, 20)的索引错误;
  • 结果映射:支持一对一、一对多关联查询的自动映射(如用户关联订单),无需手动嵌套遍历 ResultSet。
  1. 内置防 SQL 注入机制
  • MyBatis 默认使用#{}参数占位符(底层封装 PreparedStatement),自动规避 SQL 注入;
  1. 自动化资源与事务管理
  • 集成连接池(如 HikariCP),自动管理 Connection 的创建 / 释放,避免连接泄漏;
  • 支持声明式事务(如 Spring+MyBatis 中@Transactional注解),无需手动调用commit()/rollback()
  1. 内置性能优化能力
  • 支持一级缓存(SqlSession 级别)、二级缓存(Mapper 级别),减少重复查询;
  • 提供分页插件(如 PageHelper),无需手写LIMIT语句,分页逻辑统一管理。
  1. 良好的数据库适配性
  • 通过配置不同的数据库方言(dialect),可快速切换数据库,SQL 语法差异由框架适配(如分页、主键生成策略)。

Mybatis的业务架构如下

来看一个使用示例

java 复制代码
import com.learn.more.entiry.User;
import java.io.IOException;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class JdbcDemo {
  public static void main(String[] args) throws IOException {
    Reader reader = null;
    SqlSession session = null;
    try {
      reader = Resources.getResourceAsReader("mybatis‐config.xml");
      SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
      session = sqlMapper.openSession();
      UserMapper mapper = session.getMapper(UserMapper.class);
      User user = mapper.selectById(1L);
      System.out.println(user.toString());
    } catch (Exception e) {
    } finally {
      if (reader != null) {
        reader.close();
      }
      if (session != null) {
        session.close();
      }
    }
  }
}

三 Mybatis启动流程

Mybatis启动过程,就是解析配置文件、初始化基础组件,最终创建SqlSession的过程。

3.1 解析主配置文件

使用Mybatis时,有两个配置文件;一个用于声明Mybatis运行参数,一个用于声明CRUD语句的mapper文件。

先来看Mybatis的主配置文件。如下,从中可以获取环境信息、Mapper文件列表、全局设置、类型别名、类型处理器、插件配置等。

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 resource="org/apache/ibatis/databases/blog/blog-derby.properties"/>

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

  <typeAliases>
    <typeAlias alias="Author" type="org.apache.ibatis.domain.blog.Author"/>
  </typeAliases>

  <typeHandlers>
    <typeHandler javaType="String" jdbcType="VARCHAR" handler="org.apache.ibatis.builder.CustomStringTypeHandler"/>
  </typeHandlers>

  <objectFactory />

  <plugins>
    <plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
      <property name="pluginProperty" value="100"/>
    </plugin>
  </plugins>

  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC">
        <property name="" value=""/>
      </transactionManager>
      <dataSource type="UNPOOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>

  <mappers>
    <mapper resource="org/apache/ibatis/builder/AuthorMapper.xml"/>
  </mappers>
</configuration>

源码中是如何处理上述配置的呢?如下代码:

java 复制代码
final String resource = "org/apache/ibatis/builder/MapperConfig.xml";
final Reader reader = Resources.getResourceAsReader(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);

来看SqlSessionFactoryBuilder#build方法,它会将XML中配置信息转化为Configuration对象。

使用XMLConfigBuilder#parseConfiguration方法,逐个处理XML标签,最终构建Configuration对象。处理XML文件时使用了org.w3c.dom、org.xml.sax等依赖包。

Configuration类持有所有配置信息,部分如下:

3.1.1 全局属性

对于一些全局属性,未配置时有默认值。在使用Mybatis时需要关注。

最后,使用Configuration对象创建一个SqlSessionFactory实例。

3.1.2 类型别名注册

TypeAliasRegistry类,用于保存简化名到Java类的映射;这使得我们可以在Mapper.xml中配置如resultType="int",而不用java.lang.Integer全类名。

创建TypeAliasRegistry时自行注册了常用Java类型的别名。

创建Configuration对象时,默认注册了mybatis框架自身需要的一些别名。这也是能配置如的原因。

3.1.3 类型处理器

TypeHandler用于定义javaType与jdbcType之间相会转换逻辑。

java 复制代码
private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = 
    new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = 
    new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();

比如要将MySQL中的TINYINT处理成Java中的Byte,将使用ByteTypeHandler。

3.2 解析Mapper文件

在主配置文件中,mapper文件有多种配置方式。

java 复制代码
<configuration>
  <mappers>
    <!-- 指定Mapper XML文件的相对路径 -->
    <mapper resource="com/example/mapper/UserMapper.xml"/>
    <!-- 接口类路径,要求接口与 XML 同路径同名 -->
    <mapper class="com.example.mapper.UserMapper"/>
    <mapper url="file:///D:/project/com/example/mapper/UserMapper.xml"/>
    <!-- 扫描指定包下的所有Mapper接口(需满足接口与XML同路径同名) -->
    <package name="com.example.mapper"/>
  </mappers>
</configuration>

在XMLConfigBuilder#mapperElement中,分别处理了以上几种配置方式:

源码中会通过XMLMapperBuilder,来完成Mapper文件解析。同时将Mapper.xml文件对应的接口,注册到Configuration。

已下面这个文件为例,是对User信息的CRUD。

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

    <select id="getUserById" parameterType="java.lang.Integer" resultType="com.example.pojo.User">
        SELECT id, username, age, create_time FROM user WHERE id = #{id}
    </select>

    <insert id="addUser" parameterType="com.example.pojo.User">
        INSERT INTO user (username, age, create_time)
        VALUES (#{username}, #{age}, #{createTime})
    </insert>

    <update id="updateUser" parameterType="com.example.pojo.User">
        UPDATE user 
        SET username = #{username}, 
            age = #{age}, 
            create_time = #{createTime}
        WHERE id = #{id}
    </update>

    <delete id="deleteUserById" parameterType="java.lang.Integer">
        DELETE FROM user WHERE id = #{id}
    </delete>
</mapper>

在XMLMapperBuilder#configurationElement中,解析了各个标签。

一个Mapper文件的解析结果,会按照用途被保存到Configuration中:

  • Configuration#mapperRegistry属性,记录着mapper接口与其代理工厂
  • 解析XML中的CRUD语句,封装为MappedStatement对象,保存在Configuration#mappedStatements属性中
  • 将resultMap保存在Configuration#resultMaps
  • 将parameterMap保存在Configuration#parameterMaps

3.3 创建SqlSession

经过以上步骤,创建了一个包含了MyBatis需要的所有配置Configration对象,接着会创建一个

SqlSessionFactory对象。

java 复制代码
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}

SqlSessionFactory接口有两个实现:SqlSessionManager、DefaultSqlSessionFactory。两者的差异在于:

DefaultSqlSessionFactory

  • 纯工厂模式:专注于创建SqlSession实例
  • 无状态设计:线程安全,可被多线程共享
  • 简单直接:每次调用openSession()都创建新的SqlSession

SqlSessionManager

  • 工厂+会话双重身份:既是SqlSessionFactory又是SqlSession
  • 会话管理:提供托管会话功能,支持ThreadLocal会话绑定
  • 代理模式:内部使用动态代理处理SqlSession操作

SqlSessionManager内部委托给DefaultSqlSessionFactory,本质上是对DefaultSqlSessionFactory的功能扩展,通过代理模式提供了更高级的会话管理能力:一个线程中始终使用同一个SqlSession。

Mybatis中默认使用DefaultSqlSessionFactory。通过以下步骤,将创建一个SqlSession对象。

相关推荐
消失的旧时光-19432 小时前
微服务的本质,其实是操作系统设计思想
java·大数据·微服务
Coder_Boy_2 小时前
基于SpringAI的智能平台基座开发-(四)
java·人工智能·spring boot·langchain·springai
码界奇点2 小时前
基于Spring Boot的内容管理系统框架设计与实现
java·spring boot·后端·车载系统·毕业设计·源代码管理
墨雪不会编程3 小时前
C++【string篇1遍历方式】:从零开始到熟悉使用string类
java·开发语言·c++
蒂法就是我3 小时前
有一张表,只有一个字段没有插入主建,能插入成功吗? 隐藏的 rowid除了在这里用到还在哪里用到了?
java
a努力。3 小时前
字节Java面试被问:系统限流的实现方式
java·开发语言·后端·面试·职场和发展·golang
独自破碎E3 小时前
Java中的Exception和Error有什么区别?
java·开发语言
小徐Chao努力3 小时前
【Langchain4j-Java AI开发】08-向量嵌入与向量数据库
java·数据库·人工智能
qq_377112373 小时前
从零开始深入理解并发、线程与等待通知机制
java·开发语言