MyBatis 核心配置讲解(上)

大家好,我是王有志,一个分享硬核 Java 技术的互金摸鱼侠。

前两篇的文章中我们分别介绍了 MyBatis 和 MyBaits 的应用组成,到这里基础篇的内容就结束了。

从今天开始,我们正式进入 MyBatis 学习的第二阶段:MyBatis 的应用部分。这个阶段从 MyBatis 应用程序的核心配置文件 mybatis-config.xml 开始入手,逐步推进到映射器(Mapper.xml),动态 SQL 语句的编写以及 MyBatis 与 Spring 和 Spring Boot 的集成。

在讲解 MyBatis 的核心配置文件时,以及未来讲解 MyBatis 的映射器(Mapper.xml)时,会少量涉及到文档类型定义(即 DTD,Document Type Definition)的内容,由于很多小伙伴可能不太熟悉 DTD,因此我会在文末的部分通过一张图来简单介绍下 DTD。

Tips

  • 目前 DTD 正在逐渐被功能更强,标准化程度更高的 XSD(XML Schema Definition)取代,因此我们只要简单了解 DTD 即可
  • 点击这里可以直接下载 MyBatis 核心配置文件的 DTD。

MyBaits 的核心配置文件

我们打开 mybatis-3-configs.dtd 可以看到,该文档最开始定义了 MyBatis 配置中的 configuration 元素及包含的子元素:

<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>

configuration 元素是 mybatis-config.xml 的根元素,本身并未定义任何属性,只定义了 11 个子元素,这里需要注意,子元素定义的顺序也是它们在 MyBatis 核心配置文件 mybatis-config.xml 中使用的顺序

接下来我们按照 DTD 中定义的元素顺序,逐个讲解元素的用法。由于数量比较多,我会分为两期和大家分享,本期分享 configuration 的前 5 个子元素:properties,settings,typeAliases,typeHandlers 和 objectFactory。

properties 元素(配置)

properties 元素用于声明配置,在 DTD 中的定义如下:

<!ELEMENT properties (property*)>
<!ATTLIST properties
resource CDATA #IMPLIED
url CDATA #IMPLIED
>

properties 定义了两个属性:resource 属性和 url 属性,以及一个子元素 property。

resource 属性和 url 属性

properties 元素提供了两个属性:resource 和 url,通过它们允许通过其它配置文件或网络中获取配置

属性是否必填说明
resource非必填与属性 url 互斥
url非必填与属性 resource 互斥

Tips:属性 resource 与 url 互斥的原因,我会在属性与子标签的优先级中提到。

使用它们需要提前准备好配置文件,或能够通过网络获取配置,我这里以使用配置文件为例。首选我们准备一个配置文件 mysql-config.properties,将它放在 resources 目录下,具体内容如下:

mysql.driver=com.mysql.cj.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/mybatis
mysql.username=root
mysql.password=123456

接着我们修改 MyBatis 入门中示例的 mybatis-config.xml,如下:

<configuration>
  <properties resource="mysql-config.properties" />

  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${mysql.driver}"/>
        <property name="url" value="${mysql.url}"/>
        <property name="username" value="${mysql.username}"/>
        <property name="password" value="${mysql.password}"/>
      </dataSource>
    </environment>
  </environments>
</configuration>
property 元素

property 元素在 DTD 中的定义如下

<!ELEMENT property EMPTY>
<!ATTLIST property
name CDATA #REQUIRED
value CDATA #REQUIRED
>

通过 property 元素可以在 mybatis-config.xml 中声明配置。

属性是否必填说明
name
value

在 properties 元素中使用 property 元素无需额外的准备,直接写在 mybatis-config.xml 中即可,如下:

<configuration>
  <properties>
    <property name="mysql.driver" value="com.mysql.cj.jdbc.Driver"/>
    <property name="mysql.url" value="jdbc:mysql://localhost:3306/mybatis"/>
    <property name="mysql.username" value="root"/>
    <property name="mysql.password" value="123456"/>
  </properties>

  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${mysql.driver}"/>
        <property name="url" value="${mysql.url}"/>
        <property name="username" value="${mysql.username}"/>
        <property name="password" value="${mysql.password}"/>
      </dataSource>
    </environment>
  </environments>
</configuration>

Tips:在 mybatis-3-config.dtd 中,property 标签是一个通用元素,会作为很多元素的子元素出现,例如:objectFactory 元素,databaseIdProvider 元素等。

属性与子元素的混合使用

properties 元素支持属性与子元素的混合使用。我们稍微修改下外部配置文件 mysql-config.properties,删除 mysql.driver 和 mysql.url,如下:

mysql.username=root
mysql.password=123456

接着修改 mybatis-config.xml,删除通过 property 元素配置的 mysql.username 和 mysql.password,并且引入外部配置文件 mysql-config.properties,如下:

<configuration>
  <properties resource="mysql-config.properties">
    <property name="mysql.driver" value="com.mysql.cj.jdbc.Driver"/>
    <property name="mysql.url" value="jdbc:mysql://localhost:3306/mybatis"/>
  </properties>

  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${mysql.driver}"/>
        <property name="url" value="${mysql.url}"/>
        <property name="username" value="${mysql.username}"/>
        <property name="password" value="${mysql.password}"/>
      </dataSource>
    </environment>
  </environments>
</configuration>

通过测试,你会发现 MyBatis 应用依旧可以良好的运转。不过这时你可能会产生疑惑,如果我不删除外部配置文件 mysql-config.properties 中的配置,也不删除 property 元素中的配置,到底会是谁说的算?

属性与子元素的优先级

先说结论,在 properties 元素中,通过属性 resource 和 url 获取的配置优先级高于 property 元素的配置,如果同时使用属性和子元素,且存在相同配置名的配置,最终生效的配置是通过属性获取到的配置。

这里涉及到一些源码,不过非常简单,我们在 MyBatis 应用的组成中提到过,MyBatis 中的 mybatis-config.xml 由 XMLConfigBuilder 负责解析,我们能够非常容易的在 XMLConfigBuilder 中找到解析 properties 的相关的源码:

private void propertiesElement(XNode context) throws Exception {
  if (context == null) {
    return;
  }
  
  // 获取子元素的配置
  Properties defaults = context.getChildrenAsProperties();

  // 获取properties的属性
  String resource = context.getStringAttribute("resource");
  String url = context.getStringAttribute("url");

  // 校验resource与url是否同时存在
  if (resource != null && url != null) {
    throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
  }

  // 获取通过properties属性的配置
  if (resource != null) {
    defaults.putAll(Resources.getResourceAsProperties(resource));
  } else if (url != null) {
    defaults.putAll(Resources.getUrlAsProperties(url));
  }

  // 获取通过configuration的配置
  Properties vars = configuration.getVariables();
  if (vars != null) {
    defaults.putAll(vars);
  }
  parser.setVariables(defaults);
  configuration.setVariables(defaults);
}

源码中的第 7 行调用了XNode#getChildrenAsProperties并生成了 Properties 对象,作为默认的配置对象 defaults。

public Properties getChildrenAsProperties() {
  Properties properties = new Properties();
  for (XNode child : getChildren()) {
    String name = child.getStringAttribute("name");
    String value = child.getStringAttribute("value");
    if (name != null && value != null) {
      properties.setProperty(name, value);
    }
  }
  return properties;
}

接着执行到第 10 行和第 11 行,分别从 properties 元素获取属性 resource 和 url 的值,并在 14~16 行校验了两者不能同时存在,否则会抛出异常 BuilderException,这也是 properties 元素的属性 resource 与 url 互斥的原因。

接着是 19~21 行,从属性 resource 或 url 中获取配置,并通过Properties#putAll方法添加到默认的配置对象 defaults 中。Properties 是 Java 提供的工具类,底层使用的容器是 ConcurrentHashMap,到这里就能够解释为什么通过属性 resource 和 url 获取到配置的优先级高于通过子元素 property 获取到的配置了。

最后是 26~29 行的内容,这里是获取 Configuration 对象中的配置,并添加到默认配置对象 defaults 中,那么 Configuration 对象中的配置是如何来的呢?

还记得我们构建 SqlSessionFactory 时调用的SqlSessionFactoryBuilder#build方法吗?它有非常多的重载方法:

public SqlSessionFactory build(Reader reader);

public SqlSessionFactory build(Configuration config);

public SqlSessionFactory build(Reader reader, Properties properties);

重载方法中允许我们通过 Java 编码的方式来设置 Properties,这时设置的 Properties 对象会加载到 Configuration 对象中,例如:

Reader reader = Resources.getResourceAsReader("mybatis-config.xml");

Properties properties = new Properties();
properties.setProperty("mysql.password", "123456");

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader, properties);

这时生效的配置信息是通过代码设置的 Properties。

综合上面的内容,我们可以得到 MyBatis 中配置优先级的完整结论:优先级最高的是通过代码设置的配置,其次是通过 Properties 元素的属性 resource 和 url 设置的配置,优先级最低的是通过 Properties 元素的Property 子元素设置的配置

settings 元素(设置)

settings 元素用于调整 MyBatis 中的各项设置,它在 DTD 中的定义如下:

<!ELEMENT settings (setting+)>

<!ELEMENT setting EMPTY>
<!ATTLIST setting
name CDATA #REQUIRED
value CDATA #REQUIRED
>

settings 元素没有属性,只有一个 setting 子元素,也是典型的 K-V 形式。这也就是说,如果想要调整 MyBatis 应用的设置,必须要使用 setting 子元素。

settings 元素本身没有什么特别的,重要的是它提供了非常对的 MyBatis 的配置功能,如下图:

MyBatis的设置配置.png

Tips:以上部分截取自MyBatis中文网:设置(settings)。

typeAliases 元素(别名)

tyoeAliases 元素用于为 Java 类型设置别名,它在 DTD 中的定义如下:

<!ELEMENT typeAliases (typeAlias*,package*)>

<!ELEMENT typeAlias EMPTY>
<!ATTLIST typeAlias
type CDATA #REQUIRED
alias CDATA #IMPLIED
>

<!ELEMENT package EMPTY>
<!ATTLIST package
name CDATA #REQUIRED
>

typeAliases 元素本身没有任何属性,只有两个子元素:typeAlias 元素和 package 元素

typeAlias 元素

typeAlias 元素用于设定单个 Java 类型的别名。

属性是否必填说明
type使用 Java 类型的全限名
alias自定义的别名

我们看到,alias 属性是非必填的,如果没有填写的话,MyBatis 会如何处理呢?MyBatis 会默认使用将首字母小写后 Java 类型的名字作为别名,例如:

<configuration>
  <!-- 省略其它配置 -->
  <typeAliases>
    <typeAlias type="com.wyz.entity.UserDO"/>
  </typeAliases>
  <!--省略其它配置 -->
</configuration>

此时 UserDO 在 MyBatis 中的别名是 userDO, 配置别名后我们就可以直接在映射器(Mapper.xml)中使用:

<mapper namespace="com.wyz.mapper.UserMapper">
  <select id="selectAll" resultType="userDO" >
    select user_id, name, age, gender, id_type, id_number from user
  </select>
</mapper>
package 元素

package 元素用于设定包下所有 Java 类型的别名。

属性是否必填说明
name包名

如果只使用 package 元素的话,MyBatis 同样会使用将首字母小写后的 Java 类型的名字作为别名。但 MyBatis 也提供了@Alias注解,方便为每个类型起别名,例如:

package com.wyz.entity;

@Alias("user")
public class UserDO implements Serializable {
  // 省略属性
}

接着在 mybatis-config.xml 中配置:

<configuration>
  <!-- 省略其它配置 -->
  <typeAliases>
    <package name="com.wyz.entity"/>
  </typeAliases>
  <!--省略其它配置 -->
</configuration>

此时,我们就可以在映射器(Mapper.xml)中使用 user 作为 com.wyz.entity.UserDO 的别名了。

MyBatis 内置的别名

上面是自定义别名的部分,实际上 MyBatis 已经为常见的 Java 类型定义了别名,例如:java.lang.String 在 MyBatis 中的别名是“string”,java.lang.Integer 在 MyBatis 中的别名是“int”。

你可能会有疑惑,那基本数据类型 int 有别名吗?

答案是有的,MyBatis 为 Java 中的每个基础数据类型都定义了别名,与它们的包装类型的别名有所差异,我们一起来看一下:

基础数据类型别名包装类型别名
byte_byteBytebyte
char_char/_characterCharacterchar/character
long_longLonglong
int_int/_integerIntegerint/integer
short_shortShortshort
double_doubleDoubledouble
float_floatFloatfloat
boolean_booleanBooleanboolean

基础数据类型“痛失真名”~~

当然,以上只是一部分 MyBatis 为 Java 类型定义的别名,更多常见 Java 类型的别名定义可以参考 MyBatis 的源码 TypeAliasRegistry 类。

Tips:通常我不使用 typeAliases 标签,因为使用全限名能够更加方便的在映射器(Mapper.xml)中查找到对应的 Java 类型。

typeHandlers 元素(类型处理器)

typeHandlers 元素用于定义类型处理器,即 Java 类型与数据库类型的相互转换的处理器,它在 DTD 中的定义如下:

<!ELEMENT typeHandlers (typeHandler*,package*)>

typeHandlers 元素没有属性,只定义了两个子元素:typeHandler 元素和 package 元素。

typeHandler 元素

typeHandler 元素在 DTD 中的定义如下:

<!ELEMENT typeHandler EMPTY>
<!ATTLIST typeHandler
javaType CDATA #IMPLIED
jdbcType CDATA #IMPLIED
handler CDATA #REQUIRED
>

typeHandler 元素中定义了 3 个属性:

属性是否必填说明
javaTypeJava 类型
jdbcTypeJDBC 类型
handler类型处理器全限名

typeHandler 元素,可以定义 Java 类型与 JDBC 类型互相转换的类型处理器,如:

<configuration>
  <typeHandlers>
    <typeHandler jdbcType="VARCHAR" javaType="java.lang.String" handler="org.apache.ibatis.type.StringTypeHandler"/>
  </typeHandlers>
</configuration>

StringTypeHandler 是 MyBatis 内置的类型处理器,MyBatiis 已经为我们内置了非常多的类型处理器,完全能够满足日常项目中的使用了。

Tips

  • 关于 MyBatis 内置处理器,下面会在 MyBatis 的内置处理器的部分提到;
  • 关于 typeHandler 元素中 jdbcType 属性和 javaType 属性非必填的问题,会在自定义类型处理器的部分提到。
package 元素

在 typeHandlers 元素下使用 package 元素,会加载 package 元素中指定包名下所有符合条件的类型处理器。例如:

<configuration>
  <typeHandlers>
    <package name="com.wyz.customize.handler.type"/>
  </typeHandlers>
</configuration>

如上的定义,会加载 com.wyz.customize.handler.type 中所有符合条件的类型处理器。但是 package 元素只能指定包名,MyBatis 该如何识别类型处理器是哪些 Java 类型与数据库类型之间的转换呢?

别急,我们接着往下看。

自定义类型处理器

现在,我们已经知道了如何在 mybatis-config.xml 中配置类型处理器了,接下来我们就定义自己的类型处理器。

MyBatis 中提供了两种定义类型处理器的方法:

  • 实现 TypeHandler 接口;
  • 继承抽象类 BaseTypeHandler。

其中抽象类 BaseTypeHandler 中已经做了非常多的通用实现,采用继承 BaseTypeHandler 的方法可以省去大量的编码工作,因此我这里在实现自定义类型处理器的时候选择了继承 BaseTypeHandler。

我们定义一个针对于 String 与 Varchar 的类型处理器,实现一个简单且无意义的需求:针对于参数和结果集,我们对 String 类型的字段添加后缀“_wyz”,我将这个类型处理器命名为 NewStringTypeHandler,代码如下:

package com.wyz.customize.handler.type;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;

import java.sql.*;

public class NewStringTypeHandler extends BaseTypeHandler<String> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, parameter + "_wyz");
    }

    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return rs.getString(columnName) + "_wyz";
    }

    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return rs.getString(columnIndex) + "_wyz";
    }

    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return cs.getString(columnIndex) + "_wyz";
    }
}

接下来,我们在 mybatis-config.xml 中配置这个类型处理器:

<configuration>
  <typeHandlers>
    <typeHandler jdbcType="VARCHAR" javaType="java.lang.String" handler="com.wyz.customize.handler.type.NewStringTypeHandler"/>  </typeHandlers>
</configuration>

最后我们通过测试可以看到,所有查询语句的响应结果中 VARCHAR 类型的字段都添加了后缀“_wyz”,而所有插入或修改语句在提交后,数据库中存储的所有 VARCHAR 类型的字段也被添加上了后缀“_wyz”。

前面我们已经提到了,typeHandler 元素中的属性 jdbcType 与 javaType 是非必填的,如果我们不设置 jdbcType 与 javaType 的话会发生什么?答案是自定义类型处理器依旧会被注册到 MyBatis 的类型处理器中,但是我们无法使用(涉及到源码,这部分我们放在 MyBatis 的源码分析篇中再聊)。

那么为什么 typeHandler 元素中这两个属性是非必填的呢?因为 MyBatis 还提供了两个注解@MappedTypes@MappedJdbcTypes,允许我们在自定义类型处理器上定义 Java 类型与数据库类型,如下:

@MappedTypes({String.class})
@MappedJdbcTypes({JdbcType.VARCHAR, JdbcType.CHAR})
public class NewStringTypeHandler extends BaseTypeHandler<String> {
    // 省略代码
}

这样,我们即便不在 typeHandler 元素中设置属性 jdbcType 与 javaType,MyBatis 也同样能够正确的注册自定义类型处理器。

再来看 package 元素,虽然没有元素中没有设置 jdbcType 与 javaType 的属性,但是我们想到 package 元素也可以通过@MappedTypes@MappedJdbcTypes注解,来指定 Java 类型与数据库类型的转换方式,这里我们就不再写相关的代码了。

MyBatis 内置的类型处理器

最后我们来看 MyBatis 中内置的类型处理器,在 MyBatis 3.5.15 版本中,MyBatis 为 80 种类型定义了 51 个类型处理器,我们可以通过查看 BaseTypeHandler 的实现类来查看 MyBatis 的内置处理器的数量。

为什么类型与类型处理器的数量不一致呢?

是因为 MyBatis 为了兼容 Java 中的基础类型与包装类型,以及不同数据库之间相同类型的叫法不同,需要为每种类型都注册类型处理器,但他们可以共用同一个类型处理器,例如,在 MyBatis 注册内置处理器的 TypeHandlerRegistry 的构造方法中为布尔类型注册类型处理器时:

public TypeHandlerRegistry(Configuration configuration) {
  // 省略代码
  register(Boolean.class, new BooleanTypeHandler());
  register(boolean.class, new BooleanTypeHandler());
  register(JdbcType.BOOLEAN, new BooleanTypeHandler());
  register(JdbcType.BIT, new BooleanTypeHandler());
  // 省略代码
}

这里我就不一一展示了,感兴趣的小伙伴可以自行查看 TypeHandlerRegistry 构造方法的源码。

objectFactory 元素(对象工厂)

objectFactory 元素用于定义对象工厂,对象工厂用于创建结果集的映射对象,它在 DTD 中的定义如下:

<!ELEMENT objectFactory (property*)>
<!ATTLIST objectFactory
type CDATA #REQUIRED
>

objectFactory 元素本身并没有任何属性,只定义了一个子元素 property,用于定义 objectFactory 的参数。通过 property 元素配置的属性,会在 ObjectFactory 初始化之后通过ObjectFactory#setProperties方法传递到对象工厂中

默认情况下(即不配置自定义对象工厂),MyBatis 会使用对象工厂的唯一实现类 DefaultObjectFactory,它的实现非常简单,仅仅是通过反射调用结果集对象的无参构造器完成了对象的实例化,除此之外,并没有额外的处理。

自定义对象工厂

同自定义类型处理器一样,MyBatis 提供了两种自定义对象工厂的方式:

  • 实现 ObjectFactory 接口;
  • 继承 DefaultObjectFactory。

同样的,我们可以选择继承 DefaultObjectFactory 来完成自定义对象工厂,并实现相应的方法即可。接下来,我们写一个比较“荒诞”的需求,因为我实在是没想到太好的自定义对象工厂的实际案例

这个需求是这样的,为数据库中 user 表的查询结果的 VARCHAR 类型字段添加默认值“wyz”,即 UserDO 实例对象中 String 类型的字段为 NULL 时赋默认值“wyz”。当然实现这个需求我们有好几种方式可以选择,如:通过为数据库中的字段设置默认值,或者为 Java 对象的字段设置默认值来完成。不过这里为了展示 MyBatis 的自定义对象工厂的使用方法,我通过自定义工厂来实现这个需求。

首先,我们为这个自定义工厂命名为 CustomizeObjectFactory,并在 MyBatis 的核心配置 mybatis-config.xml 中配置这个自定义对象工厂,例如:

<configuration>
  <!-- 省略配置 -->
  <objectFactory type="com.wyz.customize.factory.object.CustomizeObjectFactory">
    <property name="default" value="wyz"/>
  </objectFactory>
  <!-- 省略配置 -->
</configuration>

其中子元素 property 中定义了我们将要使用的默认值。

接着我们来完成这个自定义对象工厂,通过继承 DefaultObjectFactory 并实现相应的方法,代码如下:

package com.wyz.customize.factory.object;

import com.alibaba.fastjson2.JSON;
import com.wyz.entity.UserDO;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Properties;

public class CustomizeObjectFactory extends DefaultObjectFactory {
  
    String defaultValue;

    @Override
    public void setProperties(Properties properties) {
        defaultValue = properties.getProperty("default");
    }

    @Override
    public <T> T create(Class<T> type) {
        T t = super.create(type);
        if(type.equals(UserDO.class)) {
            Field[] fields = type.getDeclaredFields();
            for(Field field : fields) {
                if(field.getType().equals(String.class)) {
                    field.setAccessible(true);
                    try {
                        field.set(t, defaultValue);
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
        return t;
    }

    @Override
    public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        return super.create(type, constructorArgTypes, constructorArgs);
    }

    @Override
    protected Class<?> resolveInterface(Class<?> type) {
        return super.resolveInterface(type);
    }

    @Override
    public <T> boolean isCollection(Class<T> type) {
        return super.isCollection(type);
    }
}

可以看到,我重写了ObjectFactory#setProperties方法,并在该方法中为类成员变量 defaultValue 赋值,前面我们提到过,ObjectFactory#setProperties方法的作用时间是在对象工厂初始化完成之后。

接着我重写了其中一个ObjectFactory#create方法,通过super.create调用父类 DefaultObjectFactory 实现的ObjectFactory#create方法完成默认的实例化后,我们判断其类型为 UserDO,并通过反射对 UserDO 实例对象中 String 类型的字段赋默认值。

我们先把数据库中的某些字段修改为 NULL,如:

image.png

接着,执行我们的查询全部用户的测试案例,可以看到输出结果中,用户名为小明的用户,他的性别赋上了默认值“wyz”。

image.png

通常来说,我们在应用程序中,MyBatis 提供的 DefaultObjectFactory 已经能够满足绝大部分的应用场景了,我们不需要实现自定义对象工厂。

附录:DTD 简介

这部分并不是完整的 DTD 教程,如果想要完成的学习 DTD ,请移步相关教程,本文只对 mybatis-3-confg.dtd 中出现的相关语法做一个简单的介绍。

DTD(Document Type Definition)即文档类型定义,这里我引用维基百科中关于 DTD 的定义:

XML文件的文档类型定义(Document Type Definition)可以看成一个或者多个XML文件的模板,在这里可以定义XML文件中的元素、元素的属性、元素的排列方式、元素包含的内容等等。

DTD 主要用于 XML 文档的结构定义和约束,主要功能包括:

  • 定义 XML 文档中的可使用的元素,元素间的顺序,元素的嵌套关系和元素的属性等;
  • 用于验证 XML 文档的结构,在 XML 文件中引入 DTD,解析器会根据 DTD 的定义验证文档的结构;
  • 约定通过 XML 进行数据交换的标准格式,保证数据交换的正确性。

我们来截取 mybatis-3-config.dtd 的部分内容:

<!ELEMENT configuration (properties?, settings?)>

<!ELEMENT properties (property*)>
<!ATTLIST properties
resource CDATA #IMPLIED
url CDATA #IMPLIED
>

<!ELEMENT property EMPTY>
<!ATTLIST property
name CDATA #REQUIRED
value CDATA #REQUIRED
>

通过一张图来解释下上面 DTD 的内容:

关于 DTD 的内容就简单介绍到这里,已经足够应付 MyBatis 的 DTD 了。

本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!


好了,今天的内容就到这里了,如果本文对你有帮助的话,希望多多点赞支持,如果文章中出现任何错误,还请批评指正。最后欢迎大家关注分享硬核 Java 技术的金融摸鱼侠王有志,我们下次再见!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【QT学习】9.绘图,三种贴图,贴图的转换,不规则贴图(透明泡泡),简单绘图工具制作

一。绘图的解释 Qt 中提供了强大的 2D 绘图系统&#xff0c;可以使用相同的 API 在屏幕和绘图设备上进行绘制&#xff0c;它主要基于QPainter、QPaintDevice 和 QPaintEngine 这三个类。 QPainter 用于执行绘图操作&#xff0c;其提供的 API 在 GUI 或 QImage、QOpenGLPaintDev…

maya blendshape

目录 shape编辑器 maya创建blendshape python 脚本 添加形变动画 查看顶点个数 shape编辑器 打开方式&#xff1a; 窗口-动画编辑器-形变编辑器 maya创建blendshape python 脚本 import maya.cmds as cmds# 创建基础网格 - 球体 baseMesh cmds.polySphere(name"bas…

Postman 工具发送请求的技巧与实践

在开发和测试 API 时&#xff0c;发送 JSON 格式的请求是一个常见需求。 在 Postman 中构建和发送 JSON 请求 创建一个新的请求 首先&#xff0c;在 Postman 启动界面上找到并点击 “New” 按钮&#xff0c;选择 “HTTP Request” 来开始新建一个请求。这一步骤允许你定义请…

Unity射击游戏开发教程:(7)Powerup的使用

确定 PowerUp 效果应持续多长时间 我在游戏中放置的第一个道具是三重射击。当玩家收集三重射击能量时,他们可以一次发射 3 束激光,而正常情况下只能发射 1 束激光。在实施道具时,您需要考虑它们的功能以及它将如何影响游戏玩法。至于三连射&

Linux-缓冲区(简单理解)

1. 缓冲区是什么 缓冲区就是一段内存空间。 2. 为什么要有缓冲区 IO写入有两种&#xff1a; 写透模式&#xff08;WT&#xff09; 成本高&#xff0c;效率低写回模式&#xff08;WB&#xff09; 成本低&#xff0c;效率高 写透模式&#xff1a;每次的文件写入都要立即刷新…

海外仓WMS管理系统:标准化海外仓管理模式,效率和管理模式双提升

就目前的跨境电商发展速度和体量来看&#xff0c;标准化海外仓管理的模式不再是一个选项&#xff0c;而是必走之路。 今天会重点和大家聊一下&#xff0c;海外仓企业应该如何利用好WMS管理系统&#xff0c;快速的标准化仓库管理的模式&#xff0c;以及大家比较关心的&#xff0…

JAVA读取文件完成词频统计

词频统计原数据和结果数据地址&#xff1a;https://download.csdn.net/download/LiHaoHang6/88845654?spm1001.2014.3001.5501 运行效果展示&#xff1a; 原数据展示&#xff1a; 词频统计思路&#xff1a; 1&#xff1a;先通过BufferedReader来读取本地文本文件,之后将文本…

excel 按照姓名日期年月分组求和

excel 需要按照 姓名 日期的年份进行金额求和统计&#xff0c;采用sumifs 进行统计 注意&#xff1a;sumifs 不支持 合并列拆分计算&#xff0c;合并列只会计算一个值 表格数据大概如下&#xff1a;(sheet) ABC姓名日期金额A2023/01/01500A2023/01/151500B2023/01/01200B202…

基于SpringBoot开发的同城租房系统租房软件APP小程序源码

项目背景 一、市场前景 随着城市化进程的加快和人口流动性的增强&#xff0c;租房市场正逐渐成为一个不可忽视的巨大市场。传统的租房方式往往存在着信息不对称、效率低下等问题&#xff0c;而同城租房软件的出现&#xff0c;则有效地解决了这些问题&#xff0c;为租房市场注…

云计算时代,企业面临的云安全风险

如今&#xff0c;随着云计算等新兴科技的发展&#xff0c;不同类型企业间的关联越来越多&#xff0c;它们之间的业务边界已被打破&#xff0c;企业上云成为了大势所趋。云计算应用帮助企业改变了IT资源不集中的状况&#xff0c;同时&#xff0c;数据中心内存储的大量数据信息&a…

Mediator 中介者

意图 使用一个中介者对象来封装一系列的对象交互。中介者使各个对象不需要显式地互相引用&#xff0c;从而使其耦合松散&#xff0c;而且可以独立的改变他们之间的交互。 结构 Mediator&#xff08;中介者&#xff09;定义一个接口用于各同事&#xff08;Colleague&#xff0…

数值积分——复化梯形求积公式 | 北太天元

复化求积法的思想&#xff1a; 将区间 [ a , b ] [a,b] [a,b]进行 n n n等分&#xff0c;步长 h b − a n h\frac{b-a}{n} hnb−a​&#xff0c;等分点 x k a k h , k 0 , 1 , 2 , ⋯ , n x_{k}akh,k0,1,2,\cdots,n xk​akh,k0,1,2,⋯,n, 先在每个子区间 [ x k , x k 1 ] …

普惠金融淘金热:抢占‘高成长‘企业,抓住下一个十年的财富机遇!

官.网地址&#xff1a;合合TextIn - 合合信息旗下OCR云服务产品 2013年&#xff0c;十八届三中全会正式提出“发展普惠金融”&#xff0c;普惠金融自此上升为国家战略&#xff1b;十年来&#xff0c;我国普惠金融取得了长足发展&#xff0c;逐步构建了多层次、广覆盖的中国特…

文件上传漏洞-白名单检测

如何确认是否是白名单检测 上传一张图片与上传一个自己构造的后缀&#xff0c;如果只能上传图片不能上传其它后缀文件&#xff0c;说明是白名单检测。 绕过技巧 可以利用 00 截断的方式进行绕过&#xff0c;包括 %00 截断与 0x00 截断。除此之外如果网站存在文件包含漏洞&…

《环阳宗海逍遥游》

第一天:《六十八道拐》五月二日游兴浓&#xff0c;大观公园门囗逢。海埂西门再集合&#xff0c;蓝光城里意无穷。呈贡过后松茂过&#xff0c;阳宗镇上心欢融。宜良城中暂歇脚&#xff0c;六十八拐路难通。宜良住宿赏夜色&#xff0c;期待明朝再接龙。 第二天:《情人岛苗王峡行》…

【正点原子Linux连载】 第三十四章 Linux USB驱动实验 摘自【正点原子】ATK-DLRK3568嵌入式Linux驱动开发指南

1&#xff09;实验平台&#xff1a;正点原子ATK-DLRK3568开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id731866264428 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/docs/boards/xiaoxitongban 第三十…

模块化 手写实现webpack

模块化 common.js 的导入导出方法&#xff1a; require \ export 和 module.exports export 和 module.export nodejs 内存1.4G -> 2.8G cjs ESModule 主要区别&#xff1a; require属于动态类型&#xff1a;加载执行 同步 esmodul是静态类型&#xff1a;引入时并不会真的去…

mysql事故复盘: 单行字节最大阈值65535字节(原创)

背景 记得还在银行做开发&#xff0c;投产上线时&#xff0c;项目发版前&#xff0c;要提DDL的sql工单&#xff0c;mysql加1个字段&#xff0c;因为这张表为下游数据入湖入仓用的&#xff0c;长度较大。在测试库加字段没问题&#xff0c;但生产库字段加不上。 先说结论 投产…

[前端]NVM管理器安装、nodejs、npm、yarn配置

NVM管理器安装、nodejs、npm、yarn配置 NVM管理器安装 nvm(Node.js version manager) 是一个命令行应用&#xff0c;可以协助您快速地 更新、安装、使用、卸载 本机的全局 node.js 版本。 nvm下载地址&#xff1a;https://github.com/coreybutler/nvm-windows/releases 1.全部…

手撕sql面试题:根据分数进行排名,不使用窗口函数

分享一道面试题&#xff1a; 有一个分数表id 是该表的主键。该表的每一行都包含了一场考试的分数。Score 是一个有两位小数点的浮点值。 以下是表结构和数据&#xff1a; Create table Scores ( id int(11) NOT NULL AUTO_INCREMENT, score DECIMAL(3,2), PRIMARY KEY…