浅谈mybatis源码当中常用类(上)

mybatis源码--常用类

一、XPathParser

XPath可以很简单的使用路径表达式XML文档中选取节点元素。该工具的核心作用就是解析xml文件,包括我们的配置文件和mapper文件。这项技术也是爬虫的核心技术之一,下边我们了解一下xpath的基础语法,但这不是我们的重点,一切以了解为主。如下图:在浏览器中我们可以很轻松的获取一个标签的xpath:

他可能张如下的这个样子:

css 复制代码
/html/body/div[2]/div[2]/div[3]/div/div

我们当然可以简单猜测一下他所表达的含义:

根目录下的html下的body元素下的第二个div下的第二个div下的第三个div下的第一个div下的第一个div,事实上确实是这样的。

1、基础语法

下面列出了最有用的路径表达式:

表达式 描述
nodename 选取此节点的所有子节点。
/ 从根节点选取。
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
. 选取当前节点。
.. 选取当前节点的父节点。
@ 选取属性。

**例子:**在下面的表格中,我们已列出了一些路径表达式以及表达式的结果:

路径表达式 结果
properties 选取 properties元素的所有节点。
/properties 选取根元素 properties。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径!
properties/property 选取属于 properties的子元素的所有 property元素。
//property 选取所有 property 元素,而不管它们在文档中的位置。
properties//property 选择属于 properties元素的后代的所有 property元素,而不管它们位于 properties之下的什么位置。
//@lang 选取名为 lang 的所有属性。

2、谓语(Predicates)

谓语是用来查找某个【特定的节点或者包含某个指定的值】的节点。通常,谓语被嵌在方括号中。

**例子:**在下面的表格中,我们列出了带有谓语的一些路径表达式,以及表达式的结果:

路径表达式 结果
/properties/property[1] 选取属于 properties子元素的第一个 property元素。
/properties/property[last()] 选取属于 properties子元素的最后一个 property元素。
/properties/property[last()-1] 选取属于 properties子元素的倒数第二个 property元素。
/properties/property[position()❤️] 选取最前面的两个属于 properties元素的子元素的 property元素。
//title[@lang] 选取所有拥有名为 lang 的属性的 title 元素。
//title[@lang='eng'] 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。
/properties/property[value>10.00] 选取 properties 元素的所有 property元素,且其中的 value元素的值须大于 10.00。
/properties/property[value>10.00]/title 选取 properties元素中的 property元素的所有 value元素,且其中的 price 元素的值须大于 10.00。

3、选取未知节点

XPath 通配符可用来选取未知的 XML 元素。

通配符 描述
* 匹配任何元素节点。
@* 匹配任何属性节点。
node() 匹配任何类型的节点。

**例子:**在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:

路径表达式 结果
/bookstore/* 选取 bookstore 元素的所有子元素。
//* 选取文档中的所有元素。
//title[@*] 选取所有带有属性的 title 元素。

4、选取若干路径

通过在路径表达式中使用"|"运算符,您可以选取若干个路径。

**例子:**在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:

| 路径表达式 | 结果 |
|-----------------------|--------------|-----------------------------------------------------------|
| //book/title | //book/price | 选取 book 元素的所有 title 和 price 元素。 |
| //title | //price | 选取文档中的所有 title 和 price 元素。 |
| /bookstore/book/title | //price | 选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。 |

5、测试用例

我们使用mybatis提供的XPathParser对mybatis.xml进行解析,更多的例子我们不在赘述:

java 复制代码
@Test
public void testXpathParser() throws IOException {
    InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
    XPathParser xPathParser = new XPathParser(inputStream, true, null, new XMLMapperEntityResolver());
    XNode xNode = xPathParser.evalNode("/configuration/environments/environment/dataSource");
    System.out.println(xNode);
}

二、Configuration类

)1、java配置

针对上述的xml配置,我们可以使用如下的java代码替换:

ini 复制代码
    @Test
    public void testConfiguration() {

        Configuration configuration = new Configuration();
        Properties properties = new Properties();
        properties.put("driver", "com.mysql.cj.jdbc.Driver");
        properties.put("url", "jdbc:mysql://localhost:3306/ydlclass?characterEncoding=utf8&serverTimezone=Asia/Shanghai");
        properties.put("username", "root");
        properties.put("password", "root");
        configuration.setVariables(properties);
        // 添加日志实现,和驼峰式命名
        configuration.setLogImpl(Slf4jImpl.class);
        configuration.setMapUnderscoreToCamelCase(true);

        configuration.getTypeAliasRegistry().registerAliases("com.ydlclass.mybatissourcestudy.entity");

        // 配置环境
        JdbcTransactionFactory jdbcTransactionFactory = new JdbcTransactionFactory();
        PooledDataSource pooledDataSource = new PooledDataSource();
        pooledDataSource.setDriver(configuration.getVariables().getProperty("driver"));
        pooledDataSource.setUrl(configuration.getVariables().getProperty("url"));
        pooledDataSource.setUsername(configuration.getVariables().getProperty("username"));
        pooledDataSource.setPassword(configuration.getVariables().getProperty("password"));
        Environment environment = new Environment("development", jdbcTransactionFactory, pooledDataSource);
        configuration.setEnvironment(environment);

        SqlSource sqlSource = new StaticSqlSource(configuration,
                "insert into account (username,money) values ('jerry',1000)"
        );
        MappedStatement mappedStatement = new MappedStatement.Builder(
                configuration, "ydlclass.insert", sqlSource, SqlCommandType.INSERT
        ).build();
        configuration.addMappedStatement(mappedStatement);
        log.info("configuration -> {}",configuration);

    }

所有的配置项都可以使用编码的形式进行配置,我们就不一一讲解了,接下来我们看看如何使用XMLConfigBuilder来构建一个配置类。

2、构建配置类

一个包装了XPathParser的工具类,可以将xml的配置文件解析成为一个配置类,代码如下:

ini 复制代码
@Test
public void tesXMLConfigBuilder() throws IOException {
    InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
    XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder(inputStream);
    Configuration configuration = xmlConfigBuilder.parse();
    System.out.println(configuration.getEnvironment().getDataSource());
}

源码解析:

1、构建builder

csharp 复制代码
public XMLConfigBuilder(InputStream inputStream) {
    this(inputStream, null, null);
}
// XMLMapperEntityResolver是MyBatis dtd的离线实体解析器,他并不重要。
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}

2、解析的核心方法

csharp 复制代码
public Configuration parse() {
    if (parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}

3、parseConfiguration

在这里我们看到,整个解析的过程是有序的,所以我们在配置的时候一定要注意配置的顺序,否则会报错:

scss 复制代码
private void parseConfiguration(XNode root) {
    try {
        // 依次解析每一个节点
        propertiesElement(root.evalNode("properties"));
        Properties settings = settingsAsProperties(root.evalNode("settings"));
        loadCustomVfs(settings);
        loadCustomLogImpl(settings);
        typeAliasesElement(root.evalNode("typeAliases"));
        pluginElement(root.evalNode("plugins"));
        objectFactoryElement(root.evalNode("objectFactory"));
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        reflectorFactoryElement(root.evalNode("reflectorFactory"));
        settingsElement(settings);
        // read it after objectFactory and objectWrapperFactory issue #631
        environmentsElement(root.evalNode("environments"));
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        typeHandlerElement(root.evalNode("typeHandlers"));
        mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

三、OGNL表达式

在mybatis中的动态sql中存在很多表达式,如if标签中常见的(username != null && username != '')或者 #{id},为了解析这类标签,mybatis使用了OGNL技术,OGNL是 Object-Graph Navigation Language 的缩写,对象-图形导航语言,语法为:#{ }。

1、OGNL三要素

(1)表达式(Expression)

表达式是整个OGNL的核心,所有的OGNL操作都是针对表达式的解析结果进行处理的。表达式规定了此次OGNL操作到底要干什么。因此,表达式其实是一个带有语法含义的字符串,这个字符串将规定操作的类型和操作的内容。

OGNL支持大量的表达式语法,不仅支持"链式"描述对象访问路径,还支持在表达式中进行简单的计算,甚至还能够支持复杂的Lambda表达式等。该课程并不是主要讲解OGNL,我们仅仅作为了解mybaits的源码而学习。

(2)Root对象(Root Object)

OGNL的Root对象可以理解为OGNL的操作对象。当OGNL表达式规定了"干什么"以后,我们还需要指定对谁干 。OGNL的Root对象实际上是一个Java对象,是所有OGNL操作的实际载体。这就意味着,如果我们有一个OGNL的表达式,那么我们实际上需要针对Root对象去进行OGNL表达式的计算并返回结果。

(3)上下文环境(Context)

有了表达式和Root对象,我们已经可以使用OGNL的基本功能。例如,根据表达式针对OGNL中的Root对象进行"取值"或者"写值"操作。

不过,事实上,在OGNL的内部,所有的操作都会在一个特定的数据环境中运行,这个数据环境就是OGNL的上下文环境(Context)。说得再明白一些,就是这个上下文环境(Context)将规定OGNL的操作在哪里干

OGNL的上下文环境是一个Map结构,称之为OgnlContext。之前我们所提到的Root对象(Root Object),事实上也会被添加到上下文环境中去,并且将被作为一个特殊的变量进行处理。

2、OGNL的基本操作

使用OGNL需要如下的依赖,但事实上我们并不需要显示引入该依赖,因为mybatis已经通过依赖传递的方式将其引入:

xml 复制代码
<dependency>
    <groupId>ognl</groupId>
    <artifactId>ognl</artifactId>
    <version>3.3.3</version>
</dependency>

(1)对Root对象(Root Object)的访问

针对OGNL的Root对象的对象树的访问是通过使用"点号"将对象的引用串联起来实现的。通过这种方式,OGNL实际上将一个树形的对象结构转化成了一个链式结构的字符串结构来表达语义。

java 复制代码
@Test
public void testOgnlContext1() throws OgnlException {
    // 定义一个参数
    Account account = new Account();
    account.setUsername("tom");
    // 解析表达式,这种表达式在<if>标签中的test中使用经常使用
    Object value = Ognl.getValue("username == null && username == ''", account);
    System.out.println("value = " + value);
}

当然表达式一样可以链式调用:

arduino 复制代码
// 获取Root对象中dept属性中name属性的实际值  
dept.name

(2)对上下文环境(Context)的访问

由于OGNL的上下文是一个Map结构,在OGNL进行计算时可以事先在上下文环境中设置一些参数,并让OGNL将这些参数带入进行计算。有时候也需要对这些上下文环境中的参数进行访问,访问这些参数时,需要通过#符号加上链式表达式来进行,从而表示与访问Root对象(Root Object)的区别。

typescript 复制代码
@Test
public void testOgnlContext2() throws OgnlException {
    // 定义一个map上下文
    Account account = new Account();
    account.setUsername("tom");
    Map<String,Object> map = new HashMap<>(4);
    map.put("account",account);

    Object username = Ognl.getValue("#account.username",map,new Object() );
    System.out.println(username);
}

(3)对静态变量的访问

在OGNL中,对于静态变量或者静态方法的访问,需要通过@[class]@[field|method]的表达式语法来进行访问:

java 复制代码
// 访问@com.ydlclass.mybatissource.util.DBUtilBindThread@threadLocal类中名为ENABLE的属性值  
@Test
public void testOgnlContext3() throws OgnlException {
    Object enable = Ognl.getValue("@com.ydlclass.mybatissource.util.DBUtilBindThread@threadLocal",new Object() );
    System.out.println(enable);
}

(4)方法调用

在OGNL中调用方法,可以直接通过类似Java的方法调用方式进行,也就是通过点号加方法名称完成方法调用,甚至可以传递参数。这里我们就不做测试了。

typescript 复制代码
@Test
public void testOgnlContext4() throws OgnlException {
    // 定义一个map上下文
    Account account = new Account();
    account.setUsername("Maloney");
    Map<String,Object> map = new HashMap<>(4);
    map.put("account",account);

    Object username = Ognl.getValue("#account.username.substring(1,5)",map,new Object() );
    System.out.println(username);
}

四、别名注册器

mybatis提供了TypeAliasRegistry作为别名注册器,同时默认注入了大量的基础类型的别名,他是配置类的一个成员变量:

java 复制代码
 protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();

其实现如下:

scss 复制代码
public class TypeAliasRegistry {

    private final Map<String, Class<?>> typeAliases = new HashMap<>();

    // 构造方法中默认注册了大量的别名
    public TypeAliasRegistry() {
        registerAlias("string", String.class);

        registerAlias("byte", Byte.class);
        registerAlias("char", Character.class);
        registerAlias("character", Character.class);
        registerAlias("long", Long.class);
        registerAlias("short", Short.class);
        registerAlias("int", Integer.class);
        registerAlias("integer", Integer.class);
        registerAlias("double", Double.class);
        registerAlias("float", Float.class);
        registerAlias("boolean", Boolean.class);

        registerAlias("byte[]", Byte[].class);
        registerAlias("char[]", Character[].class);
        registerAlias("character[]", Character[].class);
        registerAlias("long[]", Long[].class);
        registerAlias("short[]", Short[].class);
        registerAlias("int[]", Integer[].class);
        registerAlias("integer[]", Integer[].class);
        registerAlias("double[]", Double[].class);
        registerAlias("float[]", Float[].class);
        registerAlias("boolean[]", Boolean[].class);

        registerAlias("_byte", byte.class);
        registerAlias("_char", char.class);
        registerAlias("_character", char.class);
        registerAlias("_long", long.class);
        registerAlias("_short", short.class);
        registerAlias("_int", int.class);
        registerAlias("_integer", int.class);
        registerAlias("_double", double.class);
        registerAlias("_float", float.class);
        registerAlias("_boolean", boolean.class);

        registerAlias("_byte[]", byte[].class);
        registerAlias("_char[]", char[].class);
        registerAlias("_character[]", char[].class);
        registerAlias("_long[]", long[].class);
        registerAlias("_short[]", short[].class);
        registerAlias("_int[]", int[].class);
        registerAlias("_integer[]", int[].class);
        registerAlias("_double[]", double[].class);
        registerAlias("_float[]", float[].class);
        registerAlias("_boolean[]", boolean[].class);

        registerAlias("date", Date.class);
        registerAlias("decimal", BigDecimal.class);
        registerAlias("bigdecimal", BigDecimal.class);
        registerAlias("biginteger", BigInteger.class);
        registerAlias("object", Object.class);

        registerAlias("date[]", Date[].class);
        registerAlias("decimal[]", BigDecimal[].class);
        registerAlias("bigdecimal[]", BigDecimal[].class);
        registerAlias("biginteger[]", BigInteger[].class);
        registerAlias("object[]", Object[].class);

        registerAlias("map", Map.class);
        registerAlias("hashmap", HashMap.class);
        registerAlias("list", List.class);
        registerAlias("arraylist", ArrayList.class);
        registerAlias("collection", Collection.class);
        registerAlias("iterator", Iterator.class);

        registerAlias("ResultSet", ResultSet.class);
    }
    //.....
}

关于别名注册器的使用及其简单:

csharp 复制代码
@Test
public void testTypeAliasRegistry(){
    TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
    typeAliasRegistry.registerAlias("employee", Employee.class);
    typeAliasRegistry.registerAliases("com.ydlclass.mybatissource.entity");
}

1、批量注册

typescript 复制代码
public void registerAliases(String packageName) {
    registerAliases(packageName, Object.class);
}

public void registerAliases(String packageName, Class<?> superType) {
    // ResolverUtil找到所有Object的子类
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    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);
        }
    }
}

2、单个别名的注册

vbnet 复制代码
public void registerAlias(Class<?> type) {
    //
    String alias = type.getSimpleName();
    Alias aliasAnnotation = type.getAnnotation(Alias.class);
    if (aliasAnnotation != null) {
        alias = aliasAnnotation.value();
    }
    registerAlias(alias, type);
}

public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
        throw new TypeException("The parameter alias cannot be null");
    }
    // issue #748
    String key = alias.toLowerCase(Locale.ENGLISH);
    if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
        throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
    }
    typeAliases.put(key, value);
}
相关推荐
HelloWord~40 分钟前
SpringSecurity+vue通用权限系统2
java·vue.js
让我上个超影吧41 分钟前
黑马点评【基于redis实现共享session登录】
java·redis
00后程序员1 小时前
提升移动端网页调试效率:WebDebugX 与常见工具组合实践
后端
HyggeBest1 小时前
Mysql的数据存储结构
后端·架构
TobyMint1 小时前
golang 实现雪花算法
后端
G探险者1 小时前
【案例解析】一次 TIME_WAIT 导致 TPS 断崖式下降的排查与优化
后端
BillKu1 小时前
Java + Spring Boot + Mybatis 插入数据后,获取自增 id 的方法
java·tomcat·mybatis
全栈凯哥2 小时前
Java详解LeetCode 热题 100(26):LeetCode 142. 环形链表 II(Linked List Cycle II)详解
java·算法·leetcode·链表
chxii2 小时前
12.7Swing控件6 JList
java
全栈凯哥2 小时前
Java详解LeetCode 热题 100(27):LeetCode 21. 合并两个有序链表(Merge Two Sorted Lists)详解
java·算法·leetcode·链表