浅谈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);
}
相关推荐
陈平安Java and C5 小时前
MyBatisPlus
java
秋野酱5 小时前
如何在 Spring Boot 中实现自定义属性
java·数据库·spring boot
安的列斯凯奇6 小时前
SpringBoot篇 单元测试 理论篇
spring boot·后端·单元测试
Bunny02126 小时前
SpringMVC笔记
java·redis·笔记
架构文摘JGWZ6 小时前
FastJson很快,有什么用?
后端·学习
BinaryBardC6 小时前
Swift语言的网络编程
开发语言·后端·golang
feng_blog66886 小时前
【docker-1】快速入门docker
java·docker·eureka
邓熙榆6 小时前
Haskell语言的正则表达式
开发语言·后端·golang
枫叶落雨2228 小时前
04JavaWeb——Maven-SpringBootWeb入门
java·maven
m0_748232398 小时前
SpringMVC新版本踩坑[已解决]
java