科普文:深入理解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方法解析每行数据,逻辑和非嵌套映射相同。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

怎么用PPT录制微课?详细步骤解析!

随着信息技术的不断发展&#xff0c;微课作为一种新型的教学形式&#xff0c;因其短小精悍、针对性强等特点&#xff0c;在教育领域得到了广泛的应用。而PPT作为一款常用的演示工具&#xff0c;不仅可以用来制作课件&#xff0c;还可以利用其内置的录屏功能或结合专业的录屏软件…

C++:类与对象(上)

目录 一、类的定义 1.1类的定义格式 1.2访问限定符 1.3类域 二、实例化 2.1实体化的概念 2.2类对象的大小 三、this指针 前言&#xff1a; 这篇文章是对类和对象的初步介绍&#xff0c;我将用三篇文章描述类和对象&#xff0c;希望对大家有所帮助 一、类的定义 什么是…

jenkins打包java项目报错Error: Unable to access jarfile tlm-admin.jar

jenkins打包boot项目 自动重启脚本失败 查看了一下项目日志报错&#xff1a; Error: Unable to access jarfile tlm-admin.jar我检查了一下这个配置&#xff0c;感觉没有问题&#xff0c;包可以正常打&#xff0c; cd 到项目目录下面&#xff0c;手动执行这个sh脚本也是能正常…

这可能是最详细的 Dagger2 使用教程 二(限定注解 @Named、@Qulifier 和 范围注解 @Singleton、@Scope)

通过上一篇文章我们知道了 Dagger2 的基本使用&#xff0c;在这篇文章中&#xff0c;我们将讲解 Dagger 中的两个重要概念以及相关注解。 这可能是最详细的 Dagger2 使用教程 一&#xff08;基本使用&#xff09; 类型上再加限定&#xff1a;Named 和 Qulifier 注解的使用 通…

罗技K380无线键盘及鼠标:智慧互联,一触即通

目录 1. 背景2. K380无线键盘连接电脑2.1 键盘准备工作2.2 电脑配置键盘的连接 3. 无线鼠标的连接3.1 鼠标准备工作3.2 电脑配置鼠标的连接 1. 背景 有一阵子经常使用 ipad&#xff0c;但是对于我这个习惯于键盘打字的人来说&#xff0c;慢慢在 ipad 上打字&#xff0c;实在是…

北摩高科应邀参加空客供应商大会

民航市场一直以来都是北摩高科重要的战略发展方向&#xff0c;进入国际航空巨头供应链体系也是公司的长期愿景。7月9日至10日&#xff0c;北摩高科公司应邀参与空客集团在天津举办的供应商大会及晚宴。 图1&#xff1a;空客集团采购总监Juergen Westermeier与北摩高科领导 会上…

基于Java+SpringMvc+Vue技术的药品进销存仓库管理系统设计与实现系统(源码+LW+部署讲解)

注&#xff1a;每个学校每个老师对论文的格式要求不一样&#xff0c;故本论文只供参考&#xff0c;本论文页数达到60页以上&#xff0c;字数在6000及以上。 基于JavaSpringMvcVue技术的在线学习交流平台设计与实现 目录 第一章 绪论 1.1 研究背景 1.2 研究现状 1.3 研究内容…

Linux权限相关

目录 Linux中的用户 Linux权限管理 Linux的文件访问者分类 Linux的文件类型和访问权限 文件类型 文件权限 文件权限的修改 文件所有者修改 文件所有者所在组修改 目录权限 粘滞位 文件掩码 在Linux中&#xff0c;权限包括用户的权限和文件的权限 Linux中的用户 在…

jenkins系列-06.harbor

https://github.com/goharbor/harbor/releases?page2 https://github.com/goharbor/harbor/releases/download/v2.3.4/harbor-offline-installer-v2.3.4.tgz harbor官网&#xff1a;https://goharbor.io/ 点击 Download now 链接&#xff0c;会自动跳转到上述github页面&am…

t-SNE降维可视化并生成excel文件使用其他画图软件美化

t-sne t-SNE&#xff08;t-分布随机邻域嵌入&#xff0c;t-distributed Stochastic Neighbor Embedding&#xff09;是由 Laurens van der Maaten 和 Geoffrey Hinton 于 2008 年提出的一种非线性降维技术。它特别适合用于高维数据的可视化。t-SNE 的主要目标是将高维数据映射…

Milvus核心设计(2)-----TSO机制详解

目录 背景 动机 Timestamp种类及使用场景 Guarantee timestamp Service timestamp Graceful time Timestamp同步机制 主流程 时间戳同步流程 背景 Milvus 在设计上突出了分布式的设计,虽然Chroma 也支持分布式的store 与 query。但是相对Milvus来说,不算非常突出。…

LabVIEW心电信号自动测试系统

开发了一种基于LabVIEW的心电信号自动测试系统&#xff0c;通过LabVIEW开发的上位机软件&#xff0c;实现对心电信号的实时采集、分析和自动化测试。系统包括心电信号采集模块、信号处理模块和自动化测试模块&#xff0c;能够高效、准确地完成心电信号的测量与分析。 硬件系统…

Vue3 markRaw的使用

markRaw 作用:将一个对象标记为不可以被转化为代理对象。返回该对象本身。 应用场景: 1.有些值不应被设置成响应式时,例如复杂的第三方类库等 2.当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能 3.在动态渲染组件的时候我们就可以使用 markRaw 包裹。markRaw 的…

秋招突击——7/9——MySQL索引的使用

文章目录 引言正文B站网课索引基础创建索引如何在一个表中查看索引为字符串建立索引全文索引复合索引复合索引中的排序问题索引失效的情况使用索引进行排序覆盖索引维护索引 数据库基础——文档资料学习整理创建索引删除索引创建唯一索引索引提示复合索引聚集索引索引基数字符串…

git 笔记

文章目录 前言一些代码托管仓库初步的一些理解设置个人信息创建自己的仓库查看仓库的状态添加文件到暂存区把暂存区的文件添加到版本库查询两个文件之间的区别查看版本迭代信息版本回滚查看所有的历史版本快捷切换应用感受分支的一些相关的操作假设新建一个分支并跳转到这个分支…

【ARM】MDK安装ARM_compiler5无法打开安装程序

【更多软件使用问题请点击亿道电子官方网站】 1、 文档目标 在客户安装了最新版本的MDK5.37及后续更新版本&#xff0c;但原工程使用ARM_Compiler_5.06进行编译和调试&#xff0c;需安装ARM_Compiler_5.06的编译器版本&#xff0c;但在解压缩的过程中后续无法打开ARM_Compiler…

FinalShell介绍,安装与应用

目录 一、什么是finalshell 二、finalshell功能 三、为什么要用finalshell 四、安装finalshell 五、finalshell使用 1.添加连接 获取虚拟ip地址 2.启动连接 一、什么是finalshell FinalShell是一体化的的服务器,网络管理软件,不仅是ssh客户端,还是功能强大的开发,运维工…

【学习笔记】无人机(UAV)在3GPP系统中的增强支持(四)-无人机系统(UAS)命令与控制(C2)通信用例

引言 本文是3GPP TR 22.829 V17.1.0技术报告&#xff0c;专注于无人机&#xff08;UAV&#xff09;在3GPP系统中的增强支持。文章提出了多个无人机应用场景&#xff0c;分析了相应的能力要求&#xff0c;并建议了新的服务级别要求和关键性能指标&#xff08;KPIs&#xff09;。…

Re:从零开始的C++世界——(一)入门基础

文章目录 C发展历史1.命名空间1.1 namespace的价值1.2 namespace的定义1.3 命名空间使⽤ 2.C输⼊&输出3.缺省参数3.1 缺省参数的概念3.2 缺省参数的分类 4.函数重载5.引⽤5.1引⽤的概念和定义5.2 引⽤的特性5.3 const引⽤5.4 使用场景5.5 指针和引⽤的关系 6.内联函数6.1内…

NAT地址转换+多出口智能选路,附加实验内容

本章主要讲&#xff1a;基于目标IP、双向地址的转换 注意&#xff1a;基于目标NAT进行转换 ---基于目标IP进行地址转换一般是应用在服务器端口映射&#xff1b; NAT的基础知识 1、服务器映射 服务器映射是基于目标端口进行转换&#xff0c;同时端口号也可以进行修改&…