科普文:深入理解Mybatis

概叙

        
(1) JDBC

        JDBC(Java Data Base Connection,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成.JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。

        ·优点:运行期:快捷、高效

        ·缺点:编辑期:代码量大、繁琐异常处理、不支持数据库跨平台

(2) DBUtils(相当于C#中的DBHelper类)

        DBUtils是Java编程中的数据库操作实用工具,小巧简单实用。DBUtils封装了对JDBC的操作,简化了JDBC操作,可以少写代码。

        DBUtils三个核心功能介绍

  1.         QueryRunner中提供对sql语句操作的API
  2.         ResultSetHandler接口,用于定义select操作后,怎样封装结果集
  3.         DBUtils类,它就是一个工具类,定义了关闭资源与事务处理的方法

(3)Hibernate

        Hibernate 是由 Gavin King 于 2001 年创建的开放源代码的对象关系框架。它强大且高效的构建具有关系对象持久性和查询服 务的 Java 应用程序。

        Hibernate 将 Java 类映射到数据库表中,从 Java 数据类型中映射到 SQL 数据类型中,并把开发人员从 95% 的公共数据持续 性编程工作中解放出来。

        Hibernate 是传统 Java 对象和数据库服务器之间的桥梁,用来处理基于 O/R 映射机制和模式的那些对象。

Hibernate 优势

  1. Hibernate 使用 XML 文件来处理映射 Java 类别到数据库表格中,并且不用编写任何代码
  2. 为在数据库中直接储存和检索 Java 对象提供简单的 APIs。
  3. 如果在数据库中或任何其它表格中出现变化,那么仅需要改变 XML 文件属性。
  4. 抽象不熟悉的 SQL 类型,并为我们提供工作中所熟悉的 Java 对象。
  5. Hibernate 不需要应用程序服务器来操作。
  6. 操控你数据库中对象复杂的关联。
  7. 最小化与访问数据库的智能提取策略。
  8. 提供简单的数据询问。

Hibernate劣势

  1. hibernate的完全封装导致无法使用数据的一些功能。
  2. Hibernate的缓存问题。
  3. Hibernate对于代码的耦合度太高。
  4. Hibernate寻找bug困难。
  5. Hibernate批量数据操作需要大量的内存空间而且执行过程中需要的对象太多

(4) JDBCTemplate

        JdbcTemplate针对数据查询提供了多个重载的模板方法,你可以根据需要选用不同的模板方法.如果你的查询很简单,仅仅是传入相应SQL或者相关参数,然后取得一个单一的结果,那么你可以选择如下一组便利的模板方法。

        优点:运行期:高效、内嵌Spring框架中、支持基于AOP的声明式事务

         缺点:必须于Spring框架结合在一起使用、不支持数据库跨平台、默认没有缓存

(5) MyBatis

        MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。

        1、Mybatis是一个半ORM(对象关系映射)框架,底层封装了JDBC,是程序员在开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。使得程序员可以花更多的精力放到业务开发中。另外,程序员直接编写原生态sql,严格控制sql执行性能,灵活度高。

        2、MyBatis 可以使用简单的 XML文件 或注解方式来配置和映射原生信息,将 POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。

        3、通过xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过java对象和 statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。(从执行sql到返回result的过程)。

Mybaits的优点:

        1、基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。

        2、与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;

        3、很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)。

        4、能够与Spring很好的集成;

        5、提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。

MyBatis框架的缺点:

        1、SQL语句编写工作量大,熟练度要高:SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。

        2、SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。

MyBatis框架适用场合:

        1、MyBatis专注于SQL本身,是一个足够灵活的DAO层解决方案。

        2、对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis将是不错的选择。

MyBatis与Hibernate有哪些不同?

        1、Mybatis是一个半自动的ORM框架,在查询关联对象或关联集合对象时,需要手动编写sql语句来完成;Hibernate是全自动ORM映射工具,查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,不需要编写sql.

        2、Mybatis直接编写原生态sql,可以严格控制sql执行性能,灵活度高,非常适合对性能要求高,需求变化频繁的项目;但是如果涉及到较多的字段或者关联多表时,sql语句编写量大且对开发人的sql语句编写功底要求高。

        3、Hibernate对象/关系映射能力强,数据库无关性好,适合需求变化不大的项目,使用hibernate开发可以节省很多代码,提高效率。

MyBatis 的核心组件

1、SqlSessionFactoryBuilder:

  • SqlSessionFactoryBuilder 负责解析配置文件并构建 SqlSessionFactory。

  • 它通常使用 XML 配置文件(mybatis-config.xml)作为输入。

  • 在解析过程中,它会创建 Configuration 对象,该对象包含 MyBatis 的所有配置信息。

  • 解析完成后,它会调用 SqlSessionFactoryBuilder 的 build 方法来创建 SqlSessionFactory 实例。

SqlSessionFactoryBuilder 是 MyBatis 中用于构建 SqlSessionFactory 的类。它主要负责解析 MyBatis 的配置文件,并基于配置信息构建 SqlSessionFactory。由于 MyBatis 的源代码文件通常较长,V哥尽量简化并只列出与 SqlSessionFactoryBuilder 相关的关键代码段,并加上注释。

以下是 SqlSessionFactoryBuilder 的代码简化版本:

import org.apache.ibatis.builder.xml.XMLConfigBuilder;  
import org.apache.ibatis.session.SqlSessionFactory;  
import org.apache.ibatis.session.SqlSessionFactoryBuilder;  
  
import java.io.InputStream;  
import java.io.Reader;  
  
public class SqlSessionFactoryBuilder {  
  
    // 使用XML配置文件构建SqlSessionFactory  
    public SqlSessionFactory build(Reader reader) {  
        return build(reader, null, null);  
    }  
  
    // 使用XML配置文件构建SqlSessionFactory,并允许传入Environment和Properties  
    public SqlSessionFactory build(Reader reader, String environment, Properties properties) {  
        try {  
            // 使用XML配置构建器创建Configuration对象  
            XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);  
            // 解析配置文件,返回Configuration对象  
            Configuration configuration = parser.parse();  
            // 基于Configuration对象创建SqlSessionFactory  
            return new SqlSessionFactoryBuilder.SqlSessionFactoryImpl(configuration);  
        } catch (Exception e) {  
            throw ExceptionFactory.wrapException("Error building SqlSession.", e);  
        } finally {  
            // 关闭读取器  
            ErrorContext.instance().reset();  
            try {  
                reader.close();  
            } catch (IOException e) {  
                // 忽略关闭读取器时可能抛出的异常  
            }  
        }  
    }  
  
    // 使用InputStream构建SqlSessionFactory  
    public SqlSessionFactory build(InputStream inputStream) {  
        return build(inputStream, null, null);  
    }  
  
    // 使用InputStream构建SqlSessionFactory,并允许传入Environment和Properties  
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {  
        try {  
            // 使用XML配置构建器创建Configuration对象  
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);  
            // 解析配置文件,返回Configuration对象  
            Configuration configuration = parser.parse();  
            // 基于Configuration对象创建SqlSessionFactory  
            return new SqlSessionFactoryBuilder.SqlSessionFactoryImpl(configuration);  
        } catch (Exception e) {  
            throw ExceptionFactory.wrapException("Error building SqlSession.", e);  
        } finally {  
            // 关闭输入流  
            ErrorContext.instance().reset();  
            try {  
                inputStream.close();  
            } catch (IOException e) {  
                // 忽略关闭输入流时可能抛出的异常  
            }  
        }  
    }  
  
    // SqlSessionFactoryImpl是SqlSessionFactory的默认实现  
    private static class SqlSessionFactoryImpl implements SqlSessionFactory {  
        private final Configuration configuration;  
  
        private SqlSessionFactoryImpl(Configuration configuration) {  
            this.configuration = configuration;  
        }  
  
        // ... 其他方法实现,例如openSession等  
    }  
}

解释: 

1、SqlSessionFactoryBuilder 类提供了几个重载的 build 方法,这些方法接收不同的参数(如 Reader或 InputStream),用于读取 MyBatis 的配置文件。

2、在每个 build 方法中,首先创建了一个 XMLConfigBuilder 对象,这个对象负责解析 MyBatis 的 XML 配置文件。

3、XMLConfigBuilder 的 parse 方法被调用,它会读取配置文件并构建 Configuration 对象,该对象包含了 MyBatis 的所有配置信息。

4、构建完 Configuration 对象后,使用它创建 SqlSessionFactory 的默认实现 SqlSessionFactoryImpl 的实例。

5、如果在解析配置文件或创建 SqlSessionFactory 的过程中发生异常,会捕获异常并包装为 MyBatis 自定义的异常类型。

6、在方法执行完毕后,无论是否发生异常,都会尝试关闭 Reader 或 InputStream 以释放资源。

7SqlSessionFactoryImpl 是 SqlSessionFactory 接口的一个默认实现,它内部持有 Configuration 对象,并提供了如 openSession 等方法用于创建 SqlSession

2、SqlSessionFactory:

  • SqlSessionFactory 是创建 SqlSession 的工厂类。

  • 它内部持有一个 Configuration 对象,该对象包含了 MyBatis 的所有配置信息。

  • 当调用 openSession 方法时,它会根据配置信息创建一个新的 SqlSession 实例。

SqlSessionFactory 在 MyBatis 中是一个核心接口,用于生产 SqlSession 对象。通常情况下,我们不会直接实现这个接口,而是使用 SqlSessionFactoryBuilder 来构建它的一个实现类实例。但是,为了解释 SqlSessionFactory的作用,V哥先展示一个简化的 SqlSessionFactory 接口和其可能的一个实现类的代码。

首先是 SqlSessionFactory 接口的简化版本:

import org.apache.ibatis.session.SqlSession;  
  
public interface SqlSessionFactory {  
  
    /**  
     * 打开一个新的SqlSession。  
     *  
     * @return 新的SqlSession实例  
     * @throws Exception 如果打开SqlSession时出错  
     */  
    SqlSession openSession();  
  
    /**  
     * 打开一个新的SqlSession,并允许传入执行器类型。  
     *  
     * @param executorType 执行器类型  
     * @return 新的SqlSession实例  
     * @throws Exception 如果打开SqlSession时出错  
     */  
    SqlSession openSession(ExecutorType executorType);  
  
    /**  
     * 打开一个新的SqlSession,并允许传入执行器类型和自动提交参数。  
     *  
     * @param executorType 执行器类型  
     * @param autoCommit 是否自动提交  
     * @return 新的SqlSession实例  
     * @throws Exception 如果打开SqlSession时出错  
     */  
    SqlSession openSession(ExecutorType executorType, boolean autoCommit);  
  
    /**  
     * 打开一个新的SqlSession,并允许传入配置属性。  
     *  
     * @param properties 配置属性  
     * @return 新的SqlSession实例  
     * @throws Exception 如果打开SqlSession时出错  
     */  
    SqlSession openSession(Properties properties);  
  
    /**  
     * 打开一个新的SqlSession,并允许传入执行器类型、自动提交参数和配置属性。  
     *  
     * @param executorType 执行器类型  
     * @param autoCommit 是否自动提交  
     * @param properties 配置属性  
     * @return 新的SqlSession实例  
     * @throws Exception 如果打开SqlSession时出错  
     */  
    SqlSession openSession(ExecutorType executorType, boolean autoCommit, Properties properties);  
  
    // ... 可能还有其他方法,如关闭SqlSessionFactory等  
}

接下来是一个可能的 SqlSessionFactory 实现类的简化版本(注意:MyBatis 并没有直接提供一个名为 SqlSessionFactoryImpl 的类, V 哥这里只是为了演示):

import org.apache.ibatis.executor.Executor;  
import org.apache.ibatis.executor.ExecutorType;  
import org.apache.ibatis.session.Configuration;  
import org.apache.ibatis.session.SqlSession;  
import org.apache.ibatis.session.SqlSessionFactory;  
  
import java.util.Properties;  
  
public class SqlSessionFactoryImpl implements SqlSessionFactory {  
  
    private final Configuration configuration;  
  
    public SqlSessionFactoryImpl(Configuration configuration) {  
        this.configuration = configuration;  
    }  
  
    @Override  
    public SqlSession openSession() {  
        return openSession(ExecutorType.SIMPLE);  
    }  
  
    @Override  
    public SqlSession openSession(ExecutorType executorType) {  
        return openSession(executorType, false);  
    }  
  
    @Override  
    public SqlSession openSession(ExecutorType executorType, boolean autoCommit) {  
        return openSession(executorType, autoCommit, null);  
    }  
  
    @Override  
    public SqlSession openSession(Properties properties) {  
        return openSession(ExecutorType.SIMPLE, properties);  
    }  
  
    @Override  
    public SqlSession openSession(ExecutorType executorType, boolean autoCommit, Properties properties) {  
        // 创建Executor实例  
        Executor executor = configuration.newExecutor(executorType, autoCommit);  
        // 使用Configuration和Executor创建SqlSession  
        return new DefaultSqlSession(configuration, executor);  
    }  
  
    // ... 其他方法实现,如关闭SqlSessionFactory等  
}

解释:

1、SqlSessionFactory 接口定义了如何打开一个或多个 SqlSessionSqlSession 是 MyBatis 的核心接口,它提供了执行 SQL 语句和获取映射结果的方法。

2、SqlSessionFactoryImpl 类是 SqlSessionFactory 接口的一个可能实现。在实际应用中,MyBatis 使用了不同的实现类,但原理类似。

3、SqlSessionFactoryImpl 的构造函数接收一个 Configuration 对象,该对象包含了 MyBatis 的所有配置信息,如环境设置、类型别名、映射文件等。

4、openSession 方法有多个重载版本,允许用户指定执行器类型、是否自动提交事务以及配置属性来打开 SqlSession。这些重载方法最终都会调用一个或多个带有所有参数的 openSession 方法,以便在打开 SqlSession 时应用所有必要的配置。

5、在 openSession 方法中,根据传入的执行器类型 (ExecutorType) 和是否自动提交 (autoCommit) 的参数,调用 Configuration 对象的 newExecutor 方法来创建一个新的执行器 (Executor) 实例。执行器负责管理和执行 SQL语句。

6、使用 Configuration 和 Executor 实例来创建一个新的 SqlSession 实例。这个 SqlSession 实例会用于执行 SQL 语句、获取映射结果以及管理数据库事务。

7、在实际应用中,SqlSessionFactory 通常通过 SqlSessionFactoryBuilder 构建。SqlSessionFactoryBuilder会读取 MyBatis 的配置文件(通常是 XML 格式),解析配置信息,并创建一个 Configuration 对象。然后,使用这个 Configuration 对象来创建一个 SqlSessionFactory 实例。

8、SqlSessionFactory 是线程安全的,一旦创建,就可以在整个应用程序中重用。通常,每个应用程序只需要一个 SqlSessionFactory 实例。

9、SqlSession 则是非线程安全的,因此不应该在多个线程之间共享。每个线程应该有自己的 SqlSession 实例。使用完 SqlSession 后,应该调用其 close 方法来释放资源。

10、SqlSessionFactory 和 SqlSession 的设计符合了工厂模式和单例模式的思想。SqlSessionFactory 负责生产 SqlSession,而 SqlSession 则负责执行具体的数据库操作。

上面的代码示例是一个简化的版本,用于解释 SqlSessionFactory 和其实现类的基本概念和工作原理。

3、SqlSession:

  • SqlSession 是执行 SQL 的核心接口。

  • 它通过 Executor 来执行 SQL 语句。

  • 当调用 selectOneselectListinsertupdatedelete 等方法时,实际上会调用 Executor 的相应方法。

  • SqlSession 也负责事务的管理,例如提交或回滚事务。

当涉及到 SqlSession 的源代码时,实际上 MyBatis 框架的源代码包含了多个与 SqlSession 相关的类,例如 DefaultSqlSession,这是 SqlSession 接口的一个常见实现。以下是一个简化的 DefaultSqlSession 类的示例,V 哥会在代码中加入中文注释来解释它的作用和功能:

import org.apache.ibatis.executor.Executor;  
import org.apache.ibatis.executor.statement.StatementHandler;  
import org.apache.ibatis.mapping.MappedStatement;  
import org.apache.ibatis.session.Configuration;  
import org.apache.ibatis.session.ResultHandler;  
import org.apache.ibatis.session.RowBounds;  
import org.apache.ibatis.session.SqlSession;  
  
import java.util.List;  
import java.util.Map;  
  
public class DefaultSqlSession implements SqlSession {  
  
    private final Configuration configuration;  
    private final Executor executor;  
  
    public DefaultSqlSession(Configuration configuration, Executor executor) {  
        this.configuration = configuration;  
        this.executor = executor;  
    }  
  
    @Override  
    public <T> T selectOne(String statement, Object parameter) {  
        // 根据statement和parameter获取MappedStatement  
        MappedStatement ms = configuration.getMappedStatement(statement);  
        // 创建StatementHandler  
        StatementHandler statementHandler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);  
        // 使用Executor执行查询,并返回结果  
        return executor.query(ms, statementHandler);  
    }  
  
    @Override  
    public <E> List<E> selectList(String statement, Object parameter) {  
        // 类似selectOne,但返回结果是List  
        MappedStatement ms = configuration.getMappedStatement(statement);  
        StatementHandler statementHandler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);  
        return executor.query(ms, statementHandler, RowBounds.DEFAULT, ResultHandler.DEFAULT_RESULT_HANDLER);  
    }  
  
    @Override  
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {  
        // 与上一个selectList方法类似,但允许传入RowBounds以进行分页查询  
        MappedStatement ms = configuration.getMappedStatement(statement);  
        StatementHandler statementHandler = configuration.newStatementHandler(this, ms, parameter, rowBounds, null, null);  
        return executor.query(ms, statementHandler, rowBounds, ResultHandler.DEFAULT_RESULT_HANDLER);  
    }  
  
    @Override  
    public void select(String statement, Object parameter, ResultHandler resultHandler) {  
        // 执行查询,并将结果传递给ResultHandler进行处理  
        MappedStatement ms = configuration.getMappedStatement(statement);  
        StatementHandler statementHandler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);  
        executor.query(ms, statementHandler, RowBounds.DEFAULT, resultHandler);  
    }  
  
    @Override  
    public int insert(String statement, Object parameter) {  
        // 执行插入操作,并返回影响的记录数  
        MappedStatement ms = configuration.getMappedStatement(statement);  
        StatementHandler statementHandler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);  
        return executor.update(ms, statementHandler);  
    }  
  
    @Override  
    public int update(String statement, Object parameter) {  
        // 执行更新操作,并返回影响的记录数  
        MappedStatement ms = configuration.getMappedStatement(statement);  
        StatementHandler statementHandler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);  
        return executor.update(ms, statementHandler);  
    }  
  
    @Override  
    public int delete(String statement, Object parameter) {  
        // 执行删除操作,并返回影响的记录数  
        MappedStatement ms = configuration.getMappedStatement(statement);  
        StatementHandler statementHandler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);  
        return executor.update(ms, statementHandler);  
    }  
  
    @Override  
    public <T> T getMapper(Class<T> type) {  
        // 获取Mapper接口的代理实现  
        return configuration.getMapper(type, this);  
    }  
  
    // ... 可能还有其他方法,如提交事务、回滚事务、关闭SqlSession等  
  
    @Override  
    public void close() {  
        // 清理资源,
        // 关闭SqlSession
    }
    // ... 省略其他可能的方法和细节

上面的代码片段是 DefaultSqlSession 的简化版本,用于解释 SqlSession 的一些基本操作。下面 V 哥将对关键部分进行解释:

  • 构造器:

    • DefaultSqlSession 的构造器接受一个 Configuration 对象和一个 Executor 对象。Configuration 对象包含了 MyBatis 的所有配置信息,而 Executor 对象则负责执行 SQL 语句。

  • 查询方法:

    • selectOneselectListselect 等方法用于执行查询操作。它们首先从 Configuration 中获取与提供的 SQL 语句标识符对应的 MappedStatement,然后创建一个 StatementHandler 来处理 SQL 语句的生成和参数绑定。最后,它们使用 Executor 来执行查询并返回结果。

  • 增删改方法:

    • insertupdatedelete 等方法用于执行插入、更新和删除操作。它们与查询方法类似,但返回的是受影响的记录数。

  • 获取Mapper:

    • getMapper 方法用于获取一个 Mapper 接口的代理实现。这允许你直接使用接口调用方法而无需手动创建和配置代理。

  • 关闭SqlSession:

    • close 方法用于关闭 SqlSession,释放相关资源。

        需要注意的是,SqlSession 是线程不安全的,因此通常每个线程都应该有自己的 SqlSession 实例。同时,SqlSession 的使用通常遵循“打开-执行-关闭”的模式,以确保资源的正确释放。

        在实际应用中,你通常不会直接创建 DefaultSqlSession 的实例,而是使用 SqlSessionFactory 来创建 SqlSessionSqlSessionFactory 负责根据配置创建 SqlSession 实例,并管理相关的资源。

        希望这些注释和解释能够帮助你理解 SqlSession 的作用和工作原理。如果需要更深入的理解,建议阅读 MyBatis 的官方文档和源代码。

4、Mapper 接口及其实现:

  • Mapper 接口是开发者定义的,用于描述数据库操作。

  • MyBatis 使用 JDK 动态代理为 Mapper 接口创建代理对象。

  • 当调用 Mapper 接口的方法时,代理对象会拦截调用,并转换为 SQL 语句的执行。

  • 这个转换过程涉及 MapperStatement 的查找和解析,以及参数和结果的映射。

        在 MyBatis 中,Mapper 接口通常没有直接的实现类,而是通过 MyBatis 的动态代理机制自动生成代理对象。Mapper接口定义了与数据库操作相关的方法,而 MyBatis 会根据这些方法自动生成相应的 SQL 语句并执行。

        下面是一个简单的 Mapper 接口示例及注释:

// 定义一个 Mapper 接口,用于映射数据库操作  
public interface UserMapper {  
  
    // 根据 ID 查询用户信息  
    // @Select 注解用于指定查询的 SQL 语句  
    // #{id} 是参数占位符,表示方法参数  
    @Select("SELECT * FROM user WHERE id = #{id}")  
    User selectUserById(int id);  
  
    // 插入用户信息  
    // @Insert 注解用于指定插入的 SQL 语句  
    // 使用 @Options 注解可以配置插入操作的一些选项,比如是否使用生成的键等  
    @Insert("INSERT INTO user (name, age) VALUES (#{name}, #{age})")  
    @Options(useGeneratedKeys = true, keyProperty = "id")  
    int insertUser(User user);  
  
    // 更新用户信息  
    // @Update 注解用于指定更新的 SQL 语句  
    @Update("UPDATE user SET name = #{name}, age = #{age} WHERE id = #{id}")  
    int updateUser(User user);  
  
    // 删除用户信息  
    // @Delete 注解用于指定删除的 SQL 语句  
    @Delete("DELETE FROM user WHERE id = #{id}")  
    int deleteUser(int id);  
  
    // 查询所有用户信息  
    // @Select 注解指定查询所有用户的 SQL 语句  
    @Select("SELECT * FROM user")  
    List<User> selectAllUsers();  
  
    // 其他的数据库操作方法...  
}

解释:

        接口定义: UserMapper 是一个接口,它定义了与 user 表相关的数据库操作。 注解: MyBatis 提供了注解(如 @Select@Insert@Update@Delete)来简化 SQL 语句的编写。这些注解允许你在接口方法上直接指定 SQL 语句。

        参数占位符: 在 SQL 语句中,#{id}、#{name}、#{age} 等是参数占位符,它们会在运行时被方法参数的实际值替换。

        自动映射: MyBatis 会自动将查询结果映射到 User 类型的对象上,前提是你的 User 类的属性名称和数据库表的列名能够对应上。

        动态代理: 当你在 MyBatis 的 SqlSession 中调用 getMapper(UserMapper.class) 时,MyBatis 会根据 UserMapper 接口动态生成一个代理对象。这个代理对象会在运行时拦截方法调用,并自动执行相应的 SQL 语句。

        选项配置: @Options 注解用于配置 SQL 语句执行的一些选项。例如,在插入操作中,useGeneratedKeys = true 表示使用数据库自动生成的主键,keyProperty = "id" 指定将生成的主键设置到 User 对象的 id 属性上。

        返回类型: 方法的返回类型通常与 SQL 语句的执行结果相对应。例如,查询单个用户返回 User 对象,查询多个用户返回 List<User>

        在实际应用中,你通常不需要手动编写 Mapper 接口的实现类。你只需要定义接口,并在 XML 映射文件(如果不使用注解)或注解中编写 SQL 语句。MyBatis 会负责接口的动态代理实现和 SQL 语句的执行。这大大简化了数据库操作的开发过程。

5、MappedStatement:

  • MappedStatement 是 MyBatis 内部表示一个 SQL 映射语句的对象。

  • 它包含 SQL 语句、参数类型、结果映射等信息。

  • 当 MyBatis 解析 Mapper XML 文件时,会为每个 SQL 语句创建一个 MappedStatement 对象,并存储在 Configuration 对象中。

  • 执行 SQL 时,MyBatis 会根据方法签名或 ID 查找对应的 MappedStatement。

        MappedStatement 是 MyBatis 中的一个核心类,它代表了一个映射语句,即一个 SQL 语句及其相关的配置信息。在 MyBatis 中,MappedStatement 对象是由 MyBatis 在解析 XML 映射文件或注解时创建的,并存储在 Configuration对象中。

        由于 MappedStatement 是 MyBatis 内部使用的核心类,其实现细节和源代码通常较为复杂,不适合在这里完整地列出。不过,我可以为你提供一个简化版的 MappedStatement 类结构,并添加必要的注释来解释其主要组成部分。

        请注意,以下代码仅用于解释目的,帮助你更好的理解:

// MappedStatement 类简化版,用于解释其主要组成部分  
public class MappedStatement {  
  
    // 映射语句的唯一标识符  
    private String id;  
  
    // 映射语句对应的 SQL 语句  
    private String sql;  
  
    // 映射语句的类型(SELECT, INSERT, UPDATE, DELETE)  
    private SqlCommandType sqlCommandType;  
  
    // 参数类型,即传递给 SQL 语句的参数的类型  
    private Class<?> parameterType;  
  
    // 结果类型,即 SQL 语句执行后返回的结果的类型  
    private Class<?> resultType;  
  
    // 语句的结果映射配置  
    private ResultMap resultMap;  
  
    // 语句使用的数据库 ID(用于分库分表等情况)  
    private String databaseId;  
  
    // 语句使用的参数处理器类型  
    private Class<? extends ParameterHandler> parameterHandlerType;  
  
    // 语句使用的结果处理器类型  
    private Class<? extends ResultHandler> resultHandlerType;  
  
    // 语句使用的 SQL 语句解析器类型  
    private Class<? extends StatementHandler> statementHandlerType;  
  
    // 语句使用的绑定器类型  
    private Class<? extends TypeHandler> boundSqlTypeHandler;  
  
    // 语句的插件列表  
    private List<Interceptor> interceptors;  
  
    // ... 可能还有其他字段和方法  
  
    // 构造函数(通常不是直接创建的,而是通过 MyBatis 的内部机制)  
    public MappedStatement(String id, String sql, SqlCommandType sqlCommandType, Class<?> parameterType,  
                           Class<?> resultType, ResultMap resultMap, String databaseId,  
                           Class<? extends ParameterHandler> parameterHandlerType,  
                           Class<? extends ResultHandler> resultHandlerType,  
                           Class<? extends StatementHandler> statementHandlerType,  
                           Class<? extends TypeHandler> boundSqlTypeHandler,  
                           List<Interceptor> interceptors) {  
        this.id = id;  
        this.sql = sql;  
        this.sqlCommandType = sqlCommandType;  
        this.parameterType = parameterType;  
        this.resultType = resultType;  
        this.resultMap = resultMap;  
        this.databaseId = databaseId;  
        this.parameterHandlerType = parameterHandlerType;  
        this.resultHandlerType = resultHandlerType;  
        this.statementHandlerType = statementHandlerType;  
        this.boundSqlTypeHandler = boundSqlTypeHandler;  
        this.interceptors = interceptors;  
    }  
  
    // Getter 和 Setter 方法省略...  
  
    // ... 可能还有其他方法,如执行 SQL 语句、获取绑定参数等  
}

解释:

        1、标识符 id: 每个 MappedStatement 对象都有一个唯一的标识符,它通常对应于 Mapper 接口中的一个方法名。

        2、SQL 语句 sql: 存储了具体的 SQL 语句字符串。

        3、语句类型 sqlCommandType: 表示这个映射语句是查询、插入、更新还是删除操作。

        4、参数类型 parameterType 和结果类型 resultType: 分别表示传递给 SQL 语句的参数类型和 SQL 语句执行后返回的结果类型。

        5、结果映射 resultMap: 用于复杂结果集的映射配置。

        6、数据库 ID databaseId: 用于分库分表等高级功能。

        7、处理器类型: 包括参数处理器 parameterHandlerType、结果处理器 resultHandlerType、语句处理器 statementHandlerType 和绑定器 boundSqlTypeHandler,它们都是用于处理 SQL 语句执行过程中不同阶段的任务的类型。

        8、插件列表 interceptors: 存储了应用于这个映射语句的插件列表,插件可以用于拦截和修改 SQL 语句的执行过程。

        在实际应用中,MappedStatement 对象是由 MyBatis 在启动时解析 XML 映射文件或注解时创建的,并存储在 Configuration 对象中。当执行数据库操作时,MyBatis 会根据 Mapper 接口方法的名称查找对应的 MappedStatement 对象,并使用其中的信息来构建和执行 SQL 语句。

        由于 MappedStatement 是 MyBatis内部实现的一部分,它的具体细节可能会随着 MyBatis 的版本更新而有所变化。然而,其核心功能和设计原则通常保持一致:为 SQL 映射语句提供元数据信息和运行时环境。

        在实际的 MyBatis 实现中,MappedStatement 类通常包含更多的字段和方法,用于处理更复杂的场景,比如动态 SQL、缓存配置、结果集映射、条件分支等等。它通常还与 MyBatis 的其他关键组件如 SqlSessionExecutorStatementHandler 等紧密协作,以完成 SQL 语句的执行和结果处理。

        当你使用 MyBatis 时,你通常不需要直接创建或操作 MappedStatement 对象。相反,你会通过定义 Mapper 接口和 XML 映射文件来声明你的 SQL 映射语句,然后 MyBatis 会自动为你处理 MappedStatement 的创建和管理。

6、Executor:

  • Executor 是 SQL 语句执行的核心。

  • 它有三个实现类:SimpleExecutor、ReuseExecutor 和 BatchExecutor,分别对应不同的执行策略。

  • Executor 负责与 JDBC 交互,包括创建 PreparedStatement、设置参数、执行 SQL、处理结果等。

  • 它会使用 TypeHandler 来处理参数和结果集的转换。

        由于 Executor 类是 MyBatis 框架中的核心组件,其源代码相对较长且涉及多个内部类和复杂逻辑。在这里,V 哥将为你提供一个简化版的 Executor 类及其部分实现,来解释其主要功能。

// Executor接口,定义了执行SQL语句的方法  
public interface Executor {  
    // 执行更新操作(插入、更新、删除)  
    int update(MappedStatement ms, Object parameter);  
  
    // 执行查询操作,返回结果列表  
    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql);  
  
    // 执行查询操作,返回单个结果  
    <E> E query(MappedStatement ms, Object parameter, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql);  
  
    // 执行查询操作,返回结果集游标  
    <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql);  
  
    // 刷新缓存  
    void flushStatements();  
  
    // 关闭Executor,释放资源  
    void close(boolean forceClose);  
  
    // 是否已关闭  
    boolean isClosed();  
  
    // 获取事务对象  
    Transaction getTransaction();  
  
    // 延迟加载是否开启  
    boolean isLazyLoadEnabled();  
  
    // 设置延迟加载是否开启  
    void setLazyLoadEnabled(boolean lazyLoadEnabled);  
}  
  
// BaseExecutor类,Executor接口的一个基础实现类  
public abstract class BaseExecutor implements Executor {  
    protected final Configuration configuration;  
    protected final Transaction transaction;  
    protected ErrorContext errorContext;  
  
    public BaseExecutor(Configuration configuration, Transaction transaction) {  
        this.configuration = configuration;  
        this.transaction = transaction;  
        this.errorContext = new ErrorContext();  
    }  
  
    // 省略其他方法...  
  
    // 更新操作实现  
    @Override  
    public int update(MappedStatement ms, Object parameter) {  
        // ... 更新操作的实现逻辑,包括预处理语句、设置参数、执行更新等  
    }  
  
    // 查询操作实现(返回结果列表)  
    @Override  
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) {  
        // ... 查询操作的实现逻辑,包括预处理语句、设置参数、执行查询、处理结果集等  
    }  
  
    // ... 其他方法的实现...  
}  
  
// SimpleExecutor类,BaseExecutor的一个简单实现,用于执行SQL语句  
public class SimpleExecutor extends BaseExecutor {  
    public SimpleExecutor(Configuration configuration, Transaction transaction) {  
        super(configuration, transaction);  
    }  
  
    // 更新操作实现(继承自BaseExecutor)  
    @Override  
    public int update(MappedStatement ms, Object parameter) {  
        // 这里可以添加SimpleExecutor特有的逻辑,或者直接调用父类的实现  
        return super.update(ms, parameter);  
    }  
  
    // 查询操作实现(继承自BaseExecutor)  
    @Override  
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) {  
        // 这里可以添加SimpleExecutor特有的逻辑,或者直接调用父类的实现  
        return super.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);  
    }  
  
    // ... 其他方法的实现...  
}

解释:

        1、Executor 接口: 定义了执行 SQL 语句所需的方法,包括更新、查询等。它是 MyBatis 中执行器模式的核心部分,允许不同的执行策略(如批处理、重用预处理语句等)通过不同的实现类来实现。

        2、BaseExecutor 类: 是 Executor 接口的一个基础实现类,提供了执行器的一些通用逻辑。它通常包含配置信息、事务对象和错误上下文等成员变量。BaseExecutor 提供了对 SQL 语句执行的基础支持,但具体的执行逻辑可能由其子类实现。

        3、SimpleExecutor 类: 是 BaseExecutor 的一个具体实现,它可能不包含复杂的逻辑或优化,但提供了基本的 SQL执行功能。在实际应用中,MyBatis 可能提供了更多的执行器实现类,比如 ReuseExecutor 用于重用预处理语句,BatchExecutor 用于批量执行等。

        在 MyBatis 的实际实现中,Executor 类及其实现通常包含更多的成员变量、方法和复杂的逻辑,以处理SQL语句的解析、参数绑定、结果映射以及缓存等高级功能。此外,Executor 类通常还会与其他组件如 StatementHandlerParameterHandlerResultSetHandler 和 TypeHandler 等紧密合作,以构建和执行完整的SQL执行流程。

下面,V 哥将进一步解释 Executor 及其实现类在 MyBatis 中的一些核心功能:

1、SQL解析与绑定:

  • Executor 接收 MappedStatement 作为输入,该对象包含了SQL语句的元数据信息。

  • 使用 ParameterHandler 处理参数绑定,将用户提供的参数转换为JDBC可以理解的格式,并设置到预处理语句中。

2、执行SQL语句:

  • 调用JDBC的 Statement 或 PreparedStatement 执行SQL语句。

  • Executor 可能管理自己的预处理语句缓存,以提高性能。

3、结果处理:

  • 使用 ResultSetHandler 将JDBC的 ResultSet 转换为Java对象列表。

  • 涉及类型转换和结果映射,使用 TypeHandler 来处理字段类型和Java类型之间的转换。

4、事务管理:

  • Executor 通常与事务管理对象(如 Transaction)一起工作,以确保SQL操作在事务的上下文中执行。

  • 负责提交或回滚事务,以处理成功或失败的SQL操作。

5、缓存管理:

  • MyBatis 提供了一级缓存和二级缓存机制,Executor 负责管理这些缓存。

  • 在执行查询时,首先检查缓存中是否有结果,如果有则直接返回,避免重复执行SQL

6、延迟加载:

  • MyBatis 支持延迟加载,即当需要时才加载关联数据。

  • Executor 需要在适当的时候触发延迟加载的执行。

MyBatis 提供了多种 Executor 实现类,它们之间的主要差异在于执行策略和资源管理:

SIMPLE: 最基本的实现,每次执行都创建一个新的预处理语句。

REUSE: 重用预处理语句,以减少JDBC对象的创建和销毁开销。

BATCH: 批量执行SQL语句,适用于大量数据的插入、更新或删除操作。

每种实现都有其特定的使用场景和性能特点,用户可以根据应用的需求选择合适的实现。

Executor 是 MyBatis 框架中的核心组件之一,它负责执行SQL语句并处理结果。通过不同的实现类,MyBatis 提供了灵活的执行策略,以满足不同应用场景的性能需求。在实际应用中,用户通常不需要直接创建或管理 Executor 对象,而是通过配置和使用 MyBatis 的 API 来间接使用它。

7、TypeHandler:

  • TypeHandler 是 Java 类型和 JDBC 类型之间的桥梁。

  • MyBatis 提供了一系列内置的 TypeHandler,如 StringTypeHandlerIntegerTypeHandler 等。

  • 当需要自定义类型转换时,开发者可以实现自己的 TypeHandler

  • TypeHandler 负责将 Java 对象转换为 JDBC 参数,以及将 JDBC 结果集转换为 Java 对象。

TypeHandler 是 MyBatis 中一个非常核心的组件,它负责 Java 类型和 JDBC 类型之间的转换。TypeHandler 定义了类型转换的接口,并提供了一些基础实现。以下是一个简化版的 TypeHandler 接口及其一个实现类的示例。

// TypeHandler接口,定义了类型转换的方法  
public interface TypeHandler<T> {  
    // 设置参数值  
    void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;  
  
    // 从结果集中获取值  
    T getResult(ResultSet rs, String columnName) throws SQLException;  
  
    // 从结果集中获取值(使用列索引)  
    T getResult(ResultSet rs, int columnIndex) throws SQLException;  
  
    // 从CallableStatement中获取值  
    T getResult(CallableStatement cs, int columnIndex) throws SQLException;  
}  
  
// BaseTypeHandler类,TypeHandler的一个基础实现类,提供了默认的类型转换逻辑  
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {  
    // 设置参数值(默认实现,子类可覆盖)  
    @Override  
    public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {  
        if (parameter == null) {  
            if (jdbcType == null) {  
                throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");  
            }  
            ps.setNull(i, jdbcType.TYPE_CODE);  
        } else {  
            setNonNullParameter(ps, i, parameter, jdbcType);  
        }  
    }  
  
    // 设置非空参数值(子类需要实现这个方法)  
    protected abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;  
  
    // 从结果集中获取值(默认实现,子类可覆盖)  
    @Override  
    public T getResult(ResultSet rs, String columnName) throws SQLException {  
        return getResult(rs, rs.findColumn(columnName));  
    }  
  
    // 从结果集中获取值(默认实现,子类需要实现这个方法)  
    @Override  
    public T getResult(ResultSet rs, int columnIndex) throws SQLException {  
        return getNullableResult(rs, columnIndex);  
    }  
  
    // 从CallableStatement中获取值(默认实现,子类需要实现这个方法)  
    @Override  
    public T getResult(CallableStatement cs, int columnIndex) throws SQLException {  
        return getNullableResult(cs, columnIndex);  
    }  
  
    // 获取非空结果(子类需要实现这个方法)  
    protected abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;  
  
    // 获取非空结果(子类需要实现这个方法)  
    protected abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;  
  
    // 获取非空结果(子类需要实现这个方法)  
    protected abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;  
}  
  
// IntegerTypeHandler类,TypeHandler的一个具体实现,用于处理Integer类型的转换  
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {  
  
    // 设置非空参数值  
    @Override  
    protected void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType) throws SQLException {  
        ps.setInt(i, parameter);  
    }  
  
    // 从结果集中获取非空Integer值  
    @Override  
    protected Integer getNullableResult(ResultSet rs, String columnName) throws SQLException {  
        return rs.getInt(columnName);  
    }  
  
    // 从结果集中获取非空Integer值(使用列索引)  
    @Override  
    protected Integer getNullableResult(ResultSet rs, int columnIndex) throws SQLException {  
        return rs.getInt(columnIndex);  
    }  
  
    // 从CallableStatement中获取非空Integer值  
    @Override  
    protected Integer getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {  
        return cs.getInt(columnIndex);  
    }  
}

解释: 

1、TypeHandler 接口:

  • setParameter 方法:将 Java 类型的参数设置到 PreparedStatement 对象中,以便执行 SQL 语句。

  • getResult 方法:从 ResultSet 或 CallableStatement 对象中获取指定列的结果,并将其转换为 Java 类型。

2、BaseTypeHandler 抽象类:

  • setParameter 方法提供了默认实现,用于处理 null 值和 JDBC 类型的设置。

  • setNonNullParameter 是一个抽象方法,子类需要实现,用于处理非空值的设置。

  • getResult方法也提供了默认实现,它们通常调用 getNullableResult 抽象方法,子类需要实现具体的转换逻辑。

3、IntegerTypeHandler 类:

  • 继承自 BaseTypeHandler<Integer>,专门用于处理 Integer 类型的转换。

  • 实现了 setNonNullParameter 方法,用于将 Integer 类型的参数设置到 PreparedStatement 中。

  • 实现了 getNullableResult 方法的三个重载版本,用于从 ResultSet 或 CallableStatement 中获取 Integer 类型的结果。

使用场景:

        当 MyBatis 执行 SQL 语句时,它需要根据 Java 类型的参数和 SQL 查询的结果来设置参数和获取结果。这时,MyBatis 会查找合适的 TypeHandler 来执行这些类型转换。如果 MyBatis 提供了现成的 TypeHandler(如 IntegerTypeHandler),它可以直接使用。如果没有现成的 TypeHandler,用户也可以自定义 TypeHandler 来处理特殊的类型转换逻辑。

        TypeHandler 接口及其实现类在 MyBatis 中扮演了非常重要的角色,它们负责在 Java 类型和 JDBC 类型之间进行转换,使得 MyBatis 能够灵活地处理各种类型的参数和结果集。通过自定义 TypeHandler,用户可以扩展 MyBatis 的类型转换能力,以满足不同的业务需求。

8、Plugin:

  • Plugin 是 MyBatis 的插件机制,允许开发者在核心流程中插入自定义逻辑。

  • 插件通过实现 Interceptor 接口并覆盖 intercept 方法来定义自己的拦截逻辑。

  • 插件在 MyBatis 初始化时通过 Plugin 类进行包装,并插入到目标对象的代理链中。

  • 当目标对象的方法被调用时,插件的拦截逻辑会先被执行。

  Plugin 类在 MyBatis 中通常用于拦截和修改 MyBatis 的核心行为。它允许用户在不修改 MyBatis 核心代码的情况下,对 SQL 语句的生成、参数设置、结果集处理等过程进行自定义处理。以下是一个简化版的 Plugin 类及其实现。

// Plugin接口,定义插件需要实现的方法  
public interface Plugin {  
    // 包裹目标对象,返回一个被拦截对象  
    Object wrap(Object target);  
  
    // 获取插件的属性  
    Class<?> getType();  
  
    // 获取插件的处理程序  
    Interceptor getInterceptor();  
  
    // 插件是否可以被用于目标对象  
    boolean isTarget(Object target);  
  
    // 静态方法,用于生成插件实例  
    static Object wrap(Object target, Interceptor interceptor, Class<?> type) {  
        // 创建Plugin对象  
        Plugin plugin = new Plugin(target, interceptor, type);  
        // 返回被拦截的目标对象  
        return plugin.wrap(target);  
    }  
  
    // Plugin类的私有构造器,防止外部直接实例化  
    private Plugin(Object target, Interceptor interceptor, Class<?> type) {  
        // 初始化成员变量  
        this.target = target;  
        this.interceptor = interceptor;  
        this.type = type;  
    }  
  
    // 成员变量  
    private Object target;  
    private Interceptor interceptor;  
    private Class<?> type;  
}  
  
// Interceptor接口,定义插件需要实现的拦截方法  
public interface Interceptor {  
    // 插件在MyBatis初始化时调用  
    void intercept(Invocation invocation) throws Throwable;  
  
    // 插件的ID,用于唯一标识插件  
    Object plugin(Object target);  
  
    // 插件的属性集合  
    void setProperties(Properties properties);  
}  
  
// 假设我们有一个实现Interceptor接口的自定义插件  
public class MyCustomPlugin implements Interceptor {  
  
    // 插件的属性  
    private String someProperty;  
  
    @Override  
    public Object intercept(Invocation invocation) throws Throwable {  
        // 在这里编写拦截逻辑  
        // 例如,可以修改SQL语句、参数等  
        System.out.println("Intercepted method: " + invocation.getMethod().getName());  
        // 继续执行原始逻辑  
        return invocation.proceed();  
    }  
  
    @Override  
    public Object plugin(Object target) {  
        // 在这里可以对目标对象进行包装或处理  
        return Plugin.wrap(target, this, MyCustomPlugin.class);  
    }  
  
    @Override  
    public void setProperties(Properties properties) {  
        // 设置插件的属性  
        this.someProperty = properties.getProperty("someProperty");  
    }  
}

解释:

1、Plugin 接口:

  • wrap(Object target): 这是一个用于包装目标对象的方法,通常会在插件初始化时被调用,返回被包装后的对象,这个对象会代理目标对象的行为,并在必要时插入拦截逻辑。

  • getType(): 返回插件的类类型。

  • getInterceptor(): 返回插件的拦截器实现。

  • isTarget(Object target): 判断插件是否适用于目标对象。

  • wrap(Object target, Interceptor interceptor, Class<?> type): 这是一个静态方法,用于创建并返回 Plugin 实例,同时完成目标对象的包装。

2、Interceptor 接口:

  • intercept(Invocation invocation): 这是插件的核心方法,当目标对象的方法被调用时,这个方法会被执行。在这里,你可以编写自定义的拦截逻辑。

  • plugin(Object target): 这是一个用于包装目标对象的方法,返回包装后的对象。在 MyBatis 中,这个方法通常与 Plugin 接口的 wrap 方法结合使用,以创建代理对象。

  • setProperties(Properties properties): 这是一个设置插件属性的方法,MyBatis 在配置插件时会调用此方法。

3、MyCustomPlugin 类:

  • 这个类实现了 Interceptor 接口,是自定义插件的具体实现。

  • 在 intercept 方法中,你可以编写拦截目标对象方法执行的代码,例如修改 SQL 语句、修改参数等。

  • plugin 方法返回包装后的目标对象,通常直接调用 Plugin.wrap 方法。

  • setProperties 方法用于设置插件的配置属性。

使用场景:

        当你在 MyBatis 中需要修改 SQL 语句、参数设置或结果集处理时,你可以编写一个自定义的 Interceptor 实现,并使用 Plugin 接口来包装目标对象,从而在不修改 MyBatis 核心代码的情况下扩展其功能。在 MyBatis 的配置文件中配置插件后,MyBatis 会在启动时加载插件。

Plugin 类的使用:

在 MyBatis 中,Plugin 类的使用通常涉及到以下步骤:

1、编写自定义插件:

  • 创建一个类实现 Interceptor 接口,实现其中的 interceptplugin 和 setProperties 方法。

  • 在 intercept 方法中编写拦截逻辑,比如修改 SQL 语句、参数或处理结果集。

  • 在 plugin 方法中调用 Plugin.wrap 方法包装目标对象。

  • 在 setProperties 方法中处理插件配置属性。

2、配置插件:

  • 在 MyBatis 的配置文件(通常是 mybatis-config.xml)中,使用 <plugins> 元素配置插件。

  • 在 <plugin> 子元素中指定插件的 interceptor 实现类,以及可能的属性。

3、启动 MyBatis:

  • 当 MyBatis 启动时,它会加载并初始化配置的插件。

  • 插件的 intercept 方法会在相应的方法调用时被触发。

示例配置:

在 mybatis-config.xml 配置文件中配置自定义插件:

<configuration>  
    <!-- 其他配置 -->  
    <plugins>  
        <plugin interceptor="com.example.MyCustomPlugin">  
            <property name="someProperty" value="someValue"/>  
        </plugin>  
    </plugins>  
    <!-- 其他配置 -->  
</configuration>

Plugin 类的实现细节:

        在 Plugin 类的实现中,通常会使用动态代理技术来包装目标对象。当目标对象的方法被调用时,动态代理会拦截调用,并首先执行插件的拦截逻辑,然后再调用原始方法。

        Plugin 类中的 wrap 方法通常利用 Java 的反射 API 和动态代理(例如 JDK 动态代理或 CGLIB)来创建目标对象的代理。代理对象会实现目标对象的接口,并在调用方法时执行拦截逻辑。

注意:

  • 插件的 intercept 方法必须谨慎处理,避免引入性能问题或破坏 MyBatis 的行为。

  • 插件的 plugin 方法必须正确处理目标对象,确保返回的是正确的代理对象。

  • 插件的 setProperties 方法应该能够处理所有必要的配置属性,并在需要时验证它们的值。

  Plugin 类在 MyBatis 中是一个非常重要的机制,它允许用户在不修改 MyBatis 核心代码的情况下扩展其功能。通过编写自定义的 Interceptor 实现,并正确配置插件,用户可以拦截和修改 MyBatis 的行为,以满足特定的业务需求。在实际应用中,需要深入理解 MyBatis 的内部机制和动态代理技术,才能有效地使用 Plugin 类来扩展 MyBatis 的功能。

MyBatis整体架构图

        MyBatis 分为三层架构,分别是基础支撑层、核心处理层和接口层,如上两图所示。

1. 基础支撑层


1.1 类型转换模块

        <typeAliase> 标签的别名机制,由基础支撑层中的类型转换模块实现的;
        JDBC 类型与 Java 类型之间的相互转换,绑定实参、映射 ResultSet 场景中都有所体现:​​​​​​      

  • 在 SQL 模板绑定用户传入实参的场景中,类型转换模块会将 Java 类型数据转换成 JDBC 类型数据;
  • 在将 ResultSet 映射成结果对象的时候,类型转换模块会将 JDBC 类型数据转换成 Java 类型数据。


1.2 日志模块

        MyBatis 提供了日志模块来集成 Java 生态中的第三方日志框架,该模块目前可以集成 Log4j、Log4j2、slf4j 等优秀的日志框架。

1.3 反射工具模块

        MyBatis 的反射工具箱是在 Java 反射的基础之上进行的一层封装,为上层使用方提供更加灵活、方便的 API 接口,同时缓存 Java 的原生反射相关的元数据,提升了反射代码执行的效率,优化了反射操作的性能。

1.4 Binding 模块

        通过 SqlSession 获取 Mapper 接口的代理,然后通过这个代理执行关联 Mapper.xml 文件中的数据库操作。通过这种方式,可以将一些错误提前到编译期,该功能就是通过 Binding 模块完成的。

1.5 数据源模块

        持久层框架核心组件之一就是数据源,MyBatis 自身提供了一套不错的数据源实现,也是 MyBatis 的默认实现。MyBatis 的数据源模块中也提供了与第三方数据源集成的相关接口,这也为用户提供了更多的选择空间,提升了数据源切换的灵活性。

1.6缓存模块

        数据库是实践生成中非常核心的存储,数据库性能的优劣直接影响了上层业务系统的优劣。
很多线上业务都是读多写少的场景,在数据库遇到瓶颈时,缓存是最有效、最常用的手段之一(如下图所示),正确使用缓存可以将一部分数据库请求拦截在缓存这一层,这就能够减少一部分数据库的压力,提高系统性能。


        MyBatis 就提供了一级缓存和二级缓存,具体实现位于基础支撑层的缓存模块中。

1.7 解析器模块

        mybatis-config.xml 配置文件和 Mapper.xml 配置文件的解析。

1.8 事务管理模块

        持久层框架一般都会提供一套事务管理机制实现数据库的事务控制,MyBatis 对数据库中的事务进行了一层简单的抽象,提供了简单易用的事务接口和实现。一般情况下,Java 项目都会集成 Spring,并由 Spring 框架管理事务。

2. 核心处理层

        核心处理层是 MyBatis 核心实现所在,其中涉及 MyBatis 的初始化以及执行一条 SQL 语句的全流程。

2.1 配置解析

        MyBatis 有三处可以添加配置信息的地方,分别是:mybatis-config.xml 配置文件、Mapper.xml 配置文件以及 Mapper 接口中的注解信息。在 MyBatis 初始化过程中,会加载这些配置信息,并将解析之后得到的配置对象保存到 Configuration 对象中。

2.2 SQL 解析与 scripting 模块

        MyBatis 的最大亮点应该要数其动态 SQL 功能了,只需要通过 MyBatis 提供的标签即可根据实际的运行条件动态生成实际执行的 SQL 语句。MyBatis 提供的动态 SQL 标签非常丰富,包括 <where> 标签、<if> 标签、<foreach> 标签、<set> 标签等。

        MyBatis 中的 scripting 模块就是负责动态生成 SQL 的核心模块。它会根据运行时用户传入的实参,解析动态 SQL 中的标签,并形成 SQL 模板,然后处理 SQL 模板中的占位符,用运行时的实参填充占位符,得到数据库真正可执行的 SQL 语句。

2.3 SQL 执行

        要执行一条 SQL 语句,会涉及非常多的组件,比较核心的有:Executor、StatementHandler、ParameterHandler 和 ResultSetHandler。

        其中,Executor 会调用事务管理模块实现事务的相关控制,同时会通过缓存模块管理一级缓存和二级缓存。SQL 语句的真正执行将会由 StatementHandler 实现。StatementHandler 会先依赖 ParameterHandler 进行 SQL 模板的实参绑定,然后由 java.sql.Statement 对象将 SQL 语句以及绑定好的实参传到数据库执行,从数据库中拿到 ResultSet,最后,由 ResultSetHandler 将 ResultSet 映射成 Java 对象返回给调用方,这就是 SQL 执行模块的核心。

2.4 插件

        很多成熟的开源框架,都会以各种方式提供扩展能力。当框架原生能力不能满足某些场景的时候,就可以针对这些场景实现一些插件来满足需求,这样的框架才能有足够的生命力。这也是 MyBatis 插件接口存在的意义。

3. 接口层

        接口层是 MyBatis 暴露给调用的接口集合,这些接口都是使用 MyBatis 时最常用的一些接口,例如,SqlSession 接口、SqlSessionFactory 接口等。其中,最核心的是 SqlSession 接口,你可以通过它实现很多功能,例如,获取 Mapper 代理、执行 SQL 语句、控制事务开关等。

架构流程图

执行流程

       (1) MyBatis配置文件config.xml:配置了全局配置文件,配置了MyBatis的运行环境等信息。mapper,xml:sql的映射文件,配置了操作数据库的sql语句,此文件需在config.xml中加载。        

        (2)SqlSessionFactory:通过MyBatis环境等配置信息构造SqlSessionFactory(会话工厂)。

        (3)SqlSession:通过会话工厂创建SqlSession(会话),对数据库进行增删改查操作。

        (4)Exector执行器:MyBatis底层自定义了Exector执行器接口来具体操作数据库,Exector接口有两个实现,一个基本执行器(默认),一个是缓存执行器,SqlSession底层是通过Exector接口操作数据库。

        (5)MappedStatement:MyBatis的一个底层封装对象,它包装了MyBatis配置信息与sql映射信息等。mapper.xml中的insert/select/update/delete标签对应一个MappedStatement对象。标签的id就是MappedStatement的id。

        MappedStatement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo、Executor通过MappedStatement在执行sql前将输入的Java对象映射至sql中,输入参数映射就是JDBC编程对preparedStatement设置参数。MappedStatement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过MappedStatement在执行sql后将输出结果映射至Java对象中,输出结果映射就是JDBC编程对结果的解析处理过程。

调用流程图

Mapper代理执行原理

Mapper代理开发方式使用的是JDK的动态代理(针对有接口的类进行动态代理)。

Springboot整合Mybatis的流程

1.查询前

也就是springboot启动时做的工作

实例化SqlSessionFactory
        1.构建一个DefaultSqlSessionFactory,主要作用就是维护Configuration和查询时获取DefaultSqlSession,然后通过DefaultSqlSession执行查询操作。
        2.实例化的过程中会解析mapper.xml中的各种标签封装成xxxsqlSource,保存在Configuration的mappedstatelment的sqlSource属性中。
        3.解析mapper.xml中的各种标签的过程中会对已经解析过的xml对应的mapper进行保存,保存在Configuration的mapperRegistry的konwnMappers中,key是接口全限定名,value是接口对应的MapperProxyFactory类型,保存是为了实例化mapper接口时能获取到mapper及对应的MapperProxyFactory(作用是实例化时创建mapper接口的代理类MapperProxy)
        4.mybatis-plus这种,不写xml的,会解析baseMapper中的方法,根据实体类信息等,生成sql。
实例化SqlSessionTemplate
        1.构建一个SqlSessionTemplate,用来在实例化mapper接口时获取mapper以及在执行查询时获取sqlsession。SqlSessionTemplate里维护DefaultSqlSessionFactory,比如获取Configuration就会通过SqlSessionTemplate获取DefaultSqlSessionFactory然后在获取Configuration,查询时获取和数据库关联的sqlsession,也是通过SqlSessionTemplate维护的DefaultSqlSessionFactory的opensession方法获取到的,类型是DefaultSqlSession。
实例化mapper
        扫描mapper文件变成BeanDefinition(@Mapper和@MapperScan),变成BeanDefinition后会把BeanDefinition中的BeanClass属性设置为MapperFactoryBean类型,以便在spring容器实例化对象时,对mapper接口也进行实例化,也就是生成对应的代理类MapperProxy,用以执行mapper的增删改查方法。
        实例化完这三个对象,springboot就可以等待前端调用接口然后执行mapper方法进行增删改查了。

2.查询时

        也就是前端调接口,然后调service,然后调mapper的方法时做的工作

解析传参
        当通过servcie调用mapper接口的方法时,会调用代理对象MapperProxy的invoke 方法。然后会调用MapperMethod的invoke 方法。在MapperMethod的invoke 方法会调用MapperMethod的execute方法。在这个方法中会调用SqlSessionTemplate的对应方法执行查询,在调用之前会进行方法参数解析,最终方法是ParamNameResolver类的getNamedParams,得到一个map,key是参数名,value是参数值。
获取最终的sql
        mapper.xml中的sql会在MybatisAutoConfiguration中构建SqlSessionFactory时得到解析,如果有where if之类的标签会被解析成DynamicSqlSource,如果是普通的查询语句(select * from departments where department_id=#{depId})则会被解析成RawSqlSource,这个属性会被存在configuration的mappedstatements属性中,属性名称为sqlSource。然后执行查询时,会从sqlSource中拿到对应的原始sql,然后再进行解析,也就是把方法调用时的传参拼接到sql中以及拼接where if这种动态标签,最终得到完整的sql。方法就是对应的SqlSource类的getBoundsql方法。这里DynamicSqlSource类的getBoundsql方法也会调用RawSqlSource的getBoundSql方法。

3.查询后

也就是查询出结果后做的工作

  1. 解析返回值:关键类DefaultResultSetHandler,基本逻辑都是在这个类实现的。关键类ResultSetWrapper,保存要映射的字段集合和查询出的数值的字节数组
  2. DefaultResultSetHandler类的handleResultSets方法,先拿到需要映射的字段集合,封装在ResultSetWrapper中,然后再获取一个resultmap类型的集合,每个resultmap保存需要映射的类型,如果有resultmap标签则会封装到resultmappings属性中。
  3. DefaultResultSetHandler类的handleResultSet方法,调用handleRowValues方法处理结果集放到multipleResults中。
  4. DefaultResultSetHandler类的handleRowValues方法,分别处理嵌套映射和非嵌套映射。
  5. 非嵌套映射,handleRowValuesForSimpleResultMap方法,遍历映射每行数据,调用getRowValue方法。没加resultmap或者resultmap中没做映射的字段调用applyAutomaticMappings方法,resultmap中映射的字段调用applyPropertyMappings方法。具体方法就是调用对应字段类型的typeHandler从字节数组中拿到数据进行转换。所有对应字段值的字节数组在ResultSetWrapper的resultset属性中。
  6. 嵌套映射,handleRowValuesForNestedResultMap方法,遍历映射每行数据,调用重载的getRowValue方法,没加resultmap或者resultmap中没做映射的字段调用applyAutomaticMappings方法,resultmap中映射的字段调用applyPropertyMappings方法,嵌套映射的字段调用applyNestedResultMappings方法。而applyNestedResultMappings会再次调用getRowValue方法解析每行数据,逻辑和非嵌套映射相同。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/789334.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

React文档内网搭建

React文档内网搭建流程 官网地址 官网中文地址 通过官网我们可以找到React的github存储库 ReactGitHub 在介绍中可以找到对应的文档存储库 React文档存储库 此存储库是英文文档地址,我们通过中文文档地址以及该存储库作者目录下找到中文存储库 React文档中文存储库 下载…

JavaSE语法 | 初识Java!!!

初识Java 一、Java开发环境二、初步认识Java的main方法2.1 main方法的实现2.2 运行Java程序 三、注释四、标识符五、关键字 一、Java开发环境 IDEA版本&#xff1a;IntelliJ IDEA Community Edition 2022.3.3 JDK17 Windows 11 二、初步认识Java的main方法 2.1 main方法的实…

C语言入门-1.数据的类型、数据的输入输出

数据类型常量变量&#xff08;整型-浮点-字符&#xff09; 数据类型 基本类型 整型int 符号常量 定义一个整形变量时要使用关键字int #include <stdio.h> //符号常量练习 #define PI 3 2 int main() {int i PI * 2;printf("i%d\n",i);return 0; } //7 …

解密 AI 客服:LangChain+ChatGPT 打造智能客服新时代

你需要了解 ChatGPT ChatGPT 是 OpenAI 开发的一种基于人工智能技术的自然语言处理模型。它可以通过对大量文本数据进行训练&#xff0c;自动生成高质量的回答和对话。ChatGPT 具有高效、准确、自然的特点&#xff0c;可以帮助人们更加高效地处理信息和交流。 ChatGPT 有很多…

QT TCP多线程网络通信

学习目标&#xff1a; TCP网络通信编程 学习前置环境 运行环境:qt creator 4.12 QT TCP网络通信编程-CSDN博客 Qt 线程 QThread类详解-CSDN博客 学习内容 使用多线程技术实现服务端计数器 核心代码 客户端 客户端&#xff1a;负责连接服务端&#xff0c;每次连接次数1。…

启动tomcat时提示The JRE_HOME environment variable is not defined correctly

我的情况是在已经安装过jdk后&#xff0c;启动tomcat时出现以下问题 原因是环境变量配置不正确导致的 首先确认一下jre的实际安装路径 然后修改环境变量配置文件 vim /etc/profile 添加以下内容&#xff0c;JRE_HOME为实际jre的路径 然后保存退出 让文件生效一下 source…

Docker-搭建部署Jenkins(保姆篇)

文章目录 Jenkins部署拉取镜像启动容器查看初始密码关闭CSRF Jenkins页面使用解决插件下载缓慢访问jenkins页面推荐插件安装创建一个管理员账号实例配置页面展示 更多相关内容可查看 Jenkins部署 拉取镜像 如果想拉取对应版本请指明版本号 docker pull jenkins/jenkins:lts-…

数据分析入门指南:表结构数据(三)

在数字化转型的浪潮中&#xff0c;表结构数据作为企业决策支持系统的核心要素&#xff0c;其重要性日益凸显。本文深入剖析了表结构数据的本质特征、高效处理策略&#xff0c;并探讨了其在现代商业智能环境中的广泛应用&#xff0c;旨在为数据分析师与决策者提供前沿洞察与实战…

电脑屏幕亮度怎么调?3个技巧,指尖轻松调控明亮度

你是否曾因为屏幕亮度的不合适而感到眼睛疲劳&#xff1f;是否曾在深夜加班时&#xff0c;被电脑屏幕刺眼的亮度搅得心烦意乱&#xff1f;电脑屏幕亮度怎么调呢&#xff1f;本文将为你介绍3个简便易行的技巧&#xff0c;让指尖轻松掌控屏幕亮度&#xff0c;享受舒适的观看体验。…

前端vue 实现取色板 的选择

大概就是这样的 一般的web端框架 都有自带的 的 比如 ant-design t-design 等 前端框架 都是带有这个的 如果遇到没有的我们可以自己尝试开发一下 简单 的 肯定比不上人家的 但是能用 能看 说的过去 我直接上代码了 其实这个取色板 就是一个input type 是color 的input …

Vue组件通信props和$emit用法

父传子&#xff0c;通过props 子传父&#xff0c;通过$emit App.vue <template><div class"app" style"border: 3px solid #000; margin: 10px">我是APP组件<!-- 1.给组件标签&#xff0c;添加属性方式 赋值 --><!-- 添加属性传值 …

untiy 在菜单栏添加自定义按钮 点击按钮弹出一个Unity窗口,并在窗口里添加属性

using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityEngine.Rendering.PostProcessing;public class AutoGenerateWindow : EditorWindow //这是定义一个窗口 {public string subjecttName "科目名字";//科目的名字public GameOb…

补光灯LED照明 2.7V4.2V5V升60V80V100V升压恒流芯片IC-H6902B

H6902B升压恒流芯片IC确实是一款为LED照明应用设计的稳定且可靠的解决方案。这款芯片具有以下几个显著特点&#xff1a; 高效率&#xff1a;效率高达95%以上&#xff0c;这意味着在驱动LED灯时&#xff0c;电源到LED的能量转换效率非常高&#xff0c;减少了能量损失&#xff0…

抖音本地生活服务商怎么申请?附详细教程!

随着本地生活的发展潜力和行业前景的不断展现&#xff0c;本地生活服务商也逐渐成为了一大热门职业。在此背景下&#xff0c;作为拥有约8亿日活用户的抖音成为了人们选择平台时的优先考虑对象&#xff0c;而以抖音本地生活服务商怎么申请为代表的相关问题也因此在多个创业者群中…

雪花算法改造失败导致ID重复问题分享

背景 雪花算法是分布式应用中应用比较多的 ID 生成算法&#xff0c;某项目中使用该算法生成ID&#xff0c;近期被反馈算法生成的 ID 存在重复的情况&#xff0c;排了一天&#xff0c;终于找到问题根源了。 本文将总结这个 Bug &#xff0c;顺便温故一下雪花算法及改造雪花算法…

mes系统在新材料行业中的应用价值

万界星空科技新材料MES系统是针对新材料制造行业的特定需求而设计的制造执行系统&#xff0c;它集成了生产计划、过程监控、质量管理、设备管理、库存管理等多个功能模块&#xff0c;以支持新材料生产的高效、稳定和可控。以下是新材料MES系统的具体功能介绍&#xff1a; 一、生…

创建 ComfyUI 自定义节点的基本指南

ComfyUI 自定义节点基础教程 开始前的准备理解 ComfyUI 节点创建自定义节点1. 定义节点参数2. 实现节点逻辑3. 与 ComfyUI 集成 测试和改进节点结论 ComfyUI 是一个多功能的Stable Diffusion图像/视频生成工具&#xff0c;能够让开发者设计并实现自定义节点&#xff0c;扩展功能…

无线领夹麦克风哪个牌子好,揭秘降噪领夹麦排行榜内幕!

在当今这个短视频如潮水般涌动的时代&#xff0c;人们的日常生活中掀起了一股新的潮流——用Vlog来捕捉生活的点点滴滴&#xff0c;许多博主在各种短视频和直播平台上开启了他们的副业之旅。这一现象催生了麦克风技术的飞速进步&#xff0c;使其从单一的录音工具转变为拥有多种…

私域运营从0到1冷启动

私域社群的冷启动是一个从无到有的过程&#xff0c;需要策略和耐心来吸引并维护用户。以下是一些步骤和策略&#xff0c;可以帮助你的私域社群实现从0到1的冷启动&#xff1a; 1. **明确目标和定位**&#xff1a; - 确定社群的目标用户和他们的需求。 - 明确社群的主题和…

一文清晰了解CSS——简单实例

首先一个小技巧&#xff1a; 一定要学会的vsCode格式化整理代码的快捷键&#xff0c;再也不用手动调格式了-腾讯云开发者社区-腾讯云 (tencent.com) CSS选择器用于选择要应用样式的HTML元素。常见的选择器包括&#xff1a; 类选择器&#xff1a;以.开头&#xff0c;用于选择具…