MyBatis工作原理详解

文章目录

    • 1. MyBatis简介
      • 1.1 什么是MyBatis
      • 1.2 MyBatis解决的问题
      • 1.3 与JDBC、Hibernate的对比
      • 1.4 基本工作流程概览
    • 2. MyBatis核心组件
      • 2.1 SqlSessionFactoryBuilder
      • 2.2 SqlSessionFactory
      • 2.3 SqlSession
      • 2.4 Mapper接口
      • 2.5 Executor
      • 2.6 StatementHandler
      • 2.7 ParameterHandler
      • 2.8 ResultSetHandler
    • 3. MyBatis工作流程详解
      • 3.1 初始化阶段
        • 3.1.1 配置文件解析过程
        • 3.1.2 映射文件解析过程
      • 3.2 执行阶段
        • 3.2.1 Mapper代理对象的创建
        • 3.2.2 SQL语句的执行流程
        • 3.2.3 一次完整的查询示例分析
    • 4. 配置文件详解
      • 4.1 主配置文件
        • 4.1.1 properties元素
        • 4.1.2 settings元素
        • 4.1.3 typeAliases元素
        • 4.1.4 typeHandlers元素
        • 4.1.5 objectFactory元素
        • 4.1.6 plugins元素
        • 4.1.7 environments元素
        • 4.1.8 databaseIdProvider元素
        • 4.1.9 mappers元素
      • 4.2 映射文件
        • 4.2.1 mapper元素
        • 4.2.2 select元素
        • 4.2.3 insert、update、delete元素
        • 4.2.4 sql元素
        • 4.2.5 resultMap元素
        • 4.2.6 cache元素
    • 5. 参数处理机制
      • 5.1 #{}与${}的区别
      • 5.2 简单参数
      • 5.3 多个参数
      • 5.4 JavaBean参数
      • 5.5 Map参数
      • 5.6 数组和集合参数
      • 5.7 特殊参数处理
    • 6. 结果映射机制
      • 6.1 自动映射
      • 6.2 列名和属性名不匹配
      • 6.3 一对一关联
      • 6.4 一对多关联
      • 6.5 鉴别器映射
      • 6.6 延迟加载
    • 7. 动态SQL
      • 7.1 if元素
      • 7.2 choose, when, otherwise元素
      • 7.3 where元素
      • 7.4 set元素
      • 7.5 foreach元素
      • 7.6 bind元素
      • 7.7 trim元素
      • 7.8 sql元素和include元素
    • 8. 缓存机制
      • 8.1 一级缓存
      • 8.2 一级缓存的配置
      • 8.3 二级缓存
      • 8.4 二级缓存的配置
      • 8.5 自定义缓存
      • 8.6 缓存的使用场景与注意事项
    • 9. 插件机制
      • 9.1 拦截器接口
      • 9.2 @Intercepts注解
      • 9.3 可拦截的方法
      • 9.4 插件的配置
      • 9.5 插件的应用场景
    • 10. 与Spring集成
      • 10.1 依赖配置
      • 10.2 Spring配置
      • 10.3 Java配置
      • 10.4 事务管理
      • 10.5 Mapper注入
    • 11. 深入理解SqlSession
      • 11.1 SqlSession的创建
      • 11.2 SqlSession的方法
        • 查询方法
        • 更新方法
        • 事务方法
        • Mapper方法
      • 11.3 SqlSession的生命周期
      • 11.4 SqlSession与线程安全
    • 12. 常见问题与解决方案
      • 12.1 参数绑定问题
        • 问题:单个参数时无法识别参数名
        • 问题:多个参数时无法识别参数名
      • 12.2 结果映射问题
        • 问题:列名与属性名不一致
      • 12.3 性能问题
        • 问题:查询结果集过大
        • 问题:频繁创建SqlSession
      • 12.4 配置问题
        • 问题:配置文件路径错误
        • 问题:映射文件未注册
      • 12.5 动态SQL问题
        • 问题:动态SQL条件不生效
        • 问题:foreach元素使用错误
    • 13. 最佳实践
      • 13.1 项目结构
      • 13.2 命名规范
      • 13.3 使用接口绑定
      • 13.4 参数处理
      • 13.5 结果映射
      • 13.6 动态SQL
      • 13.7 缓存使用
      • 13.8 分页查询
      • 13.9 异常处理
      • 13.10 性能优化
      • 13.11 与Spring集成
      • 13.12 测试
    • 总结

1. MyBatis简介

1.1 什么是MyBatis

MyBatis是一个优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射。MyBatis几乎消除了JDBC代码和参数的手动设置以及结果集的检索。MyBatis使用简单的XML或注解进行配置,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射到数据库中的记录。

与其他ORM框架不同,MyBatis并没有将Java对象与数据库表关联起来,而是将方法与SQL语句关联。MyBatis让开发人员可以使用更自然的面向对象的方式来操作数据库。

1.2 MyBatis解决的问题

  1. 简化JDBC操作:MyBatis大大简化了JDBC操作,减少了手动编写JDBC代码的工作量。
  2. SQL与代码分离:MyBatis将SQL语句与Java代码分离,便于维护和管理。
  3. 参数映射:自动将Java对象映射到SQL语句的参数中。
  4. 结果映射:自动将SQL查询结果映射为Java对象。
  5. 支持动态SQL:可以根据不同的条件动态生成SQL语句。
  6. 缓存机制:提供一级缓存和二级缓存,提高查询效率。

1.3 与JDBC、Hibernate的对比

特性 JDBC MyBatis Hibernate
开发效率
灵活性
学习曲线 陡峭 平缓 较陡峭
SQL控制 完全手动 自定义 自动生成
性能 依赖优化
映射关系 SQL映射 对象-关系映射

1.4 基本工作流程概览

MyBatis的基本工作流程如下:

  1. 应用程序调用MyBatis的API访问数据库
  2. MyBatis根据配置文件创建SqlSessionFactory
  3. 通过SqlSessionFactory获取SqlSession实例
  4. SqlSession完成数据库操作
  5. 业务逻辑处理完成后关闭SqlSession
// 基本工作流程示例代码
// 1. 读取配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 构建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 打开SqlSession
SqlSession session = sqlSessionFactory.openSession();
try {
   
    // 4. 执行SQL语句
    User user = session.selectOne("org.mybatis.example.UserMapper.getUserById", 1);
    // 5. 处理结果
    System.out.println(user.getName());
} finally {
   
    // 6. 关闭SqlSession
    session.close();
}

2. MyBatis核心组件

MyBatis框架的核心组件构成了其工作原理的基础。每个组件都有其特定的功能和作用,它们协同工作,完成从Java对象到数据库操作的转换。

2.1 SqlSessionFactoryBuilder

SqlSessionFactoryBuilder是MyBatis的入口,用于构建SqlSessionFactory实例。它可以从XML配置文件或Configuration类构建SqlSessionFactory。

// 从XML文件创建SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

// 从Java配置类创建SqlSessionFactory
Configuration configuration = new Configuration();
// ... 配置属性和映射
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

SqlSessionFactoryBuilder的作用就是读取配置信息并创建SqlSessionFactory对象。一旦创建了SqlSessionFactory,就不再需要SqlSessionFactoryBuilder了,它可以被回收或销毁。

2.2 SqlSessionFactory

SqlSessionFactory是MyBatis的核心接口,它用于创建SqlSession实例。SqlSessionFactory的生命周期应该与应用程序的生命周期相同,通常情况下,我们只需要一个SqlSessionFactory实例。

// 创建默认的SqlSession
SqlSession session = sqlSessionFactory.openSession();

// 创建自动提交的SqlSession
SqlSession autoCommitSession = sqlSessionFactory.openSession(true);

// 创建指定事务隔离级别的SqlSession
SqlSession isolatedSession = sqlSessionFactory.openSession(TransactionIsolationLevel.READ_COMMITTED);

// 创建指定执行器类型的SqlSession
SqlSession batchSession = sqlSessionFactory.openSession(ExecutorType.BATCH);

SqlSessionFactory是线程安全的,可以被多个线程共享。它通常在应用程序启动时创建,并在应用程序结束时销毁。

2.3 SqlSession

SqlSession是MyBatis的主要接口,通过它可以执行SQL命令、获取映射器(Mapper)、管理事务。SqlSession是线程不安全的,每个线程应该有自己的SqlSession实例。

SqlSession session = sqlSessionFactory.openSession();
try {
   
    // 执行SQL语句
    List<User> users = session.selectList("org.mybatis.example.UserMapper.getAllUsers");
    
    // 插入数据
    User newUser = new User("Tom", "[email protected]");
    session.insert("org.mybatis.example.UserMapper.insertUser", newUser);
    
    // 更新数据
    User userToUpdate = session.selectOne("org.mybatis.example.UserMapper.getUserById", 1);
    userToUpdate.setName("Updated Name");
    session.update("org.mybatis.example.UserMapper.updateUser", userToUpdate);
    
    // 删除数据
    session.delete("org.mybatis.example.UserMapper.deleteUser", 2);
    
    // 提交事务
    session.commit();
} catch (Exception e) {
   
    // 回滚事务
    session.rollback();
    throw e;
} finally {
   
    // 关闭SqlSession
    session.close();
}

SqlSession的生命周期应该是请求作用域的,即一个请求一个SqlSession,请求结束后关闭SqlSession。

2.4 Mapper接口

Mapper接口是MyBatis中用于定义数据库操作的接口。MyBatis会为Mapper接口创建动态代理对象,通过代理对象执行SQL语句。

// 定义Mapper接口
public interface UserMapper {
   
    User getUserById(int id);
    List<User> getAllUsers();
    void insertUser(User user);
    void updateUser(User user);
    void deleteUser(int id);
}

// 使用Mapper接口
SqlSession session = sqlSessionFactory.openSession();
try {
   
    UserMapper userMapper = session.getMapper(UserMapper.class);
    
    // 查询
    User user = userMapper.getUserById(1);
    List<User> allUsers = userMapper.getAllUsers();
    
    // 插入
    User newUser = new User("Tom", "[email protected]");
    userMapper.insertUser(newUser);
    
    // 更新
    user.setName("Updated Name");
    userMapper.updateUser(user);
    
    // 删除
    userMapper.deleteUser(2);
    
    session.commit();
} finally {
   
    session.close();
}

Mapper接口的好处是可以使用Java接口和方法,而不必直接使用字符串调用SQL语句,这样可以提供编译时类型检查,减少运行时错误。

2.5 Executor

Executor是MyBatis的核心接口之一,负责执行SQL语句。MyBatis有三种内置的Executor类型:

  1. SIMPLE:默认的Executor,每执行一次更新操作(update、insert、delete)就提交一次事务。
  2. REUSE:重用预处理语句(PreparedStatement)。
  3. BATCH:批量执行所有更新语句。
// 在配置文件中设置默认的Executor类型
<settings>
  <setting name="defaultExecutorType" value="REUSE" />
</settings>

// 在代码中指定Executor类型
SqlSession batchSession = sqlSessionFactory.openSession(ExecutorType.BATCH);

虽然Executor是MyBatis内部的接口,但了解它的作用有助于理解MyBatis的工作原理和优化查询性能。

2.6 StatementHandler

StatementHandler负责处理JDBC Statement的创建、设置参数、执行SQL语句以及获取结果。

// MyBatis内部的StatementHandler接口
public interface StatementHandler {
   
  Statement prepare(Connection connection, Integer transactionTimeout);
  void parameterize(Statement statement);
  void batch(Statement statement);
  int update(Statement statement);
  <E> List<E> query(Statement statement, ResultHandler resultHandler);
  BoundSql getBoundSql();
  ParameterHandler getParameterHandler();
}

StatementHandler将Java对象转换为JDBC可以执行的Statement对象,它处理了从Java参数到SQL参数的转换,以及从SQL结果到Java对象的转换。

2.7 ParameterHandler

ParameterHandler负责将用户传入的参数转换为JDBC Statement中需要的参数。

// MyBatis内部的ParameterHandler接口
public interface ParameterHandler {
   
  Object getParameterObject();
  void setParameters(PreparedStatement ps);
}

ParameterHandler处理SQL语句中的占位符(?)和实际参数值之间的映射关系,它将Java对象的属性值设置到JDBC PreparedStatement中。

2.8 ResultSetHandler

ResultSetHandler负责将JDBC返回的ResultSet结果集转换为Java对象。

// MyBatis内部的ResultSetHandler接口
public interface ResultSetHandler {
   
  <E> List<E> handleResultSets(Statement stmt);
  <E> Cursor<E> handleCursorResultSets(Statement stmt);
  void handleOutputParameters(CallableStatement cs);
}

ResultSetHandler将数据库查询结果映射为Java对象,它处理了从数据库字段到Java对象属性的转换。

3. MyBatis工作流程详解

MyBatis的工作流程可以分为初始化阶段和执行阶段两部分。

3.1 初始化阶段

初始化阶段主要完成以下工作:

  1. 解析配置文件:MyBatis首先解析mybatis-config.xml配置文件,读取全局配置和映射文件位置。
  2. 解析映射文件:解析所有Mapper.xml映射文件,建立SQL语句和对应方法的映射关系。
  3. 创建会话工厂:创建SqlSessionFactory实例,准备生成SqlSession。
// 初始化阶段示例代码
// 1. 读取配置文件
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);

// 2. 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

// 3. 创建SqlSessionFactory对象
SqlSessionFactory factory = builder.build(inputStream);

// 至此,初始化阶段完成

在初始化阶段,MyBatis会解析所有XML配置文件,构建内部的Configuration对象,该对象包含了MyBatis运行所需的所有配置信息。

3.1.1 配置文件解析过程

MyBatis会按照以下顺序解析配置文件中的元素:

  1. properties:读取属性配置文件。
  2. settings:全局参数设置。
  3. typeAliases:类型别名。
  4. typeHandlers:类型处理器。
  5. objectFactory:对象工厂。
  6. plugins:插件。
  7. environments:环境配置(数据源、事务管理器)。
  8. databaseIdProvider:数据库厂商标识。
  9. mappers:映射器。


DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  
  <properties resource="org/mybatis/example/config.properties">
    <property name="username" value="dev_user"/>
    <property name="password" value="F2Fa3!33TYyg"/>
  properties>
  
  
  <settings>
    <setting name="cacheEnabled" value="true"/>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
  settings>
  
  
  <typeAliases>
    <typeAlias alias="User" type="org.mybatis.example.User"/>
    <package name="org.mybatis.example"/>
  typeAliases>
  
  
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <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/mybatis/example/UserMapper.xml"/>
    <package name="org.mybatis.example"/>
  mappers>
configuration>
3.1.2 映射文件解析过程

映射文件(Mapper.xml)定义了SQL语句和参数映射、结果映射等。MyBatis会解析这些文件,构建内存中的映射关系。



DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.UserMapper">
  <select id="getUserById" resultType="User">
    SELECT * FROM users WHERE id = #{id}
  select>
  
  <select id="getAllUsers" resultType="User">
    SELECT * FROM users
  select>
  
  <insert id="insertUser" parameterType="User">
    INSERT INTO users (name, email) VALUES (#{name}, #{email})
  insert>
  
  <update id="updateUser" parameterType="User">
    UPDATE users SET name = #{name}, email = #{email} WHERE id = #{id}
  update>
  
  <delete id="deleteUser" parameterType="int">
    DELETE FROM users WHERE id = #{id}
  delete>
mapper>

解析过程中,MyBatis会将每个SQL语句解析为一个MappedStatement对象,该对象包含了SQL语句的id、参数映射、结果映射、SQL语句类型等信息。

3.2 执行阶段

执行阶段主要完成以下工作:

  1. 创建会话:从SqlSessionFactory获取SqlSession。
  2. 绑定映射器:获取Mapper接口的代理对象。
  3. 执行SQL:调用Mapper方法,执行对应的SQL语句。
  4. 处理结果:将查询结果映射为Java对象。
  5. 释放资源:关闭SqlSession。
// 执行阶段示例代码
// 1. 从SqlSessionFactory获取SqlSession
SqlSession session = factory.openSession();

try {
   
    // 2. 获取Mapper接口代理对象
    UserMapper userMapper = session.getMapper(UserMapper.class);
    
    // 3. 执行SQL语句
    User user = userMapper.getUserById(1);
    
    // 4. 处理结果
    System.out.println("User found: " + user.getName());
    
    // 执行更新操作
    user.setName("New Name");
    userMapper.updateUser(user);
    
    // 提交事务
    session.commit();
} catch (Exception e) {
   
    // 回滚事务
    session.rollback();
} finally {
   
    // 5. 关闭SqlSession
    session.close();
}
3.2.1 Mapper代理对象的创建

当我们调用session.getMapper(UserMapper.class)时,MyBatis会为UserMapper接口创建一个动态代理对象。代理对象会拦截接口方法的调用,转而执行对应的SQL语句。

// MyBatis内部为Mapper创建代理对象的示意代码
public class MapperProxy<T> implements InvocationHandler {
   
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache;
    
    // 构造方法等省略...
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   
        // 如果是Object类方法,直接调用
        if (Object.class.equals(method.getDeclaringClass())) {
   
            return method.invoke(this, args);
        }
        
        // 从缓存中获取MapperMethod
        MapperMethod mapperMethod = methodCache.computeIfAbsent(method,
            k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        
        // 执行SQL语句
        return mapperMethod.execute(sqlSession, args);
    }
}
3.2.2 SQL语句的执行流程

当调用Mapper接口的方法时,MyBatis会执行以下步骤:

  1. 获取MappedStatement:根据方法的完全限定名找到对应的MappedStatement。
  2. 创建Executor:根据配置创建Executor实例。
  3. 创建StatementHandler:根据SQL类型创建StatementHandler实例。
  4. 创建ParameterHandler:负责处理参数映射。
  5. 创建ResultSetHandler:负责处理结果集映射。
  6. 执行SQL:调用JDBC API执行SQL语句。
  7. 处理结果:将ResultSet映射为Java对象。
// 以查询为例,MyBatis内部执行SQL的简化流程

// 1. 从Configuration获取MappedStatement
MappedStatement ms = configuration.getMappedStatement("org.mybatis.example.UserMapper.getUserById");

// 2. 创建执行器
Executor executor = configuration.newExecutor(transaction, executorType);

// 3. 创建StatementHandler
StatementHandler handler = configuration.newStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);

// 4. 准备Statement
Statement stmt = handler.prepare(connection, transactionTimeout);

// 5. 设置参数
handler.parameterize(stmt);

// 6. 执行查询
List<User> users = handler.query(stmt, resultHandler);

// 7. 关闭Statement
stmt.close();
3.2.3 一次完整的查询示例分析

以下是一个完整的查询操作示例,从调用Mapper方法到返回结果的全过程:

// 应用代码
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.getUserById(1);

// 内部执行流程:
// 1. 获取MapperProxy代理对象
// 2. 调用MapperProxy.invoke方法
// 3. 获取MapperMethod对象
// 4. 调用MapperMethod.execute方法
// 5. 根据方法类型(SELECT)调用SqlSession.selectOne方法
// 6. SqlSession委托给Executor执行查询
// 7. Executor创建StatementHandler
// 8. StatementHandler创建Statement并设置参数
// 9. StatementHandler执行查询并返回结果集
// 10. ResultSetHandler将结果集映射为User对象
// 11. 返回User对象给应用代码

这个流程展示了MyBatis如何将一个简单的接口方法调用转换为复杂的数据库操作,同时隐藏了底层的JDBC细节。

4. 配置文件详解

MyBatis的配置文件是其工作的基础,主要包括主配置文件(mybatis-config.xml)和映射文件(Mapper.xml)。

4.1 主配置文件

mybatis-config.xml是MyBatis的全局配置文件,它定义了MyBatis的行为方式。

4.1.1 properties元素

properties元素用于定义属性,这些属性可以在整个配置文件中被引用。

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
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>

properties元素可以从外部文件加载属性,也可以直接在配置文件中定义属性。

4.1.2 settings元素

settings元素用于更改MyBatis的运行时行为。

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" va

你可能感兴趣的:(mybatis,数据库)