前言

在学习MyBatis源码文章中,大胆想将其讲了解;故有此文章,如有问题,不吝指教!

留意:

学习源码必定必定不要太重视代码的编写,而是留意代码完结思想;

经过设问办法来体现代码中的思想;办法:5W+1H

源代码: https://gitee.com/xbhog/mybatis-xbhog https://github.com/xbhog/mybatis-xbhog ;交个朋友,欢迎star,也可重视大众号。

【简写Mybatis】03-Mapper xml的注册和运用

回忆&剖析

上一局完结【简写Mybatis】02-注册机的完结以及SqlSession处理;首要是为了完善Mapper的注册办法以及SqlSession规范的流程;

上一局的测验类如下;在运用上仍是比咱们平常了解的Mybatis要差好多,比方没有Mapper装备文件、Mapper映射文件、没有针对数据库操作等。

/**
 * Unit test for simple App.
 */
public class AppTest extends TestCase {
  /**
   * Rigourous Test :-)
   */
  public void testApp() {
    MapperRegistry mapperRegistry = new MapperRegistry();
    mapperRegistry.addMapper("com.xbhog");
    DefaultSqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(mapperRegistry);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    IUserDao user = sqlSession.getMapper(IUserDao.class);
    String userName = user.getUserName("xbhog");
    System.out.println("输出的信息:"+userName);
   }
}

意图

  1. XML的解析和读取
  2. 封装XML数据
  3. 封安装置类
  4. 封装MapperRegistry和SqlSessionFactory,设置统一进口
  5. 封装Mapper文件中办法的履行操作

完结

XML的解析和读取

引入相关的Maven依靠:

<dependency>
  <groupId>org.dom4j</groupId>
  <artifactId>dom4j</artifactId>
  <version>2.1.3</version>
</dependency>
<dependency>
  <groupId>cn.hutool</groupId>
  <artifactId>hutool-all</artifactId>
  <version>5.5.0</version>
</dependency>

解析的相关代码如下:已有注释

// xml文件内容转换为字符串流
Reader reader = ResourceUtil.getUtf8Reader("mybatis-config-datasource.xml");
​
// 从字符串流中读取并创立XML Document目标
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(new InputSource(reader));
​
// 获取XML文档的根元素
Element rootElement = document.getRootElement();
​
// 获取根元素下的“mappers”子元素
Element mappersElement = rootElement.element("mappers");
​
// 遍历一切“mapper”子元素
List<Element> mapperElements = mappersElement.elements("mapper");
for (Element mapperElement : mapperElements) {
  // 获取当时“mapper”元素的“resource”特点值
  String mapperResource = mapperElement.attributeValue("resource");
  System.out.println("正在检查资源:" + mapperResource);
​
  // 依据“resource”特点加载对应的XML文件内容为字符串流
  Reader mapperReader = ResourceUtil.getUtf8Reader(mapperResource);
​
  // 创立新的SAXReader实例以读取mapper文件中的XML内容
  SAXReader saxReaderForMapper = new SAXReader();
​
  // 从mapper的字符串流中创立新的Document目标
  Document mapperDocument = saxReaderForMapper.read(new InputSource(mapperReader));
​
  // 获取mapper文件的根元素
  Element mapperRootElement = mapperDocument.getRootElement();
​
  // 遍历mapper文件中一切的“select”元素
  List<Element> selectNodes = mapperRootElement.elements("select");
  for (Element selectElement : selectNodes) {
    // 获取“select”元素的各个特点值
    String selectId = selectElement.attributeValue("id");
    String parameterType = selectElement.attributeValue("parameterType");
    String resultType = selectElement.attributeValue("resultType");
    String sqlStatement = selectElement.getText();
​
    // 输出“select”元素的特点及SQL句子
    System.out.println("相关SQL映射信息:ID=" + selectId + ";参数类型=" + parameterType +
              ";成果类型=" + resultType + ";SQL句子=" + sqlStatement);
   }
}

作用是从一个主装备文件(”mybatis-config-datasource.xml”)中读取到多个mapper资源装备,并逐一加载这些mapper资源文件。针对每个mapper文件,进一步提取出其中一切的SQL select 映射界说,包含其ID、参数类型、成果类型以及详细的SQL句子;这儿是将这些信息打印出来,后续将保存到实体或许装备便利后续运用。

看下解析的效果:

正在检查资源:mapper/User_Mapper.xml
相关SQL映射信息:ID=queryUserInfoById;参数类型=java.lang.Long;成果类型=com.xbhog.User;SQL句子=
     SELECT id, userId, userHead, createTime
     FROM user
     where id = #{id}

XML装备构建器

这部分采用制作者形式完结XML的装备,该形式比较合适根本物料不变,而其组合常常发生变化的场景;中心意图是将杂乱目标的构建进程与它的表明别离。

制作者分为一下几个人物:

  1. 笼统制作者:界说了创立产品目标的各个部分的办法(比方零件或组件),一般会有一个办法来获取最终的杂乱产品。
  2. 详细制作者:完结了笼统制作者接口,完结每个部分的详细构造和安装办法,供给构造进程的详细完结。
  3. 产品(杂乱目标):是被构建的杂乱目标,包含了多个组成部件。详细制作者创立产品的各个部件并最终组合成完整的产品。

笼统制作者

按照上述的界说以及详细咱们想完结的事务能够拆分红两个操作:

  1. 初始化XML字符流,转换成Doc供后续运用
  2. 解析完的DOC,实体类进行保存,而且需求一个出口进行取得
public abstract class BaseBuilder {
​
  protected final Configuration configuration;
​
  public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
   }
​
  //只确保外部能够取得一切的信息,详细的装备在子类中赋值
  public Configuration getConfiguration() {
    return configuration;
   }
​
}

BaseBuilder供给了构建Configuration目标所需求的根本办法和特点,而且也对其子类有着通用的构建逻辑。

制作者完结

初始化XML字符流,转换Doc:

public XmlConfigBuilder(Reader reader) {
  //在处理XML装备文件中初始化Configuration
  super(new Configuration());
  // 2. dom4j 处理 xml
  SAXReader saxReader = new SAXReader();
  try {
    Document document = saxReader.read(new InputSource(reader));
    root = document.getRootElement();
   } catch (DocumentException e) {
    e.printStackTrace();
   }
}

初始化XML字符流在上一末节现已解决了,在正常Myabtis中的Sql是有占位符或许拼接符;并将相关元素的保存到mappedStatement(映射器句子类)中;

映射类句子类的编写:

/**
 * @author xbhog
 * @describe: 用于封装MyBatis中映射SQL句子的相关信息,包含装备信息、SQL类型、参数类型、成果类型、SQL句子以及动态参数映射等。
 * @date 2024/3/2
 */
public class MappedStatement {
​
  /**
   * 装备目标,包含MyBatis运转所需的环境、数据库映射等全局装备信息。
   */
  private Configuration configuration;
​
  /**
   * 映射ID,唯一标识一个MappedStatement,通常对应XML文件中的<mappedStatement>标签的id特点。
   */
  private String id;
​
  /**
   * SQL指令类型,如INSERT、UPDATE、SELECT或DELETE等。
   */
  private SqlCommandType sqlCommandType;
​
  /**
   * 参数类型,对应于传入SQL句子的参数类的全限定名。
   */
  private String parameterType;
​
  /**
   * 成果类型,对应于SQL查询成果映射到的Java类型的全限定名。
   */
  private String resultType;
​
  /**
   * SQL句子,可能是预编译的静态SQL或带有占位符的动态SQL。
   */
  private String sql;
​
  /**
   * 动态参数映射调集,键为参数的方位(从0开始计数),值为参数的称号。
   */
  private Map<Integer, String> parameter;
​
  /**
   * 空构造器,首要用于反射创立实例。
   */
  public MappedStatement() {}
​
  /**
   * 内部静态嵌套类Builder,遵循制作者规划形式,用于构建MappedStatement实例。
   */
  public static class Builder {
​
    /**
     * 储存待构建的MappedStatement目标引用。
     */
    private MappedStatement mappedStatement = new MappedStatement();
​
    /**
     * 初始化Builder目标,并设置MappedStatement的一切必要特点。
     *
     * @param configuration MyBatis的全局装备目标
     * @param id 映射ID
     * @param sqlCommandType SQL指令类型
     * @param parameterType 参数类型全限定名
     * @param resultType 成果类型全限定名
     * @param sql SQL句子
     * @param parameter 动态参数映射调集
     */
    public Builder(Configuration configuration, String id, SqlCommandType sqlCommandType, String parameterType, String resultType, String sql, Map<Integer, String> parameter) {
      mappedStatement.configuration = configuration;
      mappedStatement.id = id;
      mappedStatement.sqlCommandType = sqlCommandType;
      mappedStatement.parameterType = parameterType;
      mappedStatement.resultType = resultType;
      mappedStatement.sql = sql;
      mappedStatement.parameter = parameter;
     }
​
    /**
     * 完结构建并回来MappedStatement实例,一起进行必要的非空断言检查。
     *
     * @return 现已填充好一切必要特点的MappedStatement实例
     */
    public MappedStatement build() {
      assert mappedStatement.configuration != null : "Configuration must not be null!";
      assert mappedStatement.id != null : "ID must not be null!";
      return mappedStatement;
     }
   }
}

存储部分设置完结,咱们处理下select的操作:

// 遍历一切“mapper”子元素
List<Element> mapperElements = mappers.elements("mapper");
for (Element mapperElement : mapperElements) {
   ......
  //命名空间
  String namespace = mapperRootElement.attributeValue("namespace");
  // 遍历mapper文件中一切的“select”元素(暂时只有这一个操作)
  List<Element> selectNodes = mapperRootElement.elements("select");
  for (Element selectElement : selectNodes) {
    // 获取“select”元素的各个特点值
    String selectId = selectElement.attributeValue("id");
    String parameterType = selectElement.attributeValue("parameterType");
    String resultType = selectElement.attributeValue("resultType");
    String sql = selectElement.getText();
​
    Map<Integer, String> parameter = new HashMap<>();
    Pattern pattern = Pattern.compile("(#{(.*?)})");
    Matcher matcher = pattern.matcher(sql);
    for(int i = 1; matcher.find(); i++){
      String group1 = matcher.group(1);
      String group2 = matcher.group(2);
      log.info("匹配出来的信息为:{}:{}", group1, group2);
      parameter.put(i, group2);
      //替换占位符
      sql = sql.replace(group1,"?");
     }
    //获取全途径
    String msId = namespace + "." + selectId;
    //获取sql办法(select.....)
    String nodeName = selectElement.getName();
    //替换,坚持大小写一致
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    //保存
    MappedStatement mappedStatement = new MappedStatement.Builder(configuration, msId,
        sqlCommandType, parameterType, resultType, sql, parameter).build();
    //装备文件设置()
    configuration.addMappedStatement(mappedStatement);
   }
  // 注册Mapper映射器
  configuration.addMapper(Class.forName(namespace));
}

能够发现上述的代码中出来在XML的解析以及映射器特点的保存外,还有新加的configuration类。在mybatis中Configuration是一个重量级的中心装备类,简直包含了Mybatis运转的一切的装备信息。

在正式Mybatis中Configuration的作用:

  1. 存储全局装备信息:如数据库衔接信息(driver、url、username、password)、事务办理器设置、映射文件方位、自界说类型处理器、日志工厂等。
  2. 办理映射资源:存储并办理一切 <mapper> 标签对应的 MappedStatement 目标,这些目标包含了SQL句子、参数类型、成果类型、动态SQL解析器等信息。(本次处理的要点)
  3. 供给动态SQL解析:依据Configuration目标,MyBatis能解析包含动态元素的SQL句子。(本次处理的要点)
  4. 支撑推迟加载和缓存策略:在Configuration中能够装备二级缓存、全局的缓存开关、推迟加载策略等。
  5. 供给内置目标工厂:Configuration目标保护了一个目标工厂,用于在运转时创立比如ParameterHandler、ResultSetHandler、StatementHandler等中心目标。

本节处理流程:

【简写Mybatis】03-Mapper xml的注册和运用

这得看下SqlSessionFatoryBuilder进口类的完结:

public class SqlSessionFactoryBuilder {
​
  public SqlSessionFactory build(Reader reader) {
    XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder(reader);
    return build(xmlConfigBuilder.parse());
   }
​
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
   }
}

先说定论:configuration类自身不是单例类,相反是Mybatis经过 SqlSessionFactoryBuilder 来确保整个使用中只有一个 Configuration 实例,Configuration在* SqlSessionFactoryBuilder构建阶段完结实例化的操作。*

一旦 SqlSessionFactory 被创立,SqlSessionFatoryBuilder 的任务就完结了,之后 SqlSessionFactory 就会被用来创立 SqlSession 实例,然后履行 SQL 句子、办理事务和进行其他数据库操作。因为 SqlSessionFatoryBuilder 的任务完结后就能够丢掉,因而通常采取即用即抛的准则,正是因为SqlSessionFatoryBuilder 的这种准则,确保了Configuration 有了单例的特性。

Mapper文件中办法的履行操作封装

看下之前的invoke办法的完结:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  if(Object.class.equals(method.getDeclaringClass())){
    return method.invoke(this,args);
   }else{
    //todo 详细接口完结的办法
    return sqlSession.selectOne(method.getName(), args);
   }
}

在上一局中咱们在invoke办法中指定了sql的处理类型,根本操作都知道肯定不会有一种类型;为了读者能更好的了解,这儿先看下上一局和这一局代码的新旧流程比照。

比照规模:

└─src
  ├─main 
  │ └─java 
  │   └─com 
  │    └─xbhog 
  │     ├─binding 
  │     │ ├─MapperMethod.java(新增) 
  │     │ ├─MapperProxy.java 
  │     │ ├─MapperProxyFactory.java 
  │     │ └─MapperRegistry.java 

该流程处理代码流程不变,细节当地会有Configuration处理,详细请看代码操作。

【简写Mybatis】03-Mapper xml的注册和运用

变化比较大的是在MapperProxyinvoke办法的履行操作上;

【简写Mybatis】03-Mapper xml的注册和运用

这个时序图是MapperMethod类流程;

简单介绍下功用

  1. 在mapperMethod初始化前会先从methodCache中进行查询,存在回来,不存在构建完并回来,类似于缓存。
  2. mapperMethod初始化内包含了SqlCommand指令的构建,采用的形式仍是制作者形式,内置办法名和办法类型特点,经过接口名和办法名从Configuration获取mappedStatements构建特点值。
  3. 其他操作便是把crud进行分隔判断并履行。

到此一切涉及到的操作,都或多或少的写到了,接下来进行测验。

测验

环境构建

│  └─test
│    ├─java
│    │  └─com
│    │    └─xbhog
│    │        AppTest.java
│    │        IUserDao.java
│    │        User.java
│    │       
│    └─resources
│      │  mybatis-config-datasource.xml
│      │ 
│      └─mapper
│          User_Mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><mappers>
    <mapper resource="mapper/User_Mapper.xml"/>
  </mappers></configuration>
<mapper namespace="com.xbhog.IUserDao"><select id="queryUserInfoById" parameterType="java.lang.Long" resultType="com.xbhog.User">
     SELECT id, userId, userHead, createTime
     FROM user
     where id = #{id}
  </select></mapper>
public class AppTest extends TestCase {
​
  private Logger logger = LoggerFactory.getLogger(AppTest.class);
  /**
   * Rigourous Test :-)
   */
  public void testApp() throws Exception {
    // 1. 从SqlSessionFactory中获取SqlSession
    Reader reader = ResourceUtil.getUtf8Reader("mybatis-config-datasource.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    SqlSession sqlSession = sqlSessionFactory.openSession();
​
    // 2. 获取映射器目标
    IUserDao userDao = sqlSession.getMapper(IUserDao.class);
​
    // 3. 测验验证
    String res = userDao.queryUserInfoById("10001");
    logger.info("测验成果:{}", res);
   }
}

成果如下:

正在检查资源:mapper/User_Mapper.xml
11:55:58.436 [main] INFO  c.xbhog.builder.xml.XmlConfigBuilder - 匹配出来的信息为:#{id}:id
11:55:58.456 [main] INFO  com.xbhog.AppTest - 测验成果:你被署理了!办法:com.xbhog.IUserDao.queryUserInfoById 入参:[Ljava.lang.Object;@71c7db30,待履行的SQl:
     SELECT id, userId, userHead, createTime
     FROM user
     where id = ?

总结

整个流程大致是从装备文件出发,经过 SqlSessionFactoryBuilderXmlConfigBuilder 构建并填充 Configuration;然后 Configuration 被用来创立 SqlSessionFactory,然后生产 SqlSession;一起,Configuration 中还保护了一切 MappedStatement 以及 Mapper 相关的注册信息,确保 SqlSession 在处理数据库操作恳求时能够找到正确的映射关系并履行相应的办法。关于 Mapper 接口,它们经过 MapperRegistryMapperProxyFactoryMapperProxy 构成了一个面向接口编程的耐久层访问机制。

类名 类的作用
SqlSessionFactoryBuilder 这是一个工具类,用于从装备信息(如XML装备文件或预界说的Configuration目标)构建SqlSessionFactory实例。它接收装备输入,解析并验证装备,然后创立并初始化SqlSessionFactory
SqlSessionFactory 能够了解为SqlSession的工厂,它持有MyBatis的中心装备信息(由Configuration目标供给)。每个MyBatis使用的中心便是SqlSessionFactory实例,它担任创立SqlSession目标。SqlSessionFactory通常是线程安全的,能够被多个线程共享,并在整个使用生命周期内坚持。
SqlSession 是MyBatis履行数据库操作的首要进口点,它供给了CRUD(增修改查)等各种数据库操作办法。每个SqlSession目标都与一个数据库衔接(Connection)关联,且不是线程安全的,通常在一个恳求或操作规模内运用。
Configuration 这是MyBatis的全局装备容器,它存储了一切关于MyBatis的行为装备、一切映射器的注册信息、数据源、事务办理器、类型处理器等装备。SqlSessionFactoryBuilder正是依据Configuration的信息来构建SqlSessionFactory
DefaultSqlSessionFactory 这是SqlSessionFactory的一个详细完结类,承继自SqlSessionFactory接口,它包含了创立和办理SqlSession的实际逻辑。
DefaultSqlSession 这是SqlSession接口的默许完结,它封装了对数据库的CRUD操作以及对MappedStatement的访问。
SqlCommandType 表明SQL指令的类型枚举,包含INSERT、UPDATE、DELETE、SELECT等,这是在MappedStatement中用来区分不同类型的SQL操作。
MappedStatement 表明一个现已映射好的SQL句子及其相关装备,包含SQL类型、参数类型、成果类型、SQL句子自身以及可能的动态SQL节点和参数映射规矩。
XmlConfigBuilder 用于解析MyBatis的XML装备文件,将XML装备信息转换为Configuration目标。
BaseBuilder 在MyBatis中,如果有多个Builder类有着类似的构建逻辑,可能会界说一个基类BaseBuilder,提取公共办法和特点,不过这儿并未清晰提到它在MyBatis中的详细完结。
MapperRegistry 在MyBatis中,MapperRegistry是一个注册和办理一切Mapper接口的当地,它担任存储和查找Mapper接口对应的MapperProxyMapperMethod
MapperMethod 表明在Mapper接口中界说的某个办法的详细完结,它封装了办法对应的SQL履行逻辑。
MapperProxy MyBatis使用Java动态署理机制,经过MapperProxy来完结署理Mapper接口的目标,当调用Mapper接口办法时,实际上是调用了MapperProxy中的办法,然后履行SQL操作。
MapperProxyFactory 担任创立MapperProxy实例的工厂类,它依据Mapper接口生成对应的动态署理目标,运用户能够经过接口的办法来履行数据库操作,而不是直接与SqlSession打交道。每次需求新的Mapper署理目标时,都会经过MapperProxyFactory来创立。

学习&参阅

mp.weixin.qq.com/s/C_bb9f1Hr…

mybatis源码

AI大模型