Mybatis手写框架

image.png

1、Mybatis 核心流程

mybatis 核心业务流程:

  1. 解析 mybatis 系统配置文件,封装成 Java 的 Configuration 配置类
  2. 解析 mapper.xml 封装成 MapperStatement 供后面使用
  3. 具体的业务代码 调用 dao 的代理类
  4. 参数的映射和处理
  5. 根据入参 和 MapperStatement 动态生成可执行的 sql 语句
  6. 通过 resultHandle 处理结果集合映射,并返回结果集

总体执行活动图

Mybatis四大对象

  1. ParameterHandler
  2. SqlSource
  3. Executor
  4. ResultSetHandler

2、流程源码分析

:::info

  1. 读取mybatis主配置文件 通过流来获取指定路径的文件
  2. InputStream is = Resources.getResourceAsStream(“MybatisConfig.xml”);
  3. 创建一个SqlSessionFactoryBuilder
    1. SqlSessionFactoryBuilder builder = new SQLSessionFactoryBuilder();
  4. 通过构建者模式来创建工厂
    1. SqlSessionFactory factory = builder.build(is);
  5. 通过工厂模式创建sqlSession
    1. SqlSession sqlSession = factory.openSession();
  6. 通过动态代理获取对应的mapper
    1. UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  7. 调用方法
    1. List users = mapper.getUserList();
  8. 返回结果
    :::

image.png



二级缓存

mybatis执行流程

StatementHandler执行流程

测试类

@Before
public void init() throws Exception {
    // 指定全局配置文件路径
    String resource = "mybatis-config.xml";
    // 加载资源文件(全局配置文件和映射文件)
    InputStream inputStream = Resources.getResourceAsStream(resource);
    // 还有构建者模式,去创建SqlSessionFactory对象
    sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}

@Test
public void test1() throws Exception {
    // 指定全局配置文件的类路径
    SqlSession sqlSession = sqlSessionFactory.openSession();
    Configuration configuration = sqlSession.getConfiguration();
    TransactionFactory transactionFactory = configuration.getEnvironment().getTransactionFactory();
    Connection connection = configuration.getEnvironment().getDataSource().getConnection();
}

2.1、mybatis-config.xml全局配置解析

2.1.1、使用建造者模式来创建对象工厂

**作用:**负责加载并且解析配置文件 返回SqlSessionFactory

public class SqlSessionFactoryBuilder {
    
    public SqlSessionFactory build(InputStream inputStream) {
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream);
            return build(parser.parse());
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return null;
    }
    
    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }
}

说明:使用建造者模式创建工厂 这里使用重载实现多种组合初始化 可以学习这种思想创建 和 使用 分离 ,同时: 建造类之间 相互独立 , 在 一定程度上解耦

需要一个输出流 将配置文件的内容读取出来

InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
        for (ClassLoader cl : classLoader) {
            if (null != cl) {

                // try to find the resource as passed
                InputStream returnValue = cl.getResourceAsStream(resource);
                if (null == returnValue) {
                    returnValue = cl.getResourceAsStream("/" + resource);
                }

                if (null != returnValue) {
                    return returnValue;
                }
            }
        }
        return null;
    }

image.png
说明:通过实现多个构造器来实现不同传入参数的组合。
image.png

2.1.2、解析XML Config配置的类

解析mybatis-config.xml 解析类 的建造者模式 的基类

public abstract class BaseBuilder {

    // Configuration是MyBatis初始化过程的核心对象, MyBatis中几乎全部的配置信息会保存到Configuration对象中
  // Configuration对象是在MyBatis初始化过程中创建且是全局唯一的,
  protected final Configuration configuration;

  // 在mybatis-config xml配置文件中可以使用<typeAliases>标签定义别名,这些定义的别名都会记录在TypeAliasRegistry对象中
  protected final TypeAliasRegistry typeAliasRegistry;

  //  mybatis-config.xml 配置文件中可以使用<typeHandlers>标签添加自定义 TypeHandler器,
  // 完成指定数据库类型与 Java 类型的转换,这些 TypeHandler 都会记录在 TypeHandlerRegistry
  protected final TypeHandlerRegistry typeHandlerRegistry;


    public BaseBuilder(Configuration configuration) {
        this.configuration = configuration;
        this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
        this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
    }

    public Configuration getConfiguration() {
        return configuration;
    }
    
}

**说明:**这里的BaseBuilder抽象类就扮演着建造者模式的Builder角色。 通过及基类来实现控制属性 包括Configuration 全局配置类、TypeAliasRegistry 别名注册类、TypeHandlerRegistry 自定义处理器

2.1.3、解析mybatis-config.xml 的总解析类XMLConfigBuilder
public class XMLConfigBuilder extends BaseBuilder {

    /* 标记是否已经解析过配置文件 */
    private boolean parsed;
    
    /* 解析xml文件的解析器 */
    private final XPathParser parser;
    
    /**
     * 数据源,SqlSessionFactoryBuilder.build(InputStream in, String environment, Properties properties)
     * 不指定为空
     */
    private String environment;

    public XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        /* 初始化 Configuration */
        super(new Configuration());
        ErrorContext.instance().resource("SQL Mapper Configuration");
        
        /* 设置格外的属性 */
        this.configuration.setVariables(props);
        
        /* 标记初始化为 false */
        this.parsed = false;
        
        this.environment = environment;
        this.parser = parser;
    }
}

上面为解析全局配置文件的解析类 将config的信息全部解析解析 存储到对应的类中
XPathParser:是属于一个XML文件的解析类 结合XNode 来实现对配置文件解析通过Properties文件格式返回

2.1.4、全局配置的配置类Configuration

Configuration 类配置了需要配置的属性

/**
 * 全局配置文件的实体类
 */
public class Configuration {

  /**
   * Mybatis配置类。初始化Mybatix的配置和工厂类,一些工具类的别名,语言驱动
   */
  public Configuration() {
    //事务工厂
    //JDBC事务工厂
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    //ManagedTransaction:MyBatis自身不会去实现事务管理,而是让程序的容器如(JBOSS,Weblogic)来实现对事务的管理
    //生成MangaedTransaction工厂
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

    //数据源工厂
    //生产具有简单,同步,线程安全数据库连接池的数据源工厂
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    //生产无连接池数据源(即每次获取请求都简单的打开和关闭连接)工厂
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

    //缓存策略
    //永久缓存 Cache接口实现类,里面就是维护着一个HashMap
    typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
    //使用先进先出缓存策略的缓存装饰类
    typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    //使用最近最少使用的缓存策略的缓存装饰类
    typeAliasRegistry.registerAlias("LRU", LruCache.class);
    // 软引用回收策略 缓存装饰类
    typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
    // 弱引用回收策略 缓存装饰类
    typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

    /*
     * XML语言驱动
     * <p>
     *     Mybatis默认XML驱动类为XMLLanguageDriver,其主要作用于解析select|update|insert|delete节点为完整的SQL语句。
     * </p>
     */
    typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
    //这是一个简单的语言驱动,只能针对静态SQL的处里,如果出现动态SQL标签如
    typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

    //下面是日志别名
    typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
    typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
    typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
    typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
    typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
    typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
    typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

    //动态代理工厂,针对懒加载的
    //CGLIB代理工厂
    typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    //Javassist代理工厂
    typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

    //设置默认的语言驱动为XMLLanguagerDriver
    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    //注册RawLanguageDriver语言驱动
    languageRegistry.register(RawLanguageDriver.class);
  }
}

说明:配置类中的属性比较多 就不一一列举 源码已注释,就是将初始化全局配置文件中的参数存储到Configuration类中备用或者设置,Configuration包含了该项目配置的全部参数。

2.1.5、配置解析流程

目前框架实现配置

  1. properties配置
  2. settings 配置
  3. TypeAliases 配置
  4. environments 配置
  5. typeHandlers 配置
  6. mappers 配置

对这些配置进行解析的流程:
第一步: 通过建造者模式将配置文件的输出流传入返回一个sqlSessionFactory工厂 来获取SqlSession。

// 1. 获取到了SqlSessionFactory传入properties, properties设置配置key和value
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream, properties);

// 2.  调用方法: build(InputStream inputStream, String environment, Properties properties), 行为类是重载方法方式
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
    return build(inputStream, null, properties);
}

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
            // XMLConfigBuilder:用来解析XML全局配置文件
            // 使用构建者模式
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            // parser.parse():使用XPATH解析XML配置文件,将配置文件封装为Configuration对象
            // 返回DefaultSqlSessionFactory对象,该对象拥有Configuration对象(封装配置文件信息)
            return build(parser.parse());
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
            ErrorContext.instance().reset();
            try {
                inputStream.close();
            } catch (IOException e) {
                // Intentionally ignore. Prefer previous error.
            }
        }
    }

    public SqlSessionFactory build(Configuration config) {
        // 创建SqlSessionFactory接口的默认实现类
        return new DefaultSqlSessionFactory(config);
    }

            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            // parser.parse():使用XPATH解析XML配置文件,将配置文件封装为Configuration对象
            // 返回DefaultSqlSessionFactory对象,该对象拥有Configuration对象(封装配置文件信息)
            return build(parser.parse());
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
            ErrorContext.instance().reset();
            try {
                inputStream.close();
            } catch (IOException e) {
                // Intentionally ignore. Prefer previous error.
            }
        }
    }

    public SqlSessionFactory build(Configuration config) {
        // 创建SqlSessionFactory接口的默认实现类
        return new DefaultSqlSessionFactory(config);
    }
}

public class  XMLConfigBuilder extends BaseBuilder {
    
    private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        super(new Configuration());
        ErrorContext.instance().resource("SQL Mapper Configuration");
        // 方法传入的参数存放入Configuration的variables
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
  }
}

说明

  1. 获取SqlSessionFactoryBuilder 通过建造者模式来builder一个SqlSessionFactory 通过工厂模式来实现生产SqlSession 接口 来维护一条Sql操作。
  2. builder 方法中实现了实例化XMLConfigBuilder 对象 解析Config配置文件 初始化项目配置。
  3. 调用XMLConfigBuilder中的解析方法对mybatis-config.xml 的全局配置文件解析解析。

使用到的工具类

public Properties getChildrenAsProperties() {
    Properties properties = new Properties();
    //获取并遍历子节点
    for (XNode child : getChildren()) {
        //获取property节点的name和value属性
        String name = child.getStringAttribute("name");
        String value = child.getStringAttribute("value");
        if (name != null && value != null) {
            //设置属性到属性对象中
            properties.setProperty(name, value);
        }
    }
    return properties;
}

public List<XNode> getChildren() {
    List<XNode> children = new ArrayList<>();
    //获取子节点列表
    NodeList nodeList = node.getChildNodes();
    if (nodeList != null) {
        for (int i = 0, n = nodeList.getLength(); i < n; i++) {
            Node node = nodeList.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                // 将节点对象封装到 XNode 中,并将 XNode 对象放入 children 列表中
                children.add(new XNode(xpathParser, node, variables));
            }
        }
    }
    return children;
}

第二步:实例化一个XMLConfigBuilder 对象 传入InputStream参数 进行全局配置文件的解析 调用parse
2.1.2 那里介绍了XMLConfigBuilder 的基本参数 我们下面会使用到一个基本的解析xml的类

  1. 首先衔接一下上面XMLConfigBuilder类中的调用parse()方法来实现解析

XMLConfigBuilder中的parse解析xml配置文件调用类

 
// 是否解析过  只能解析一次配置集文件
private boolean parsed;

// XML 解析工具类
private final XPathParser parser;

// 默认数据环境配置
private String environment;

private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();

/**
 * 解析XML配置文件
 * @return
 */
public Configuration parse() {
    // 这里作用是只能加载一次
    if (parsed) {
        throw new BuilderException("XMLConfigBuilder只能创建一次!!");
    }
    parsed = true;
    // parser.evalNode("/configuration"):通过XPATH解析器,解析configuration根节点
    // 从configuration根节点开始解析,最终将解析出的内容封装到Configuration对象中
    // configuration 这个父标签开始解析
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}

说明:解析xml配置文件只需要解析一次 同时 将解析配置文件到Configuration 对象中时先需要将 根标签下的子标签全部通过 XPathParser 解析器中的Xpath语法获取指定的节点存储 到 XNode 节点中(w3.dom 包下的XNode )
image.png

private Object evaluate(String expression, Object root, QName returnType) {
    try {
        return xpath.evaluate(expression, root, returnType);
    } catch (Exception e) {
        throw new BuilderException("Error evaluating XPath.  Cause: " + e, e);
    }
}

xpath 调用执行器来实现表达式获取指定标签的内容 返回一个Node 对象 最后将返回的Node对象 传参到XNode 节点中 返回一个XNode 对象。

这里为什么使用org.w3c.dom.Node 的Node呢?

DOM中每个XML的节点都是一个Node(org.w3c.dom.Node),Node通常跟XPath配合使用,提供了解析节点元素名、属性名、属性值、节点文本内容、嵌套节点等功能,
XNode 的基本属性:

  1. node:被包装的org.w3c.dom.Node对象
  2. name:节点名
  3. body:节点内容
  4. attributes:节点属性集合
  5. variables:mybatis-config.xml配置文件中节点下引入或定义的键值对
  6. xpathParser:封装了XPath解析器,XNode对象由XPathParser对象生成,并提供了解析

这里举一个栗子

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration   PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration> 
  <properties>
    <property name="database.driver" value="com.mysql.jdbc.Driver"/>
    <property name="database.url" value="jdbc:mysql://localhost:3306/ssm?useSSL=false"/>
    <property name="database.username" value="root"/>
    <property name="database.password" value="root"/>
  </properties>
  
  <typeAliases> <!-- 别名 -->
    <typeAlias alias="role" type="com.learn.ssm.chapter4.pojo.Role"/>
  </typeAliases>
</configuration>

以节点为例子,生成一个对应的XNode节点后,
name:值为"typeAlias",由于没有文本内容也没有子节点所以body为空,
attributes:值为{alias=role, type=com.learn.ssm.chapter4.pojo.Role}的一个Properties对象,
variables:为{database.driver=com.mysql.jdbc.Driver, database.url=jdbc:mysql://localhost:3306/ssm?useSSL=false, database.username=root, database.password=root},
xpathParser:是构造函数传进来的一个参数,只需要知道它提供了解析XPath表达式的功能即可。
image.png

所以这里使用的Node 的实现 然后框架包装了Node 为 XNode 作为一个实体类

  1. 然后再了解XPathParser类的作用

XPathParser 解析类

/**
* XML解析器
*/
public class XPathParser {
    
    // 要解析的xml文件被转化成的Document对象。
    private final Document document;
    // 获取document时是否要开启校验,开启校验的话会根据xml配置文件中定义的dtd文件校验xml格式,默认不开启校验。
    private boolean validation;
    
    // mybatis-config.xml配置文件中,<Properties>节点引入或定义的属性。
    private Properties variables;
    // 封装的XPath对象,用来解析表达式。
    private XPath xpath;
    
    /***
    * 重载的构造器 可以学习 进行组合
    * @param xml
    */
    public XPathParser(String xml) {
        commonConstructor(false, null);
        this.document = createDocument(new InputSource(new StringReader(xml)));
    }
    
    public XPathParser(InputStream inputStream) {
        commonConstructor(false, null);
        this.document = createDocument(new InputSource(inputStream));
    }
    
    public XPathParser(Document document) {
        commonConstructor(false, null);
        this.document = document;
    }
    
    public XPathParser(String xml, boolean validation) {
        commonConstructor(validation, null);
        this.document = createDocument(new InputSource(new StringReader(xml)));
    }
    
    public XPathParser(InputStream inputStream, boolean validation) {
        commonConstructor(validation, null);
        this.document = createDocument(new InputSource(inputStream));
    }
    
    public XPathParser(Document document, boolean validation) {
        commonConstructor(validation, null);
        this.document = document;
    }
    
    public XPathParser(String xml, boolean validation, Properties variables) {
        commonConstructor(validation, variables);
        this.document = createDocument(new InputSource(new StringReader(xml)));
    }
    
    public XPathParser(Document document, boolean validation, Properties variables) {
        commonConstructor(validation, variables);
        this.document = document;
    }
    
    public XPathParser(InputStream inputStream, boolean validation, Properties variables) {
        commonConstructor(validation, variables);
        // 解析XML文档为Document对象
        this.document = createDocument(new InputSource(inputStream));
    }
    
 }

XPathParser的 属性值 和 构造方法
上面的工作方法都使用一个共同的构造方法 创建一个XPathParser 实例

/**
 * 通用的构造方法
 * 使用外部传入的参数,初始化成员变量;
 * 构造XPathFactory对象,获得Xpath的对象
 * @param validation 设置解析xml时是否对它进行校验。
 * @param variables
 */
private void commonConstructor(boolean validation, Properties variables){
    this.validation = validation;
    this.variables = variables;
    XPathFactory factory = XPathFactory.newInstance();
    this.xpath = factory.newXPath();
}

初始化 属性的参数后 将XNode 的参数解析 为 Document 返回 通过一个createDocument来实现创建Document对象

private Document createDocument(InputSource inputSource) {
    // important: this must only be called AFTER common constructor
    try {

        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        // 若true解析器将DTD验证被解析的文档
        factory.setValidating(validation);
        // 若true解析器将提供对 XML 名称空间的支持
        factory.setNamespaceAware(false);
        // 若true忽略注释
        factory.setIgnoringComments(true);
        // 若true,解析器在解析 XML 文档时,必须删除元素内容中的空格
        factory.setIgnoringElementContentWhitespace(false);
        factory.setCoalescing(false);
        // 若true,的解析器将扩展实体引用节点
        factory.setExpandEntityReferences(true);

        DocumentBuilder builder = factory.newDocumentBuilder();
        builder.setErrorHandler(new ErrorHandler() {
            @Override
            public void error(SAXParseException exception) throws SAXException {
                throw exception;
            }

            @Override
            public void fatalError(SAXParseException exception) throws SAXException {
                throw exception;
            }

            @Override
            public void warning(SAXParseException exception) throws SAXException {
            }
        });
        // 通过dom解析,获取Document对象
        return builder.parse(inputSource);
    } catch (Exception e) {
        throw new BuilderException("不能正确创建Document文件,原因是: " + e, e);
    }
}

说明

  1. 通过调用 实例化XMLConfigBuilder 对象时 实例化了一个XPathParser 对象传入这个XMLConfigBuilder对象中 构造方法进行初始化操作
  2. 调用commonConstructor 方法实现XPathParser 对象的属性初始化 。
  3. 将输入的流包装成InputSource 对象传入createDocument 方法中解析XNode 的数据 保存到该对象的document属性中,这个操作就是解析之前将configuration标签下的内容全部解析成Document类中保存数据。createDocument方法中创建Document是通过工厂模式来创建出一个Builder方法 然后再通过设置该工厂参数传入输出流InputStream来创建一个Document对象。
  4. 实例化XPathParser 对象解析完之后 XMLConfigBuilder 类中的parse() 解析方法 调用了parser.evalNode(“/configuration”) 方法将configuration 标签下的所有子标签通过表达式将XPathParser中的Document 属性中的数据 全部解析 成XNode 然后返回。
  5. evalNode 方法中实现了对该父标签解析 将该document对象的子标签全部解析到了包装类的XNode 中 存储。同时将properties配置标签的数据存储到variables 属性中保存 。XPath 是实现对指定标签名进行解析到Node 节点的然后将Node 的节点全部包装到XNode 节点。
  6. 最后将XPath表达式解析的Node 对象 、XPathParser 和 variables 的properties参数包装成XNode 对象返回到 XMLConfigBuilder 解析方法中通过XNode解析。
  7. 最后将封装好的XNode对象返回到XMLConfigBuilder解析类。

image.png
第三步:对全局配置文件中的标签单独解析 配置

配置文件解析 分节点解析

/**
 * 只提供 properties 标签
 * settings 标签
 * typeAliases标签
 * environments标签
 * typeHandlers 标签
 * mappers 标签
 * @param root
 */
private void parseConfiguration(XNode root) {
    try {
        // 解析</properties>标签
        propertiesElement(root.evalNode("properties"));

        // 解析</settings>标签
        /*2、对<settings>标签中的<setting>标签中的内容进行解析,这里解析的是<setting name="vfsImpl" value=",">
         * VFS是mybatis中用来表示虚拟文件系统的一个抽象类,用来查找指定路径下的资源。
         * 上面的key为vfsImpl的value可以是VFS的具体实现,必须
         * 是权限类名,多个使用逗号隔开,如果存在则设置到configuration中的vfsImpl属性中,如果存在多个,
         * 则设置到configuration中的仅是最后一个
         * */
        Properties settings = settingsAsProperties(root.evalNode("settings"));
        //
        loadCustomVfs(settings);

        // 解析</typeAliases>标签
        typeAliasesElement(root.evalNode("typeAliases"));
        settingsElement(settings);

        // 解析</environments>标签
        environmentsElement(root.evalNode("environments"));

        // 解析</typeHandlers>标签
        typeHandlerElement(root.evalNode("typeHandlers"));

        // 解析</mappers>标签
        mapperElement(root.evalNode("mappers"));

    } catch (Exception e) {
        throw new BuilderException("解析Configuration配置文件出现异常,原因为: " + e, e);
    }
}

root.evalNode(“typeAliases”) 解析该节点的方法是一样的 都是包装成XNode节点之后返回

解析配置文件中的所有标签

解析properties 标签的配置

/**
 * 解析<properties></properties>标签
 * 我们这个框架只需要实现resource就行
 * @param context
 * @throws Exception
 */
private void propertiesElement(XNode context) throws Exception {
    if (context != null){
        // 获取子标签<property>标签的内容
        Properties defaults = context.getChildrenAsProperties();
        // 获取标签<properties>标签resource属性的值
        String resource = context.getStringAttribute("resource");
        // 如果resource或者url的值不为空,则加载对应的资源信息,然后合并到defaults中
        if (resource != null) {
            // 将通过resource导入进全局配置文件的properties
            defaults.putAll(Resources.getResourceAsProperties(resource));
        }
        // 获取配置文件中 的需要替换的需要动态配置的属性值
        Properties vars = configuration.getVariables();
        if (vars != null) {
            defaults.putAll(vars);
        }
        // 将配置文件中的properties数据全替换掉
        parser.setVariables(defaults);
        configuration.setVariables(defaults);
    }
}

说明

  1. 获取properties标签的子标签的properties 配置信息
  2. 获取resource属性的参数 信息 如果resource不为空 则将该路径的位置传入工具类返回Properties文件存入defaults 中

image.png

  1. 获取Configuration 对象的配置文件 再将当前解析的properties 文件加入全局配置类。

image.png
image.png

settings 配置标签解析

/**
 * 解析setting文件
 * 将settings中的标签内容解析到Properties对象中  一样的形式储存
 * @param context
 * @return
 */
private Properties settingsAsProperties(XNode context) {
    // 说明setting已经配置过了
    if (context == null) {
        return new Properties();
    }
    // 把<setting name="" value="">标签解析为Properties对象
    Properties props = context.getChildrenAsProperties();
    // 校验每个属性,在 Configuration 中,有相应的 setter 方法,否则抛出 BuilderException 异常
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    // 如果获取的配置的<setting name="" value="">信息,name不在metaConfig中,则会抛出异常
    // 这里metaConfig中的信息是从Configuration类中解析出来的,包含set方法的属性
    // 所以在配置<setting>标签的时候,其name值可以参考configuration类中的属性,配置为小写
    for (Object key : props.keySet()) {
        // 从metaConfig的relector中的setMethods中判断是否存在该属性,setMethods中存储的是可写的属性,
        // 所以这里要到setMethods中进行判断
        if (!metaConfig.hasSetter(String.valueOf(key))) {
            throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
        }
    }
    return props;
}

说明

  1. 将 settings_ _标签的子标签以 properties 文件格式解析返回。
  2. 传入 Configuration 的 class 文件 和 localReflectorFactory 参数传入 返回一个 Configuration 类继续的文件 包含set方法的属性 在使用 hasSetter 方法来判断 configuration 对象中是否存在该配置类的属性同时是否给予修改方法。

image.png
image.png

  1. 如果解析的全部 settings 标签在 configuration 配置中全部 能找到属性 则返回这个 properties 文件。
private void loadCustomVfs(Properties props) throws ClassNotFoundException {
        String value = props.getProperty("vfsImpl");
        if (value != null) {
            String[] clazzes = value.split(",");
            for (String clazz : clazzes) {
                if (!clazz.isEmpty()) {
                    Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
                    configuration.setVfsImpl(vfsImpl);
                }
            }
        }
    }

说明:我们根据标签里面的标签,生成了一个抽象类VFS 的子类,并且赋值到Configuration中。 获取系统的本地文件资源。
将这些配置设置到VfsImpl 属性当中
上面用到元信息对象创建
元信息类MetaClass的构造方法为私有类型,所以不能直接创建,必须使用其提供的forClass方法进行创建:

public class MetaClass {

  	private final ReflectorFactory reflectorFactory;
  	private final Reflector reflector;

  	private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
    	this.reflectorFactory = reflectorFactory;
    	//根据类型创建Reflector
    	this.reflector = reflectorFactory.findForClass(type);
  	}

  	public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
  		//调用构造方法
    	return new MetaClass(type, reflectorFactory);
  	}
  	//...
}

上面代码出现了两个新的类ReflectorFactory和Reflector,MetaClass通过引入这些新类帮助它完成功能。关于这些类,可以先看下hasSetter方法

public boolean hasSetter(String name) {
    //属性分词器,用于解析属性名
    PropertyTokenizer prop = new PropertyTokenizer(name);
    //hasNext返回true,则表明name是一个复合属性
    if (prop.hasNext()) {
        if (reflector.hasSetter(prop.getName())) {
            //为属性创建创建MetaClass
            MetaClass metaProp = metaClassForProperty(prop.getName());
            //再次调用hasSetter
            return metaProp.hasSetter(prop.getChildren());
        } else {
            return false;
        }
    } else {
        // 调用 reflector 的 hasSetter 方法
        return reflector.hasSetter(prop.getName());
    }
}

typeAliases 别名标签 解析

/**
 * typeAliases 
 * @param parent
 */
private void typeAliasesElement(XNode parent) {
    // 如果parent 不为空 
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            // 如果是包
            if ("package".equals(child.getName())) {
                String typeAliasPackage = child.getStringAttribute("name");
                configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
            } else {
                // 解析typeAlias标签
                String alias = child.getStringAttribute("alias");
                String type = child.getStringAttribute("type");
                try {
                    Class<?> clazz = Resources.classForName(type);
                    if (alias == null) {
                        typeAliasRegistry.registerAlias(clazz);
                    } else {
                        typeAliasRegistry.registerAlias(alias, clazz);
                    }
                } catch (ClassNotFoundException e) {
                    throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
                }
            }
        }
    }
}

说明

  1. 这个方法是解析别名标签 将参数别名 和 目的对象注册到 configuration 对象的别名列表中。
  2. 这里有一个优先级问题:包 > 其他 (如果存在包路径 )
  3. 如果是包路径则将包路径传入 registerAliases 注册别名方法中里面实现了通过反射。
/**
 * 通过包名 来将包下所有的 java bean 来注册 别名信息
 *
 * @param packageName
 * @param superType
 */
public void registerAliases(String packageName, Class<?> superType){
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    
    //把该包下的所有类进行加载,把其Class对象放到resolverUtil的matches中
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
    for(Class<?> type : typeSet){
        if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
            // 将其全部解析注册
            registerAlias(type);
        }
    }
}

上面那个方法就是将传入的包路径,传入到resolverUtil工具类中 进行所有类进行加载,然后将加载的Class对象存放到resolverUtil的matchs中 替换返回所有的Class对象解析判断每个类是否为接口、Anonymous类、member类。如果不是则注册到aliase集合中。
image.png
image.png
最后在注册方法中获取TypeAlisaRegistry 类中将注册信息存入TYPE_ALIASES属性中。

将setting解析后的Properties对象取值存入configuration对象中

/**
 * 将上一个解析的Properties文件的key-value值进行设置
 * 如果没有配置则设置为默认值
 * @param props
 */
private void settingsElement(Properties props) {
    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
    configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
    configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
    configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
    configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
    configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
    configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
    configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
    configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
    configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
    configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
    configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
    configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
    configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
    configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
    configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
    configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
    configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
    // configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
    // configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
    configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
    configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
    // configuration.setLogPrefix(props.getProperty("logPrefix"));
    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}

image.png

environments 环境标签解析

/**
 * 解析<environments></environments>标签,
 * 这个标签下面全部的数据
 * XNode 会默认将${}解析掉  然后再获取值
 * @param context
 * @throws Exception
 */
private void environmentsElement(XNode context) throws Exception {
    if (context != null){
        if (environment == null){
            // 获取默认的环境配置
            environment = context.getStringAttribute("default");
        }
        for (XNode child : context.getChildren()) {
            String id = child.getStringAttribute("id");
            // 判断当前id 是否为默认的是否为空
            if (isSpecifiedEnvironment(id)){
                // 解析事务
                TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                // 解析数据源
                DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
                // 返回数据源
                DataSource dataSource = dsFactory.getDataSource();
                Environment.Builder environmentBuilder = new Environment.Builder(id)
                    .transactionFactory(txFactory)
                    .dataSource(dataSource);
                configuration.setEnvironment(environmentBuilder.build());
            }
        }
    }
}

说明

  1. 解析 environments 标签 配置环境包括事务的类别和数据源的类别
  2. 事务主要是包括 JDBC 和 MANAGED
  3. 数据源主要实现了 POOLED 和 UNPOOLED 池化 和 非池化
  4. 将解析的事务类型和数据源通过建造者模式将 Environment 的实例化出该建造者 然后再创建出Environment的对象。

解析事务的方法

/**
     * 解析事务的处理方法
     * @param context
     * @return
     * @throws Exception
     */
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
    if (context != null){
        // 获取事务的类别
        String type = context.getStringAttribute("type");
        // 以properties格式返回子标签内容
        Properties props = context.getChildrenAsProperties();
        // 将事务的类型传入解析别名返回对应的Class 文件 再反射一个对象
        TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
        factory.setProperties(props);
        return factory;
    }
    throw new BuilderException("配置文件需要配置一个事务类型.");

}

说明

  1. 解析事务类型 获取这个标签的type参数中的数据 然后返回标签的所有内容 通过别名来反射当前别名的class 对象 然后再调用 newInstance 来返回一个事务的工厂。(别名列表注册了key value 的别名:class 类)

image.pngimage.png

  1. 将反射处理的工厂的实例返回一个事务工厂父类 将子标签设置如工厂** 通过事务工厂返回设置别名的事务工厂**

image.png
这里为什么可以直接resolveAlias 到事务的类型? 原因是我们实例化Configuration类的时候已经将别名注册 硬编码注册了一些Class类
image.png

解析数据源的解析

private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    if (context != null){
        // 获取DataSource标签的一个类型
        String type = context.getStringAttribute("type");
        // 将子标签的数据读取出来
        Properties props = context.getChildrenAsProperties();
        // 实现池化和非池化两种连接方式  返回一个实例
        DataSourceFactory factory = (DataSourceFactory) resolveAlias(type).newInstance();
        factory.setProperties(props);
        return factory;
    }
    throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}

解析数据源方法也是一样的只是factory.setProperties 方法的实现不一样
image.png

/**
 * 非池化设置Properties参数
 * @param properties
 */
@Override
public void setProperties(Properties properties) {
    Properties driverProperties = new Properties();
    // 将DataSource传入 解析成一个对象  参数对象
    MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
    for (Object key : properties.keySet()) {
        String propertyName = (String) key;
        // 驱动需要另外设置参数
        if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
            String value = properties.getProperty(propertyName);
            driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);            } else if (metaDataSource.hasSetter(propertyName)) {
            String value = (String) properties.get(propertyName);
            Object convertedValue = convertValue(metaDataSource, propertyName, value);
            metaDataSource.setValue(propertyName, convertedValue);
        } else {
            String value = (String) properties.get(propertyName);
            Object convertedValue = convertValue(metaDataSource, propertyName, value);
            metaDataSource.setValue(propertyName, convertedValue);
            throw new DataSourceException("Unknown DataSource property: " + propertyName);
        }
    }
    if (driverProperties.size() > 0) {
        metaDataSource.setValue("driverProperties", driverProperties);
    }
}

说明

  1. 使用MetaDataSource方法实现判断数据池中是否存在这个属性并且开放了设置该属性的setter 只有存在该属性才能赋值 不存在该属性值是不能赋值 metaObject 的作用大致就是这样了
  2. 这个有一个新的学习思路 就是我们给某个类存参数 需要先判断是否存在set方法 如果存在才能存储,如果不存在就需要抛出异常。

目前获取到了DataSource对象和Transaction对象 需要包装到Environment类中
image.png

解析typeHandlers 类处理器

什么叫typeHandlers 类处理器?

  1. 类处理器就是映射jdbc 中的数据类型 和 java中的数据类型 从而我们要有一个类型转换 同时也是类型映射 比如 插入数据的时候 Date ==> time 类型 就是映射作用 思想两个语言端的数据类型转换。
 /**
 * 解析类型处理器
 *  typeHandlers类型处理器的作用是在设置参数或取结果集时,映射DB和java类型。
 *  mybatis为我们提供了大多常见的默认类型处理器。
 * @param parent
 */
private void typeHandlerElement(XNode parent) {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            // 子节点为package时,获取其name属性的值,然后自动扫描package下的自定义typeHandler
            if ("package".equals(child.getName())) {
                String typeHandlerPackage = child.getStringAttribute("name");
                typeHandlerRegistry.register(typeHandlerPackage);
            } else {
                // 子节点为typeHandler时, 可以指定javaType属性, 也可以指定jdbcType, 也可两者都指定
                // javaType 是指定java类型
                // jdbcType 是指定jdbc类型(数据库类型: 如varchar)
                String javaTypeName = child.getStringAttribute("javaType");
                String jdbcTypeName = child.getStringAttribute("jdbcType");
                // handler就是我们配置的typeHandler
                String handlerTypeName = child.getStringAttribute("handler");
                // resolveClass方法就是我们上篇文章所讲的TypeAliasRegistry里面处理别名的方法
                Class<?> javaTypeClass = resolveClass(javaTypeName);
                // JdbcType是一个枚举类型,resolveJdbcType方法是在获取枚举类型的值
                JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
                Class<?> typeHandlerClass = resolveClass(handlerTypeName);
                // 注册typeHandler, typeHandler通过TypeHandlerRegistry这个类管理
                if (javaTypeClass != null) {
                    if (jdbcType == null) {
                        typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
                    } else {
                        typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
                    }
                } else {
                    typeHandlerRegistry.register(typeHandlerClass);
                }
            }
        }
    } 
}

说明

  1. 数据类型处理器 解析也有一个优先级 package > 其他
  2. 获取标签中的每个节点的数据类型 同时系统会默认将基本的数据类型 进行转换 用户可以使用自定义的 数据类型 进行转换映射。 将其注册到typeHandlersRegistry 映射集中。

这个TypeHandlersRegistry 和 TypeAliasRegistry 是相似的 先再type包下 创建需要转换的类转换 我这里就展示一个比较常见的转换

public class IntegerTypeHandler extends BaseTypeHandler<Integer> {

	@Override
	public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
			throws SQLException {
		ps.setInt(i, parameter);
	}

	@Override
	public Integer getNullableResult(ResultSet rs, String columnName) throws SQLException {
		int result = rs.getInt(columnName);
		return result == 0 && rs.wasNull() ? null : result;
	}

	@Override
	public Integer getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
		int result = rs.getInt(columnIndex);
		return result == 0 && rs.wasNull() ? null : result;
	}

	@Override
	public Integer getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
		int result = cs.getInt(columnIndex);
		return result == 0 && cs.wasNull() ? null : result;
	}
}

常见转换的JDBC和Java之间的转换
类型处理器 Java 类型 JDBC 类型
image.png
实际注册方法

private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
    if (javaType != null) {
        Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
        if (map == null) {
            map = new HashMap<JdbcType, TypeHandler<?>>();
            TYPE_HANDLER_MAP.put(javaType, map);
        }
        map.put(jdbcType, handler);
    }
    ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
}
public void register(Class<?> typeHandlerClass) {
    boolean mappedTypeFound = false;
    MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
        for (Class<?> javaTypeClass : mappedTypes.value()) {
            register(javaTypeClass, typeHandlerClass);
            mappedTypeFound = true;
        }
    }
    if (!mappedTypeFound) {
        register(getInstance(null, typeHandlerClass));
    }
}
  /**
 * 都配置了
 * @param javaType
 * @param jdbcType
 * @param handler
 */
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
    if (javaType != null) {
        Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
        if (map == null) {
            map = new HashMap<JdbcType, TypeHandler<?>>();
            TYPE_HANDLER_MAP.put(javaType, map);
        }
        map.put(jdbcType, handler);
    }
    ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
}

一个JDBCType对应一个TypeHandler类型
可以看到对于一个Java类型是可以有多个JDBC类型相对应的,所以会存在多个TypeHandler,在这种情况下需要根据传入参数的javaType以及其在数据库中对应JdbcType一起来选定一个TypeHandler进行处理(mybatis如何知道每个字段对应的数据库字段类型的?).下面来看一个具体的TypeHandler的实现:实现Java中Date类型和jdbc中JdbcType.Time类型转换的TimeOnlyTypeHandler.JDBC中的Time类型只记录时分秒,所以如果我们传入一个代表2017-11-21 11:55:59的Date对象,那么数据库中存储的时间是11:55:59.

public class TimeOnlyTypeHandler extends BaseTypeHandler<Date> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setTime(i, new Time(parameter.getTime()));
  }

  @Override
  public Date getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    java.sql.Time sqlTime = rs.getTime(columnName);
    if (sqlTime != null) {
      return new Date(sqlTime.getTime());
    }
    return null;
  }

  @Override
  public Date getNullableResult(ResultSet rs, int columnIndex)
      throws SQLException {
    java.sql.Time sqlTime = rs.getTime(columnIndex);
    if (sqlTime != null) {
      return new Date(sqlTime.getTime());
    }
    return null;
  }

  @Override
  public Date getNullableResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    java.sql.Time sqlTime = cs.getTime(columnIndex);
    if (sqlTime != null) {
      return new Date(sqlTime.getTime());
    }
    return null;
  }
}

这就实现了类型转换 。

Mapper 标签

使用mybatis的xml方式,我们需要在mybatis.xml的mappers标签下配置告诉mybatis映射的SqlMapper的xml文件的哪里,所以我们需要注册 mappers和Mapper的映射表

 /**
 * 解析<mappers>标签
 * 使用mybatis的xml方式,我们需要在mybatis.xml的mappers标签下配置告诉mybatis映射的Sql的那里
 * mappers有四种方式来加载映射的sql。
 * 1.url
 * 2.resource
 * 3.class
 * 4.package
 * 优先级  package、resource、url、class
 * @param parent  mappers标签对应的XNode对象
 * @throws Exception
 */
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        // 获得全部标签
        for (XNode child : parent.getChildren()) {
            // 加载package的标签
            if ("package".equals(child.getName())) {
                String mapperPackage = child.getStringAttribute("name");
                configuration.addMappers(mapperPackage);
            } else {
                // 下面只会使用一个的方式来解析
                String resource = child.getStringAttribute("resource");
                String url = child.getStringAttribute("url");
                String mapperClass = child.getStringAttribute("class");

                // resource 和 url 需要使用解析类 来实现解析
                if (resource != null && url == null && mapperClass == null) {
                    ErrorContext.instance().resource(resource);
                    InputStream inputStream = Resources.getResourceAsStream(resource);
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                    mapperParser.parse();

                } else if (resource == null && url != null && mapperClass == null) {
                    ErrorContext.instance().resource(url);
                    InputStream inputStream = Resources.getUrlAsStream(url);
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                    mapperParser.parse();

                    // 通过mapperClass 的方法直接将该类直接返回Class对象
                } else if (resource == null && url == null && mapperClass != null) {
                    Class<?> mapperInterface = Resources.classForName(mapperClass);
                    configuration.addMapper(mapperInterface);

                } else {
                    throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                }
            }
        }
    }
}

说明:这里需要Mapper的xml解析器XMLMapperBuilder 这里获取配置的路径 实例化一个XMLMapperBuilder 对象 来解析 mapper标签的 调用XMLMapperBuilder方法中的parse方法来实现解析mapper映射文件。解析Mapper文件我们下一个阶段解析。
image.png
这里我们只看resource的方式 来实现 为什么package的优先级比较高 实现是一个包 本身级别就高 再者我们项目里面的Mapper.xml文件就比较多。
image.png

mybatis-config.xml 配置文件解析的主要流程


image.png

2.1.6、解析全局配置文件图

mybatis-config.xml 的标签详细介绍流程

2.2、mapper.xml文件解析

映射文件的解析过程是配置文件解析过程的一部分,MyBatis会在解析配置文件的过程中对映射文件进行解析。解析逻辑封装在XMLConfigBuilder中的mapperElement方法中
代码的主要逻辑是遍历mappers的子节点,并根据节点属性值判断通过何种方式加载映射文件或映射信息。这里把配置在注解中的内容称为映射信息,以XML为载体的配置称为映射文件。

映射方式

  1. 从文件系统中加载映射文件;
  2. 通过URL的方式加载映射文件;
  3. 通过mapper接口加载映射信息,映射信息可以配置在注解中,也可以配置在映射文件中;
  4. 通过包扫描的方式获取到某个包下的所有类,并使用第三种方式为每个类解析映射信息。
2.2.1、XMLMapperBuilder 类解析映射文件

image.png

image.png
image.png

public void parse() {
    // 检测映射文件是否已经被解析过
    if (!configuration.isResourceLoaded(resource)) {
        // 解析mapper节点
        configurationElement(parser.evalNode("/mapper"));
        // 添加资源路径到“已解析资源集合”中 将当前资源增加到已解析的集合中避免多次解析
        configuration.addLoadedResource(resource);
        // 通过命名空间绑定Mapper接口
        bindMapperForNamespace();
    }

    // 处理未完成解析的节点
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
}

image.png
image.png
说明

  1. 解析Mapper.xml 文件的第一步就是获取命名空间判断是否匹配当前的命名空间 如果匹配才会进行解析
  2. 然后解析cache-ref 标签
  3. 解析cache 标签
  4. 解析resultMap 结果集
  5. 解析sql的语句片段
  6. 解析CURD的4种方式

image.png

1、解析命名空间

image.png
MapperBuilderAssistant 类中的方法

public void setCurrentNamespace(String currentNamespace) {
    // 如果命名空间为空
    if (currentNamespace == null) {
        throw new BuilderException("这个映射集 Mapper的命名空间为空!!");
    }

    if (this.currentNamespace != null && !this.currentNamespace.equals(currentNamespace)) {
        throw new BuilderException("错误的命名空间! '"
                + this.currentNamespace + "' but found '" + currentNamespace + "'.");
    }

    this.currentNamespace = currentNamespace;
}

2、解析cache-ref标签

image.png
说明

  1. CacheRefElement解析cache-ref标签
  2. 获取当前命名空间 和命名空间的value 路径参数存储到configuration中的CacheRefMap中。
  3. 通过builderAssistant助手和当前命名空间来创建CacheRefResolver 对象来实现resolveCacheRef 解析
  4. 调用resolveCacheRef方法 - > userCahceRef 方法

image.png
通过命名空间来获取当前的 二级缓存Cache 然后将其设置为当前使用的缓存,并且将解析bool设置为false。

3、解析cache标签
/**
 * 解析cache 标签的实现方法
 * 	<cache
 * 		eviction="FIFO"
 * 		flushInterval="60000"
 * 		size="512"
 * 		readOnly="true"/>
 * 	一级缓存默认开启  二级缓存配置在映射文件中  使用者需要先开启二级缓存
 * 	同时可以不需要配置
 * @param context
 */
private void  cacheElement(XNode context) {
    if (context != null) {
        // 获取各种属性
        String type = context.getStringAttribute("type", "PERPETUAL");
        Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
        String eviction = context.getStringAttribute("eviction", "LRU");
        Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
        Long flushInterval = context.getLongAttribute("flushInterval");
        Integer size = context.getIntAttribute("size");
        boolean readWrite = !context.getBooleanAttribute("readOnly", false);
        boolean blocking = context.getBooleanAttribute("blocking", false);
        // 获取子节点配置
        Properties props = context.getChildrenAsProperties();
        // 构建缓存对象
        builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
}

image.png
说明

  1. 通过builderAssistant 对象调用useNewCache 方法来使用一个新的缓存对象 该方法是通过当前命名空间 来建造出一新的Cache对象 同时设置参数 并且build方法来将cache包装

一级缓存
一级缓存是Session级别,一般一个SqlSession对象会使用一个Executor对象来完成会话操作,Executor对象会维护一个Cache缓存,以提高查询性能,减少对DB的压力。
image.png
二级缓存
如果用户配置了cacheEnabled=true,那么在为SqlSession对象创建Executor对象时,会对Executor对象加上一个装饰者 CachingExecutor,这时SqlSession使用CachingExecutor对象完成操作请求。
CachingExecutor对查询请求,会先判断该查询请求在Application级别的二级缓存中是否有缓存结果

  • 有查询结果
  • 直接返回缓存结果
  • 缓存未命中

再交给真正的Executor对象来完成查询操作,之后CachingExecutor会将真正Executor返回的查询结果放置到缓存中,然后再返回给用户
MyBatis二级缓存设计灵活,可以使用:

  • MyBatis自己定义的二级缓存实现
  • 实现org.apache.ibatis.cache.Cache接口自定义缓存
  • 使用第三方内存缓存库,如Memcached

image.png

image.png
缓存是通过装饰器模式来实现的
image.png
通过delegate 来实现一个装饰者
Cache:Cache接口是缓存模块的核心接口,定义了缓存的基本操作。

public interface Cache {
 
  String getId();//缓存实现类的id
 
  void putObject(Object key, Object value);//往缓存中添加数据,key一般是CacheKey对象
 
  Object getObject(Object key);//根据指定的key从缓存获取数据
 
  Object removeObject(Object key);//根据指定的key从缓存删除数据
 
  void clear();//清空缓存
 
  int getSize();//获取缓存的个数
  
  ReadWriteLock getReadWriteLock();//获取读写锁
 
}

PerpetualCache:缓存的基础实现类,使用HashMap来实现缓存功能。

public class PerpetualCache implements Cache {
 
  private final String id;
 
  private Map<Object, Object> cache = new HashMap<>();
 
...

BlockingCache:阻塞版本的装饰器,保证只有一个线程到数据库去查指定key的数据。

public class BlockingCache implements Cache {
 
  //阻塞的超时时长
  private long timeout;
  // 被装饰的底层对象,一般是PerpetualCache
  private final Cache delegate;
  // 锁对象集,粒度到key值
  private final ConcurrentHashMap<Object, ReentrantLock> locks;
 
  public BlockingCache(Cache delegate) {
    this.delegate = delegate;
    this.locks = new ConcurrentHashMap<>();
  }
 
 
  @Override
  public Object getObject(Object key) {
    acquireLock(key);// 根据key获得锁对象,获取锁成功加锁,获取锁失败阻塞一段时间重试
    Object value = delegate.getObject(key);
    if (value != null) {// 获取数据成功的,要释放锁
      releaseLock(key);
    }        
    return value;
  }
 
  private ReentrantLock getLockForKey(Object key) {
    ReentrantLock lock = new ReentrantLock();// 创建锁
    ReentrantLock previous = locks.putIfAbsent(key, lock);// 把新锁添加到locks集合中,如果添加成功使用新锁,如果添加失败则使用locks集合中的锁
    return previous == null ? lock : previous;
  }
  
  // 根据key获得锁对象,获取锁成功加锁,获取锁失败阻塞一段时间重试
  private void acquireLock(Object key) {
	// 获得锁对象
    Lock lock = getLockForKey(key);
    if (timeout > 0) {// 使用带超时时间的锁
      try {
        boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
        if (!acquired) {// 如果超时抛出异常
          throw new CacheException("Couldn't get a lock in " + timeout + " for the key " +  key + " at the cache " + delegate.getId());  
        }
      } catch (InterruptedException e) {
        throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
      }
    } else {// 使用不带超时时间的锁
      lock.lock();
    }
  }
  
  private void releaseLock(Object key) {
    ReentrantLock lock = locks.get(key);
    if (lock.isHeldByCurrentThread()) {
      lock.unlock();
    }
  }
 
...

private final Cache delegate;

image.png

如果我们设置的是默认的cache缓存 就直接将缓存设置
image.png
最后返回cache 将该装饰者模式包装好的cache设置到configuration对象中 返回的cache对象就是头结点。
上面就是实现装饰者模式 :饰者是继承的有力补充,比继承灵活,不改变原有对象的情况下动态地给一个对象扩展功能,即插即用。

4、解析resultMap标签
 /**
 * 解析结果集的参数 集合
 * <resultMap id="authorResult" type="User">
 *      <id property="id" column="id"/>
 *      <result property="name" column="name"/>
 *  </resultMap>
 * @param list
 * @throws Exception
 */
private void resultMapElements(List<XNode> list) throws Exception {
    for (XNode resultMapNode : list) {
        try {
            resultMapElement(resultMapNode);
        } catch (IncompleteElementException e) {
            // ignore, it will be retried
        }
    }
}

一个一个进行解析
真正实现解析resultMap的方法 resultMapElement 方法

/**
 * 解析 resultMap 方法
 * 	<resultMap id="articleResult" type="Article">
 * 		<id property="id" column="id"/>
 * 		<result property="title" column="article_title"/>
 * 		<association property="article_author" javaType="Author">
 * 			<id property="id" column="author_id"/>
 * 			<result property="name" column="author_name"/>
 * 		</association>
 * 	</resultMap>
 * @param resultMapNode
 * @return
 */
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception{
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    // 查找是否能 找到 不能则设置为默认
    // 获取type属性
    String id = resultMapNode.getStringAttribute("id",
            resultMapNode.getValueBasedIdentifier());
    String type = resultMapNode.getStringAttribute("type",
            resultMapNode.getStringAttribute("ofType",
                    resultMapNode.getStringAttribute("resultType",
                            resultMapNode.getStringAttribute("javaType"))));
    String extend = resultMapNode.getStringAttribute("extends");
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    // 解析type属性对应的类型
    Class<?> typeClass = resolveClass(type);
    if (typeClass == null) {
        typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }
    // 结果集 的列表
    List<ResultMapping> resultMappings = new ArrayList<>();
    // 将以前的结果集全部添加进来
    resultMappings.addAll(additionalResultMappings);
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
        if ("constructor".equals(resultChild.getName())) {
            // 解析constructor节点,并生成相应的 ResultMapping
            // 目前未实现该标签
            // processConstructorElement(resultChild, typeClass, resultMappings);
            throw new BuilderException("目前未实现constructor 构造器标签");
        } else if ("discriminator".equals(resultChild.getName())) {
            // 解析discriminator节点
            // discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
            throw new BuilderException("目前未实现discriminator 鉴别器标签");
        } else {
            List<ResultFlag> flags = new ArrayList<>();
            if ("id".equals(resultChild.getName())) {
                //添加ID到flags集合中
                flags.add(ResultFlag.ID);
            }
            // 解析id和property节点,并生成相应的ResultMapping
            resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
        }
    }
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, resultMappings, autoMapping);
    try {
        return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
        configuration.addIncompleteResultMap(resultMapResolver);
        throw e;
    }
}

说明

  1. 获取resultMap的Id 默认拼装所有父节点的id 或 value 或 property
// 获取 ID , 默认值会拼装所有父节点的 id 或 value 或 property。
String id=resultMapNode.getStringAttribute("id",resultMapNode.getValueBasedIdentifier());

这里涉及到 XNode 对象中的两个函数:getStringAttribute()以及getValueBasedIdentifier()

/**
 * 生成元素节点的基础 id
 * @return
 */
public String getValueBasedIdentifier() {
  StringBuilder builder = new StringBuilder();
  XNode current = this;
  // 当前的节点不为空
  while (current != null) {
    // 如果节点不等于 this, 则在0之前插入 _ 符号, 因为是不断的获取父节点的, 因此是插在前面
    if (current != this) {
      builder.insert(0, "_");
    }
    // 获取 id, id不存在则获取value, value不存在则获取 property。
    String value = current.getStringAttribute("id",
        current.getStringAttribute("value",
            current.getStringAttribute("property", null)));
    // value 非空, 则将.替换为_, 并将value的值加上 []
    if (value != null) {
      value = value.replace('.', '_');
      builder.insert(0, "]");
      builder.insert(0,
          value);
      builder.insert(0, "[");
    }
    // 不管 value 是否存在, 前面都添加上节点的名称
    builder.insert(0, current.getName());
    // 获取父节点
    current = current.getParent();
  }
  return builder.toString();
}
  1. 解析结果集中的类型,结果集的类型, 对应的是一个 JavaBean 对象。通过反射来获得该类型。
// 获取type, type 不存在则获取 ofType, ofType 
// 不存在则获取 resultType, resultType 不存在则获取 javaType
String type = resultMapNode.getStringAttribute("type",
    resultMapNode.getStringAttribute("ofType",
        resultMapNode.getStringAttribute("resultType",
            resultMapNode.getStringAttribute("javaType"))));
// ... ...
// 获取 type 对应的 Class 对象
Class<?> typeClass = resolveClass(type);

  1. 获取继承结果集和自动映射
String extend = resultMapNode.getStringAttribute("extends");
   Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");

这两个属性在配置XML的时候,显得有些可有可无。
image.png
image.png

5、解析sql语句片段
/**
 * 如果有显式声明 databaseId ,那只有符合当前全局 databaseId 的 SQL 片段会提取;
 * 如果没有声明 databaseId ,则会全部提取。
 * 解析了两遍 SQL 片段,而且在每一次循环解析中,都会判断一次 SQL 片段是否匹配当前 databaseId ,
 * 匹配的话就会放到一个 sqlFragments 的 Map 中
 * @param list
 * @param requiredDatabaseId
 */
private void sqlElement(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
        String databaseId = context.getStringAttribute("databaseId");
        String id = context.getStringAttribute("id");
        id = builderAssistant.applyCurrentNamespace(id, false);
        // 鉴别当前SQL片段是否匹配
        if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
            sqlFragments.put(id, context);
        }
    }
}

image.png
image.png

7、XMLMapperBuilder总体流程

XMLMapperBuilder标签解析总体流程


image.png

2.2.2、XMLStatementBuilder类解析

image.png
XMLStatementBuilder创建MappedStatement的大致流程

  1. 获取SQL节点的属性值
  2. 使用XMLIncludeTransformer解析include节点
  3. 创建MappedStatement实例,保存到Configuration#mappedStatements中
  4. 默认使用XMLLanguageDriver解析SQL节点得到SqlSource,使用各种类型的NodeHandler、SqlNode解析SQL节点。SqlSource有两种:**DynamicSqlSource **SQL语句有${param0}(文本替换,可能是一段SQL)或者使用了if/where(运行时决定是否拼接SQL片段)等节点需要运行时才能解析出要执行的SQL。**RawSqlSource **使用了#{param1}占位符或者没有使用,就是一个完整SQL,不需要运行时解析得到SQL。
  5. 创建MappedStatement,保存到Configuration#mappedStatements中
/**
 * 解析<select>\<insert>\<update>\<delete>子标签
 * 步骤解析:
 * parseStatementNode() 是 XMLStatementBuilder 解析的入口,首先,他会去找CURD标签上的唯一id,
 * 然后就是databaseId,databaseId是数据库厂商标识,MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId
 * 的语句,如果带和不带的语句都有,则不带的会被忽略,一般选择databaseId的时候是需要配置多数据库厂商的时候。
 * 在解析完databaseId 之后就是解析flushCache、useCache等标签,默认情况下,如果是select语句,则是开启缓存的
 *
 * 说明:我们再来是没有设置databaseId的, 所以实现会将所有的语句全部扫描 然后进行解析。
 */
public void parseStatementNode() {
    // 获取statement的id属性(特别关键的值)
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    // 将全部sql判断扫描
    // 解析<select|update|delete|insert>标签属性
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");

    // 获取入参类型
    String parameterType = context.getStringAttribute("parameterType");
    // 别名处理,获取入参对应的Java类型
    Class<?> parameterTypeClass = resolveClass(parameterType);
    // 获取ResultMap
    String resultMap = context.getStringAttribute("resultMap");
    // 获取结果映射类型
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    // 别名处理,获取返回值对应的Java类型
    Class<?> resultTypeClass = resolveClass(resultType);
    String resultSetType = context.getStringAttribute("resultSetType");

    // 设置默认StatementType为Prepared,该参数指定了后面的JDBC处理时,采用哪种Statement
    // 这里默认大写
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    String nodeName = context.getNode().getNodeName();
    // 解析sql命令类型是什么  确定crud中的哪一类
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    // 是否查询语句
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    // <include>标签解析
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.
    // 解析<selectKey>标签  当前版本框架还未实现selectKey标签
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    // 创建SqlSource,解析SQL,封装SQL语句(未参数绑定)和入参信息
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

    /**
     * 我们不实现主键生成
     */
//        String resultSets = context.getStringAttribute("resultSets");
//        String keyProperty = context.getStringAttribute("keyProperty");
//        String keyColumn = context.getStringAttribute("keyColumn");
//        KeyGenerator keyGenerator;
//        String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
//        keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
//        if (configuration.hasKeyGenerator(keyStatementId)) {
//            keyGenerator = configuration.getKeyGenerator(keyStatementId);
//        } else {
//            keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
//                    configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
//                    ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
//        }

    // 通过构建者助手,创建MappedStatement对象
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered,databaseId, langDriver);

}

说明

  1. 获取当前节点标签下的全部属性的参数值
  2. 使用LanguageDriver创建SqlSource对象维护一个动态的sql。

image.png
image.png

1、Include标签解析

解析Include标签

创建XMLIncludeTransformer对象 来解析include标签

// 先获取全局的配置变量,也就是mybatis-config.xml配置文件中的<properties></properties>下配置的变量。
// 然后再调用重载方法进行处理
public void applyIncludes(Node source) {
    Properties variablesContext = new Properties();
    Properties configurationVariables = configuration.getVariables();
    if (configurationVariables != null) {
        variablesContext.putAll(configurationVariables);
    }
    applyIncludes(source, variablesContext, false);
}
/**
 * <sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
 * <select id="selectUsers" resultType="map">
 *   select
 *     <include refid="userColumns"><property name="alias" value="t1"/></include>,
 *   from some_table t1
 *     cross join some_table t2
 * </select>
 * @param source
 * @param variablesContext
 * @param included
 */
// 递归处理include标签中的内容
private void applyIncludes(Node source, final Properties variablesContext, boolean included) {
    // 获取结点为include的节点
    if (source.getNodeName().equals("include")) {
        // 查找refid对应的sql片段,并返回sql片段对象的深拷贝
        Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);
        // 合并include中配置的局部变量和mybatis配置文件中生命的全局变量
        Properties toIncludeContext = getVariablesContext(source, variablesContext);
        applyIncludes(toInclude, toIncludeContext, true);
        if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
            toInclude = source.getOwnerDocument().importNode(toInclude, true);
        }
        // 将include替换为sql
        source.getParentNode().replaceChild(toInclude, source);
        while (toInclude.hasChildNodes()) {
            toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
        }
        // 移除include标签
        toInclude.getParentNode().removeChild(toInclude);
    } else if (source.getNodeType() == Node.ELEMENT_NODE) {// 替换<sql></sql>标签属性的占位符
        if (included && !variablesContext.isEmpty()) {
            // 用真实值替换节点属性中的占位符
            NamedNodeMap attributes = source.getAttributes();
            for (int i = 0; i < attributes.getLength(); i++) {
                Node attr = attributes.item(i);
                attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext));
            }
        }
        // 递归处理标签下所有子标签的占位符
        NodeList children = source.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            applyIncludes(children.item(i), variablesContext, included);
        }
    } else if (included && source.getNodeType() == Node.TEXT_NODE
            && !variablesContext.isEmpty()) {
        // 用真实值替换文本或CDATA区中的占位符
        source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
    }
}

说明

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
<select id="selectUsers" resultType="map">
  select
    <include refid="userColumns"><property name="alias" value="t1"/></include>,
  from some_table t1
    cross join some_table t2
</select>

  1. 获取到configuration对象存储的properties标签中的数据

image.png

  1. 判断标签中的数据是否为空,如果不为null则复制全部数据待后面使用
  2. 判断当前节点是include标签还是element标签还是文本标签
  3. 如果是include标签则获取到refid属性的参数 解析到里面的参数 存在占位符则替换掉 并且拼接命名空间 到configuration对象存储的SQLFragments的Map中通过refid查找XNode节点 通过深拷贝返回Node节点、因为一个sql片段会被多次引用 所有一定是原始对象的拷贝。
  4. 获取include中的参数属性 name和value的属性获取 然后和configuration对象中的数据合并返回

image.png

  1. 递归处理include标签中的数据防止 include的标签中嵌套了标签

image.png
替换sql的意思是将refid的节点替换include中的数据 通过获取的sql片段来替换

  1. 将inclue标签移除 保留sql片段
  2. 如果_替换标签属性的占位符 因为引用sql标签的深克隆是会保留占位符的所以需要进行替换_

image.png

  1. 获取结点下的全部节点 通过遍历Node节点来替换节点属性中的占位符 同时处理完递归处理所有子标签的占位符
  • 替换SQL标签
<select id="selectUsers" resultType="map">
  select
    <sql id="userColumns"> t1.id,t1.username,t1.password </sql>,
  from some_table t1
    cross join some_table t2
</select>
  • 将sql的内容插入到select节点中对应的位置
<select id="selectUsers" resultType="map">
  select
    t1.id,t1.username,t1.password
    <sql id="userColumns"> t1.id,t1.username,t1.password </sql>,
  from some_table t1
    cross join some_table t2
</select>

  • 移除sql多余的节点标签
<select id="selectUsers" resultType="map">
  select
    t1.id,t1.username,t1.password,
  from some_table t1
    cross join some_table t2
</select>

执行流程

  1. 解析include元素
  2. 递归解析include元素

image.png

  1. 查找SQL片段

image.png

  1. include子节点属性值替换占位符

image.png

2、SelectKey标签解析

解析selectKey标签

目前还未实现解析selectKey标签

3、LanguageDriver 创建

通过LanguageDriver驱动来创建SqlSource动态SQL

image.png

/**
* 解析动态sql语句 标签
* 解析select\insert\ update\delete标签中的SQL语句,
* 最终将解析到的SqlNode封装到MixedSqlNode中的List集合中
* 将带有${}号的SQL信息封装到TextSqlNode
* 将带有#{}号的SQL信息封装到StaticTextSqlNode
* 	将动态SQL标签中的SQL信息分别封装到不同的SqlNode中
* @return
*/
public SqlSource parseScriptNode() {
    //解析DML标签下的子节点,封装成MixedSqlNode
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource;
    if (isDynamic) {
        //只创建DynamicSqlSource对象。完整可执行的sql,需要在调用getBoundSql(Parameter)方法时才能组装完成。
        sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
        /**
        * 调用 SqlSourceBuilder类将"#{xxx}“ 替换为占位符”?",并绑定ParameterMapping,
        * 最后返回的RawSqlSource中持有一个由SqlSourceBuilder构建的SqlSource对象。
        */
        sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
}

说明

  1. 最终将解析到的SqlNode封装到MixedSqlNode中的List集合中 所以先判断是否为动态语句 通过parseDynamicTags方法判断 同时返回一个MixedSqlNode对象最后的结果对象

image.png

  1. 获取该节点下的全部节点遍历 获取子节点下的全部标签解析存储到指定的列表中存储 判断是处理文本节点还是元素节点

image.png
image.png
如果是文本节点 获取文本的内容存储到TextSQLNode对象中保存标签将该标签下的文本对象add到contents的List中存储 统一返回
如果是元素节点则通过节点名来获取SQL标签的处理器 同时设置动态标签是dynamic的 将List列表传入MIE的SQLNode对象存储并且返回。 MixedSqlNode对象是存储SqlNode结合的 就是存储哪些SQLNode集合
contents可能包含 StaticTextSqlNode和IfSqlNode或者WhereSqlNode或者其他。 当sql语句中存在 的话就是 d y n a m i c 的通过 T e x t S Q L N o d e 来判断是否为动态标签如果存在 {}的话就是dynamic 的 通过TextSQLNode来判断是否为动态标签 如果存在 的话就是dynamic的通过TextSQLNode来判断是否为动态标签如果存在{}则加入到contents动态标签中 以TextSQLNode对象存储到contents中 如果不存在 的话以 S t a t i c T e x t S Q L N o d e 对象存储到 c o n t e n t s 中记住存在 {}的话以StaticTextSQLNode对象存储到contents 中 记住存在 的话以StaticTextSQLNode对象存储到contents中记住存在{}就是dynamic 的需要存储到TextSQLNode类中 如果是除${}的话就是static的sql语句只需要使用apply 字符串追加操作

  • StaticTextSqlNode 最简单的SQLNode 功能只能拼接到context上下文
  • image.png
  • TextSQLNode 表示包含 占位符的动态 S Q L 节点里面会使用 G e n e r i c T o k e n P a r s e r 解析“ {}占位符的动态SQL节点 里面会使用GenericTokenParser解析“ 占位符的动态SQL节点里面会使用GenericTokenParser解析{}”占位符,并直接替换成传入的实际参数值。实际参数值获取逻辑在内部BindingTokenParser.handleToken中。

image.png

  • IfSqlNode 看传入的参数值是否有满足if test里的表达式,满足将SQL片段合并到context上下文中。若不满足test则过滤掉这一部分SQL片段,不添加到context中。image.png
  • image.png
  • image.png

如果存在 等标签 需要执行多次 存储到contents中 通过里面的IfHandler处理器来对if语法的处理 是否满足if 里的内容 满足则将if标签以IfSqlNode 对象put到contents 中

  • WhereSqlNode 、SetSqlSet 节点 标签的 SqlNode 实现类,继承至WhereSqlNode。
    标签的 SqlNode 实现类,继承至SetSqlNode。WhereSqlNode会传入prefix=WHERE和prefixesToOverride=["AND ","OR ",“AND\n”, “OR\n”, “AND\r”, “OR\r”, “AND\t”, “OR\t”]
  • TrimSqlNode 标签的 SqlNode 实现类
  • image.png

image.png
如果是动态标签就add到系统的contents的List中然后存储到MixedSqlNode对象中 以备后面使用。
image.png

  1. 通过判断是否为动态标签解析 如果是动态标签则实例化DynamicSQLSource 对象存储sql 调用getBoundSql才会使用
  2. 获取到MixedSqlNode 对象 判断当前节点是为dynamic节点还是#{}节点 如果是dynamic节点则需要都要DynamicSQLSource对象 #{}节点只需要调用RawSqlSource节点维护。

image.png

  1. 调用MapperBuilderAssistant构造助手创建MappedStatement 对象 使用建造者模式创建一个MappedStatement对象 同时获取ParameterMap对象设置到Map配对Statement对象中 最后在将创建的MappedStatement对象存储到configuration对象中。
  2. 将resource 添加到configuration对象中

image.png

bindMapperForNamespace 绑定Mapper接口

image.png

parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();

这几个方法就是解析还未解析的方法 内容不太多 重新解析

这里就已经将Mapper解析完成

image.png
image.png

4、XMLScriptBuilder类的流程

5、XMLStatementBuilder总体流程

2.3、SqlSession会话获取

image.png

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        // 获取数据源环境信息
        final Environment environment = configuration.getEnvironment();
        // 获取事务工厂
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        // 获取JdbcTransaction或者ManagedTransaction  设置参数
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        // 创建Executor执行器
        final Executor executor = configuration.newExecutor(tx, execType);
        // 创建DefaultSqlSession
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
        closeTransaction(tx); // may have fetched a connection so lets call close()
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

说明

  1. openSessionFromDataSource 方法需要获取数据源、事务工厂、JdbcTransaction管理 、创建Executor执行器 最后将configuration对象和执行器 是否自动提交传入DefaultSqlSession类中创建对象
  2. 返回事务

image.png 通过事务工厂 返回一Transaction 事务实例

  1. configuration.newExecutor(tx, execType); 创建Executor执行器。
  2. 最后创建一个DefaultSqlSession 实例对象,出现异常则需要关闭事务。
  3. 放回SqlSession 对象

2.4、Mapper代理对象获取

image.png

 @SuppressWarnings("unchecked")
// getMapper方法返回mapper的动态代理生成的对象
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 根据Mapper接口的类型,从Map集合中获取Mapper代理对象工厂  存放了mapper标签解析的反射类
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        // 通过MapperProxyFactory生产MapperProxy,通过MapperProxy产生Mapper代理对象
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

说明

  1. 调用MapperRegistry类中 getMapper方法来;返回mapper的动态代理生成的对象, 该类存放了代理对象的mapper 首先通过knowMappers 集合中通过class对象获取代理工厂 如果工厂为空则抛出异常 通过MapperProxyFactory来生成MapperProxy
  2. 通过MapperProxyFactory来生产MapperProxy
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
// 使用JDK动态代理方式,生成代理对象  使用代理反射  mapperProxy实现invoke类(实现了InvocationHandler接口的类)
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

public T newInstance(SqlSession sqlSession) {
// 创建基于JDK实现的Mapper代理对象
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
// 创建实例对象
return newInstance(mapperProxy);
}

image.png

  1. Proxy.newProxyInstance 通过传入MapperInterface接口的反射类(UserMapper.class)来实例化代理对象

2.5、执行方法

1、动态代理的实现类 invoke方法
@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // 是Interface接口还是Object类
      // 如果方法是Object类自带的方法,比如没有被重写的equals toString, hashcode 等,还是执行原来的方法 不需要拦截
      // getDeclaringClass()返回表示声明由此 Method 对象表示的方法的类或接口的 Class 对象。
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
        // 调用接口的默认方法
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }

    // 如果不是object的自带方法,先去  Map<Method, MapperMethod> methodCache中找是否已经存在这个method了,
    // 没有就将method封装成MapperMethod存进methodCache中然后返回MapperMethod。
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 执行sqlSession的方法
     return mapperMethod.execute(sqlSession, args);
  }

image.png
image.png

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result = null;
    // 很明显我们的SqlCommandType是SELECT类型
    switch (command.getType()) {
        case INSERT: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.insert(command.getName(), param));
            break;
        }
        case UPDATE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.update(command.getName(), param));
            break;
        }
        case DELETE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.delete(command.getName(), param));
            break;
        }
        case SELECT:
            if (method.returnsVoid() && method.hasResultHandler()) {
                executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (method.returnsMany()) {
                result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) {
                result = executeForMap(sqlSession, args);
            } else {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(command.getName(), param);
                if (method.returnsOptional() &&
                        (result == null || !method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
            break;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + command.getName());
    }
    //是否返回类型是void类型并且Method参数列表中包含resultHandler,具体的判断在文末分析
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
        throw new BindingException("Mapper method '" + command.getName()
                + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
}

说明

  1. 获取command.getType() 的类别通过Switch 来分支

image.png

  1. 解析insert

image.png
image.png
将参数列表中的参数通过集合形式以注解中的value作为key 而形参作为value 存储到param的集合中以Object类型返回到前一次操作。
image.png
image.png
image.png
image.png

2、Statement设置参数

为Statement设置参数

 /**
 * 设置参数
 * @param ps
 * @throws SQLException
 */
@Override
public void setParameters(PreparedStatement ps) throws SQLException {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // 获取要设置的参数映射信息
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
        // 对每条语句进行传参  JDBC 一样的使用  通过序号设置参数
        for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            // 只处理入参  pstmt.setString(1, "姓名" + i);类型
            if (parameterMapping.getMode() != ParameterMode.OUT) {
                Object value;
                // 获取属性名称
                String propertyName = parameterMapping.getProperty();
                // 判断是否存在该传入参数
                if (boundSql.hasAdditionalParameter(propertyName)) {
                    value = boundSql.getAdditionalParameter(propertyName);
                } else if (parameterObject == null) {
                    value = null;
                } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                    value = parameterObject;
                } else {
                    MetaObject metaObject = configuration.newMetaObject(parameterObject);
                    value = metaObject.getValue(propertyName);
                }
                // 获取每个参数的类型处理器,去设置入参和获取返回值
                TypeHandler typeHandler = parameterMapping.getTypeHandler();
                // 获取每个参数的JdbcType
                JdbcType jdbcType = parameterMapping.getJdbcType();
                // 如果都为空则获取为空的值
                if (value == null && jdbcType == null) {
                    jdbcType = configuration.getJdbcTypeForNull();
                }
                try {
                    // 给PreparedStatement设置参数
                    typeHandler.setParameter(ps, i + 1, value, jdbcType);
                } catch (TypeException e) {
                    throw new TypeException(
                            "Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
                } catch (SQLException e) {
                    throw new TypeException(
                            "Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
                }
            }
        }
    }
}

说明

  • 先获取boundSql语句中的参数列表
  • 通过遍历对语句中的?占位符进行替换
  • image.png
  • 通过判断parameterMapping.getMode() != ParameterMode.OUT 参数类型
  • image.png
  • image.png
  1. image.png
3、选择操作解析

SELECT操作解析

case SELECT:
    if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
    } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
    } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
    } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
        if (method.returnsOptional() &&
                (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
        }
    }
    break;

image.png
说明

  1. 对返回值为空同时存在返回集处理器 上面的this.method.returnsMap()是指@MapKey控制返回

image.png 这里涉及到了一个
onvertArgsToSqlCommandParam解析入参

public Object convertArgsToSqlCommandParam(Object[] args) {
    return this.paramNameResolver.getNamedParams(args);
}

解析@Param注解
names是SortedMap,在构造函数中赋值的,判断入参有没有@Param注解。
key是入参的顺序,从0开始;value是@Param中的值
如果没有@Param注解,则value值为arg0、arg1…
将入参名和值匹配

public Object getNamedParams(Object[] args) {
    int paramCount = this.names.size();
    if (args != null && paramCount != 0) {
        // 只有一个入参时,返回入参值
        if (!this.hasParamAnnotation && paramCount == 1) {
            return args[((Integer)this.names.firstKey()).intValue()];
        } else {
            // 多个入参时,返回一个Map
            Map<String, Object> param = new ParamMap();
            int i = 0;
            for(Iterator i$ = this.names.entrySet().iterator(); i$.hasNext(); ++i) {
                Entry<Integer, String> entry = (Entry)i$.next();
                param.put(entry.getValue(), args[((Integer)entry.getKey()).intValue()]);
                String genericParamName = "param" + String.valueOf(i + 1);
                if (!this.names.containsValue(genericParamName)) {
                    param.put(genericParamName, args[((Integer)entry.getKey()).intValue()]);
                }
            }
            return param;
        }
    } else {
        return null;
    }
}

示例1: 多个入参,没有加@Param注解

@Select("SELECT count(0) from es_inter_invokfaillog where rownum = #{num} and invok_type=#{type}")
public Integer selectAny(int num,String type);

样执行sql会报错,找不到num,可以改为#{arg1}或#{param1}
示例2:多个入参,加@Param注解

@Select("SELECT count(0) from es_inter_invokfaillog where rownum = #{num} and invok_type=#{type}")
public Integer selectAny(@Param("num")int num,@Param("type")String type);

image.png

result = sqlSession.selectOne(this.command.getName(), param);

image.png

public <T> T selectOne(String statement, Object parameter) {
    return this.sqlSessionProxy.selectOne(statement, parameter);
}

sqlSessionProxy是动态代理生成的,每一次执行方法时都会重新去 new 一个DefaultSqlSession,可以看下invoke方法部分代码

// 获取session,这里有个事物的判断
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
Object unwrapped;
try {
    // 真正执行sql语句的地方
    Object result = method.invoke(sqlSession, args);
    if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
        sqlSession.commit(true);
    }

    unwrapped = result;
}

image.png
查询是调用select的查询方法
image.png
image.png

  • 正在进行query的方法是在StatementHandler中的query方法中
@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 执行PreparedStatement,也就是执行SQL语句
    ps.execute();
    // 处理结果集
    return resultSetHandler.handleResultSets(ps);
  }

先获取PreparedStatement对象在对当前的sql语句解析execute 执行。
同时将ps传入返回几个集处理方法中进行JDBC和Java的类型转换
image.png

4、返回结果集解析 转换
/**
 * 返回集参数设置 处理 {@code stmt} 中的所有结果集,返回结果对象集合
 *
 * @param stmt
 * @return
 * @throws SQLException
 */
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    // 设置日志的上下文
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    // <select> 标签的ResultMap属性,可以指定多个值,多个值之间用(,)分隔
    final List<Object> multipleResults = new ArrayList<>();
    // 初始化结果集数量为0
    int resultSetCount = 0;
    // 这里是获取第一个结果集,将传统JDBC的ResultSet包装成一个包含结果列元信息的ResultSetWrapper对象
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    // 获得ResultMap 即查询结果的转换关系
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    // 验证ResultMap标签对象数量,如果rsw不为null且ResultMapCount小于1,就会抛出异常
    validateResultMapsCount(rsw, resultMapCount);
    // 遍历多个查询结果集  一般只有一个  重复调用的是存储过程
    // 如果结果集包装类对象不为null 且 配置的ResultMap标签对象数量大于结果集数量
    while (rsw != null && resultMapCount > resultSetCount) {
        // 获取当前结果集对应的ResultMap
        ResultMap resultMap = resultMaps.get(resultSetCount);
        /**
         * 构建出来的结果对象,如果父级结果属性映射不为null,会将结果对象赋值到父级结果属性对应的结果对象中,
         * 否则将结果对象加入到resultHandler中。最后从resultHandler中取的最终的结果对象加入到多个结果
         * 对象集合中
         */
        // 转换结果集,并且添加到multipleResults中 根据resultMap处理rsw生成java对象
        handleResultSet(rsw, resultMap, multipleResults, null);
        // 获取下一个查询结果集
        rsw = getNextResultSet(stmt);
        // 清空嵌套resultMap
        cleanUpAfterHandlingResultSet();
        // 每处理完一次,结果集数量+1
        resultSetCount++;
    }

    // resultSets是针对多结果集的情况下,给每个结果集一个名称,多个名称之间使用,分割
    String[] resultSets = mappedStatement.getResultSets();
    // 如果结果集不为null
    if (resultSets != null) {
        // 如果结果包装对象不为null 而且 结果集数量小于配置的结果集名数量
        while (rsw != null && resultSetCount < resultSets.length) {
            ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
            if (parentMapping != null) {
                String nestedResultMapId = parentMapping.getNestedResultMapId();
                ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
                // 一样的调用  这里只是对多个结果集进行处理
                handleResultSet(rsw, resultMap, null, parentMapping);
            }
            // 获取下一个结果集
            rsw = getNextResultSet(stmt);
            cleanUpAfterHandlingResultSet();
            resultSetCount++;
        }
    }
    // 如果multipleResults只有一个元素,只会返回该元素对象;否则返回multipleResult
    return collapseSingleResultList(multipleResults);
}
  • 实现获取第一个结果集 来解析 判断ResultSet 结果集
  • image.png

如果getResultSet()不为空则resultSet != **null **? **new **ResultSetWrapper(resultSet, configuration) : null; 将其包装成ResultSetWrapper 对象返回 该类将ResultSet包装
image.png

  • 判断包装的ResultSetWrapper 对象是否为空 同时判断ResultMapCount 是否大于ResultSetCount 如果大于的话ResultMap标签对象数量大于结果集数量,则可以存储到configuration。
  • 获取当前结果的结果集 ResultMap 然后转换结果集 handleResultSet(rsw, resultMap, multipleResults, null);
 /**
 * 构建出来的结果对象,如果父级结果属性映射不为null,会将结果对象赋值到父级结果属性对应的结果对象中,
 *  否则将结果对象加入到resultHandler中。最后从resultHandler中取的最终的结果对象加入到多个结果
 *  对象集合中
 * @param rsw 结果集包装对象
 * @param resultMap resultMap标签对象
 * @param multipleResults 多个结果对象集合
 * @param parentMapping 父级结果属性映射
 * @throws SQLException
 */
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) {
    try {
        // 如果 父级结果属性映射不为null
        if (parentMapping != null) {
            /**
             * 构建出来的结果对象,如果父级结果属性映射不为null,会将结果对象赋值到父级结果属性对应的结果对象中,
             * 否则将结果对象加入到resultHandler中
             */
            //这里因为parentMapping已经确定不为null了,所以resultHandler传入null也不会有问题。
            handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
        } else {
            // 第一次一般来说resultHandler为空  则创建DefaultResultHandler 来处理
            if (resultHandler == null) {
                // 如果用户没有指定对结果的处理器ResultHandler,那么默认会使用DefaultResultHandler
                DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
                // 对结果集进行转换
                handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
                // 将解析后的结果添加到multiResults中
                // 如果没有指定ResultHandler,那么默认会将解析之后的结果添加到multipleResults中
                multipleResults.add(defaultResultHandler.getResultList());
            } else {
                // 用户定义了对结果集的处理方法,即ResultHandler
                // 那么使用ResultSetHandler处理之后,会将结果再交给ResultHandler进行处理
                handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
            }
        }
    } catch (SQLException throwables) {
        throwables.printStackTrace();
    }

}

这里上面的方法就是转换结果集 创建DefaultResultHandler 对象来处理结果集
image.png
通过嵌套解析 我们只管理不嵌套的结果集 handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); 根据结果集包装类对象和简单的ResultMap标签对象,构建成结果对象

  • 获取jdbc的结果集 然后先跳过分页的offset 同时判断是否超过指定的行数 如果还未超过指定的limit 指定的行数 就循环解析
  • 对resultMap 和 对象的映射解析是getRowValue 方法才进行解析的
/**
 * 映射结果集到pojo上
 * (1)createResultObject 生成对象
 * (2)applyAutomaticMappings 自动映射
 * (3)applyPropertyMappings property映射
 *
 * @param rsw
 * @param resultMap
 * @param columnPrefix
 * @return
 * @throws SQLException
 */
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    // 延迟加载的映射信息
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    // 创建要映射的PO类对象
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
    // 结果对象不为空且类型处理器也不为空
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
        final MetaObject metaObject = configuration.newMetaObject(rowValue);
        boolean foundValues = this.useConstructorMappings;
        // 是否应用自动映射,也就是通过resultType进行映射
        if (shouldApplyAutomaticMappings(resultMap, false)) {
            // 根据columnName和type属性名映射赋值
            foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
        }
        // 根据我们配置ResultMap的column和property映射赋值
        // 如果映射存在nestedQueryId,会调用getNestedQueryMappingValue方法获取返回值
        foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
        foundValues = lazyLoader.size() > 0 || foundValues;
        rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    return rowValue;
}

image.png

 /**
 * 取得构造函数所需的参数值去创建结果对象
 * @param rsw 结果集包装类对象
 * @param resultMap Mapper.xml的resultMap标签信息封装类对象
 * @param constructorArgTypes 构造函数参数类型集合
 * @param constructorArgs 构造函数参数集合
 * @param columnPrefix 列名前缀
 * @return {@link ResultMap#getType()} 类型的对象
 */
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes,
                                  List<Object> constructorArgs, String columnPrefix) throws SQLException {
    // 获取结果对象类型
    final Class<?> resultType = resultMap.getType();
    // 构建结果对象类型的元对象
    final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
    // 获取构造函数映射关系
    final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
    // 如果存在对应的TypeHandler
    if (hasTypeHandlerForResultObject(rsw, resultType)) {
        // 创建原始的结果对象
        return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
        // 构造函数映射关系不为空
    } else if (!constructorMappings.isEmpty()) {
        // 根据构造函数映射构建的结果对象
        return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
        // 如果resultType是接口或者resultType有默认的构造函数
    } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
        // 让objectFactory创建通过默认构造函数resultType对象
        return objectFactory.create(resultType);
        // 是否可以应用自动映射
    } else if (shouldApplyAutomaticMappings(resultMap, false)) {
        // 取得构造函数所需的参数值去创建结果对象(自动映射)
        return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs, columnPrefix);
    }
    throw new ExecutorException("Do not know how to create an instance of " + resultType);
}

说明

  1. 获取resultMap中存储的resultType 的反射类 resultType 通过反射获取到MetaClass 对象来操作目的对象

image.png
image.png
通过ObjectFactory工具类来通过构造函数来创建一个实例对象 将一个rowValue的Object对象

  • 进入方法applyPropertyMappings 来实现结果集和属性映射映射 applyAutomaticMappings

image.png
image.png
image.png
这里的Object自定义的实体类返回查询就已经写出来了

2.6、部分知识图

image.png

image.png2

2.7、mybatis使用过的设计模式

  1. Builder模式 :例如 SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder;
  2. 工厂模式 :例如SqlSessionFactory、ObjectFactory、MapperProxyFactory;
  3. 单例模式 :例如ErrorContext和LogFactory;
  4. 代理模式 :Mybatis实现的核心,比如MapperProxy、ConnectionLogger,用的jdk的动态代理;还有executor.loader包使用了cglib或者javassist达到延迟加载的效果;
  5. 组合模式 :例如SqlNode和各个子类ChooseSqlNode等;
  6. 模板方法模式 : 例如BaseExecutor和SimpleExecutor,还有BaseTypeHandler和所有的子类例如IntegerTypeHandler;
  7. 适配器模式 : 例如Log的Mybatis接口和它对jdbc、log4j等各种日志框架的适配实现;
  8. 装饰者模式 : 例如cache包中的cache.decorators子包中等各个装饰者的实现;
  9. 迭代器模式 : 例如迭代器模式PropertyTokenizer;
1、Builder模式

在Mybatis环境的初始化过程中,SqlSessionFactoryBuilder会调用XMLConfigBuilder读取所有的MybatisMapConfig.xml和所有的Mapper.xml文件,构建Mybatis运行的核心对象Configuration对象,然后将该Configuration对象作为参数构建一个SqlSessionFactory对象。
其中XMLConfigBuilder在构建Configuration对象时,也会调用XMLMapperBuilder用于读取
.Mapper文件,而XMLMapperBuilder会使用XMLStatementBuilder来读取和build所有的SQL语句。
在这个过程中,有一个相似的特点,就是这些Builder会读取文件或者配置,然后做大量的XpathParser解析、配置或语法的解析、反射生成对象、存入结果缓存等步骤,这么多的工作都不是一个构造函数所能包括的,因此大量采用了Builder模式来解决。
对于builder的具体类,方法都大都用build*开头,比如SqlSessionFactoryBuilder为例,它包含以下方法:
image.png
SqlSessionFactoryBuilder
即根据不同的输入参数来构建SqlSessionFactory这个工厂对象。

2、工厂模式

简单工厂模式
SqlSession可以认为是一个Mybatis工作的核心的接口,通过这个接口可以执行执行SQL语句、获取Mappers、管理事务。类似于连接MySQL的Connection对象。
image.png
SqlSessionFactory
可以看到,该Factory的openSession()方法重载了很多个,分别支持autoCommit、Executor、Transaction 等参数的输入,来构建核心的SqlSession对象。
在DefaultSqlSessionFactory的默认工厂实现里,有一个方法可以看出工厂怎么产出一个产品:

    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,
            boolean autoCommit) {
        Transaction tx = null;
        try {
            final Environment environment = configuration.getEnvironment();
            final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            final Executor executor = configuration.newExecutor(tx, execType);
            return new DefaultSqlSession(configuration, executor, autoCommit);
        } catch (Exception e) {
            closeTransaction(tx); // may have fetched a connection so lets call
                                    // close()
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }

这是一个openSession调用的底层方法,该方法先从configuration读取对应的环境配置,然后初始化TransactionFactory获得一个Transaction对象,然后通过Transaction获取一个Executor对象,最后通过configuration、Executor、是否autoCommit三个参数构建了SqlSession。
在这里其实也可以看到端倪,SqlSession的执行,其实是委托给对应的Executor来进行的。
而对于LogFactory,它的实现代码:

public final class LogFactory {
    private static Constructor<? extends Log> logConstructor;
 
    private LogFactory() {
        // disable construction
    }
 
    public static Log getLog(Class<?> aClass) {
        return getLog(aClass.getName());
    }
3、单例模式

单例模式(Singleton Pattern):单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。
单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。单例模式是一种对象创建型模式。单例模式又名单件模式或单态模式。
单例模式
在Mybatis中有两个地方用到单例模式,ErrorContext和LogFactory,其中ErrorContext是用在每个线程范围内的单例,用于记录该线程的执行环境错误信息,而LogFactory则是提供给整个Mybatis使用的日志工厂,用于获得针对项目配置好的日志对象。
ErrorContext的单例实现代码:

public class ErrorContext {
 
    private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();
 
    private ErrorContext() {
    }
 
    public static ErrorContext instance() {
        ErrorContext context = LOCAL.get();
        if (context == null) {
            context = new ErrorContext();
            LOCAL.set(context);
        }
        return context;
    }

构造函数是private修饰,具有一个static的局部instance变量和一个获取instance变量的方法,在获取实例的方法中,先判断是否为空如果是的话就先创建,然后返回构造好的对象。
只是这里有个有趣的地方是,LOCAL的静态实例变量使用了ThreadLocal修饰,也就是说它属于每个线程各自的数据,而在instance()方法中,先获取本线程的该实例,如果没有就创建该线程独有的ErrorContext。

4、代理模式

代理模式
这里有两个步骤,第一个是提前创建一个Proxy,第二个是使用的时候会自动请求Proxy,然后由Proxy来执行具体事务;
当我们使用Configuration的getMapper方法时,会调用mapperRegistry.getMapper方法,而该方法又会调用mapperProxyFactory.newInstance(sqlSession)来生成一个具体的代理:

/**
 * @author Lasse Voss
 */
public class MapperProxyFactory<T> {
 
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
 
    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }
 
    public Class<T> getMapperInterface() {
        return mapperInterface;
    }
 
    public Map<Method, MapperMethod> getMethodCache() {
        return methodCache;
    }
 
    @SuppressWarnings("unchecked")
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface },
                mapperProxy);
    }
 
    public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }
 
}

在这里,先通过T newInstance(SqlSession sqlSession)方法会得到一个MapperProxy对象,然后调用T newInstance(MapperProxy mapperProxy)生成代理对象然后返回。 先获取到Mapper的代理对象 因为MapperProxy是实现了InvocationHandler接口使用 后面调用操作方法的时候就会默认调用invoke方法 然后再去执行execute执行方法 。
而查看MapperProxy的代码,可以看到如下内容:

public class MapperProxy<T> implements InvocationHandler, Serializable {
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else if (isDefaultMethod(method)) {
                return invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        return mapperMethod.execute(sqlSession, args);
    }

非常典型的,该MapperProxy类实现了InvocationHandler接口,并且实现了该接口的invoke方法。
通过这种方式,我们只需要编写Mapper.java接口类,当真正执行一个Mapper接口的时候,就会转发给MapperProxy.invoke方法,而该方法则会调用后续的sqlSession.cud>executor.execute>prepareStatement等一系列方法,完成SQL的执行和返回。

5、组合模式

Mybatis支持动态SQL的强大功能,比如下面的这个SQL:

<update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User">
    UPDATE users
    <trim prefix="SET" prefixOverrides=",">
        <if test="name != null and name != ''">
            name = #{name}
        </if>
        <if test="age != null and age != ''">
            , age = #{age}
        </if>
        <if test="birthday != null and birthday != ''">
            , birthday = #{birthday}
        </if>
    </trim>
    where id = ${id}
</update>

在这里面使用到了trim、if等动态元素,可以根据条件来生成不同情况下的SQL;
在DynamicSqlSource.getBoundSql方法里,调用了rootSqlNode.apply(context)方法,apply方法是所有的动态节点都实现的接口:

public interface SqlNode {
    boolean apply(DynamicContext context);
}

对于实现该SqlSource接口的所有节点,就是整个组合模式树的各个节点:image.png
SqlNode
组合模式的简单之处在于,所有的子节点都是同一类节点,可以递归的向下执行,比如对于TextSqlNode,因为它是最底层的叶子节点,所以直接将对应的内容append到SQL语句中:

    @Override
    public boolean apply(DynamicContext context) {
        GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
        context.appendSql(parser.parse(text));
        return true;
    }

但是对于IfSqlNode,就需要先做判断,如果判断通过,仍然会调用子元素的SqlNode,即contents.apply方法,实现递归的解析。

@Override
public boolean apply(DynamicContext context) {
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
        contents.apply(context);
        return true;
    }
    return false;
} 
6 、模板方法模式

在Mybatis中,sqlSession的SQL执行,都是委托给Executor实现的,Executor包含以下结构:
image.png
Executor接口
其中的BaseExecutor就采用了模板方法模式,它实现了大部分的SQL执行逻辑,然后把以下几个方法交给子类定制化完成:

    @Override
    public boolean apply(DynamicContext context) {
        if (evaluator.evaluateBoolean(test, context.getBindings())) {
            contents.apply(context);
            return true;
        }
        return false;
    }

该模板方法类有几个子类的具体实现,使用了不同的策略:

  • 简单SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。(可以是Statement或PrepareStatement对象)
  • 重用ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。(可以是Statement或PrepareStatement对象)
  • 批量BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理的;BatchExecutor相当于维护了多个桶,每个桶里都装了很多属于自己的SQL,就像苹果蓝里装了很多苹果,番茄蓝里装了很多番茄,最后,再统一倒进仓库。(可以是Statement或PrepareStatement对象)

比如在SimpleExecutor中这样实现update方法:

    @Override
    public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null,
                    null);
            stmt = prepareStatement(handler, ms.getStatementLog());
            return handler.update(stmt);
        } finally {
            closeStatement(stmt);
        }
    }
7、适配器模式

在Mybatsi的logging包中,有一个Log接口:

/**
 * @author Clinton Begin
 */
public interfaceLog{

    booleanisDebugEnabled();

    booleanisTraceEnabled();

    voiderror(String s, Throwable e);

    voiderror(String s);

    voiddebug(String s);

    voidtrace(String s);

    voidwarn(String s);

}

该接口定义了Mybatis直接使用的日志方法,而Log接口具体由谁来实现呢?Mybatis提供了多种日志框架的实现,这些实现都匹配这个Log接口所定义的接口方法,最终实现了所有外部日志框架到Mybatis日志包的适配:
image.png
比如对于Log4jImpl的实现来说,该实现持有了org.apache.log4j.Logger的实例,然后所有的日志方法,均委托该实例来实现。

public classLog4jImplimplementsLog{

    private static final String FQCN = Log4jImpl.class.getName();

    private Logger log;

    publicLog4jImpl(String clazz) {
        log = Logger.getLogger(clazz);
    }

    @Override
    publicbooleanisDebugEnabled() {
        return log.isDebugEnabled();
    }

    @Override
    publicbooleanisTraceEnabled() {
        return log.isTraceEnabled();
    }

    @Override
    publicvoiderror(String s, Throwable e) {
        log.log(FQCN, Level.ERROR, s, e);
    }

    @Override
    publicvoiderror(String s) {
        log.log(FQCN, Level.ERROR, s, null);
    }

    @Override
    publicvoiddebug(String s) {
        log.log(FQCN, Level.DEBUG, s, null);
    }

    @Override
    publicvoidtrace(String s) {
        log.log(FQCN, Level.TRACE, s, null);
    }

    @Override
    public void warn(String s) {
        log.log(FQCN, Level.WARN, s, null);
    }

}
8、装饰者模式

装饰模式(Decorator Pattern) :动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。根据翻译的不同,装饰模式也有人称之为“油漆工模式”,它是一种对象结构型模式。
在mybatis中,缓存的功能由根接口Cache(org.apache.ibatis.cache.Cache)定义。整个体系采用装饰器设计模式,数据存储和缓存的基本功能由PerpetualCache(org.apache.ibatis.cache.impl.PerpetualCache)永久缓存实现,然后通过一系列的装饰器来对PerpetualCache永久缓存进行缓存策略等方便的控制。如下图:
image.png
Cache
用于装饰PerpetualCache的标准装饰器共有8个(全部在org.apache.ibatis.cache.decorators包中):

  1. FifoCache:先进先出算法,缓存回收策略
  2. LoggingCache:输出缓存命中的日志信息
  3. LruCache:最近最少使用算法,缓存回收策略
  4. ScheduledCache:调度缓存,负责定时清空缓存
  5. SerializedCache:缓存序列化和反序列化存储
  6. SoftCache:基于软引用实现的缓存管理策略
  7. SynchronizedCache:同步的缓存装饰器,用于防止多线程并发访问
  8. WeakCache:基于弱引用实现的缓存管理策略

另外,还有一个特殊的装饰器TransactionalCache:事务性的缓存
正如大多数持久层框架一样,mybatis缓存同样分为一级缓存和二级缓存

  • 一级缓存,又叫本地缓存,是PerpetualCache类型的永久缓存,保存在执行器中(BaseExecutor),而执行器又在SqlSession(DefaultSqlSession)中,所以一级缓存的生命周期与SqlSession是相同的。
  • 二级缓存,又叫自定义缓存,实现了Cache接口的类都可以作为二级缓存,所以可配置如encache等的第三方缓存。二级缓存以namespace名称空间为其唯一标识,被保存在Configuration核心配置对象中。

二级缓存对象的默认类型为PerpetualCache,如果配置的缓存是默认类型,则mybatis会根据配置自动追加一系列装饰器。
Cache对象之间的引用顺序为:
SynchronizedCache–>LoggingCache–>SerializedCache–>ScheduledCache–>LruCache–>PerpetualCache

9、迭代器模式

比如Mybatis的PropertyTokenizer是property包中的重量级类,该类会被reflection包中其他的类频繁的引用到。这个类实现了Iterator接口,在使用时经常被用到的是Iterator接口中的hasNext这个函数。

public class PropertyTokenizer implements Iterator<PropertyTokenizer> {
    
    private String name;
    private String indexedName;
    private String index;
    private String children;
 
    public PropertyTokenizer(String fullname) {
        int delim = fullname.indexOf('.');
        if (delim > -1) {
            name = fullname.substring(0, delim);
            children = fullname.substring(delim + 1);
        } else {
            name = fullname;
            children = null;
        }
        indexedName = name;
        delim = name.indexOf('[');
        if (delim > -1) {
            index = name.substring(delim + 1, name.length() - 1);
            name = name.substring(0, delim);
        }
    }
 
    public String getName() {
        return name;
    }
 
    public String getIndex() {
        return index;
    }
 
    public String getIndexedName() {
        return indexedName;
    }
 
    public String getChildren() {
        return children;
    }
 
    @Override
    public boolean hasNext() {
        return children != null;
    }
 
    @Override
    public PropertyTokenizer next() {
        return new PropertyTokenizer(children);
    }
 
    @Override
    public void remove() {
        throw new UnsupportedOperationException(
                "Remove is not supported, as it has no meaning in the context of properties.");
    }
}

可以看到,这个类传入一个字符串到构造函数,然后提供了iterator方法对解析后的子串进行遍历,是一个很常用的方法类。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
手写MyBatis框架可以遵循以下步骤: 1. 创建一个配置类,用于加载数据库配置信息和Mapper接口配置信息。 2. 创建一个SqlSession类,用于管理数据库连接、执行SQL语句和返回结果。 3. 创建一个Mapper接口,定义数据库操作方法。 4. 创建一个MapperProxy类,实现动态代理,将Mapper接口的方法调用转发给SqlSession执行对应的SQL语句。 5. 创建一个MapperRegistry类,用于管理Mapper接口和对应的MapperProxy对象。 6. 创建一个DefaultSqlSessionFactory类,用于创建SqlSession对象。 7. 创建一个SqlSessionFactoryBuilder类,用于读取配置信息并创建DefaultSqlSessionFactory对象。 8. 创建一个Configuration类,用于保存MyBatis的全局配置信息。 9. 创建一个Executor类,用于执行SQL语句。 10. 创建一个StatementHandler类,用于封装和执行JDBC操作。 11. 创建一个ResultSetHandler类,用于处理查询结果集。 12. 创建一个TypeHandler类,用于处理Java类型和数据库类型之间的转换。 13. 创建一个XMLMapperBuilder类,用于解析Mapper配置文件,并将解析结果注册到Configuration中。 14. 创建一个XMLConfigBuilder类,用于解析MyBatis配置文件,并将解析结果保存到Configuration中。 15. 在主程序中,使用SqlSessionFactoryBuilder读取配置信息创建SqlSessionFactory,并通过SqlSessionFactory创建SqlSession对象,最后使用SqlSession获取Mapper接口的代理对象,从而实现对数据库的操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值