文章目录
mybatis-config.xml介绍
在介绍【mybatis基础操作】一文中开头提到过这么一个代码片段
java
try (Reader reader = Resources.getResourceAsReader("your mybatis-config.xml path")) {
// DefaultSqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (IOException e) {
throw new RuntimeException(e);
}
其中Reader reader = Resources.getResourceAsReader("your mybatis-config.xml path")
是用来加载配置文件解析成Reader流。大家可以把这个方法当做工具类来使用, 它还有很多重载方法, 比较丰富。
注意这句代码 sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
, 它是解析配置文件的关键。
在了解这行代码之前我们先来看一下mybatis-config.xml
的dtd文件https://mybatis.org/dtd/mybatis-3-config.dtd
xml
<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>
这是开头一段代码, 定义了configuration
元素的合法结构和可以配置的属性们以及它们的顺序
- configuration: 这是 MyBatis 主配置元素的名称。
- 子元素列表:
- properties?: 可选,用于配置外部属性文件。
- settings?: 可选,用于设置 MyBatis 的行为配置。
- typeAliases?: 可选,定义 Java 类型的别名。
- typeHandlers?: 可选,配置自定义的类型处理器。
- objectFactory?: 可选,自定义对象创建工厂。
- objectWrapperFactory?: 可选,自定义对象包装工厂。
- reflectorFactory?: 可选,自定义反射工厂。
- plugins?: 可选,用于注册 MyBatis 插件。
- environments?: 可选,定义 MyBatis 环境(如数据库连接信息)。
- databaseIdProvider?: 可选,用于数据库厂商标识配置。
- mappers?: 可选,配置映射器(mapper)类。
符号说明 - ?: 表示该元素是可选的,可以出现零次或一次
- *: 表示该元素可以出现零次或多次
- +: 表示该元素至少出现一次,可以出现多次
SqlSessionFactoryBuilder
在了解了mybatis-config.xml
的基本构造之后, 再来分析SqlSessionFactoryBuilder
java
SqlSessionFactory build(Reader reader)
SqlSessionFactory build(Reader reader, String environment)
SqlSessionFactory build(Reader reader, Properties properties)
SqlSessionFactory build(Reader reader, String environment, Properties properties)
SqlSessionFactory build(InputStream inputStream)
SqlSessionFactory build(InputStream inputStream, String environment)
SqlSessionFactory build(InputStream inputStream, Properties properties)
SqlSessionFactory build(InputStream inputStream, String environment, Properties properties)
SqlSessionFactory build(Configuration config)
内部重载了多个方法, 主要分为Reader
和InputStream
以及Configuration
三种方式,。我们除了可以自定义文件流之外,还可以指定环境environment
还有全局属性properties
亦或者直接使用自定义的Configuration配置。
我们主要分析SqlSessionFactory build(Reader reader, String environment, Properties properties)
方法
java
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 解析配置文件等等所有
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
它创建了XMLConfigBuilder
用来解析配置生成一个Configuration
类。
XMLConfigBuilder
下面是XMLConfigBuilder的构造器们
java
XMLConfigBuilder(Reader reader)
XMLConfigBuilder(Reader reader, String environment)
XMLConfigBuilder(Reader reader, String environment, Properties props)
XMLConfigBuilder(Class<? extends Configuration> configClass, Reader reader, String environment,
Properties props)
XMLConfigBuilder(InputStream inputStream)
XMLConfigBuilder(InputStream inputStream, String environment)
XMLConfigBuilder(InputStream inputStream, String environment, Properties props)
XMLConfigBuilder(Class<? extends Configuration> configClass, InputStream inputStream, String environment,
Properties props)
XMLConfigBuilder(Class<? extends Configuration> configClass, XPathParser parser, String environment,
Properties props)
主要也是分三种, Reader
、InputStream
和XPathParser
下面是核心构造器
java
private XMLConfigBuilder(Class<? extends Configuration> configClass, XPathParser parser, String environment,
Properties props) {
// 反射创建Configuration对象
super(newConfig(configClass));
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
newConfig方法通过反射使用无参构造器创建了Configuration
对象, 然后给configuration设置了全局变量props
;注意到这里定义了xml解析器, 到这里一个最基本的Configuration就实例化完成。
-> 顺便说一嘴, xml文件解析器除了xPath还有dom4j,jsoup等常用解析器
开始解析配置文件
java
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
// 解析mybatis-config.xml文件中的标签们
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 文件访问器
loadCustomVfsImpl(settings);
// 日志LogFactory, 默认使用SLF4J
loadCustomLogImpl(settings);
// 扫描别名类
typeAliasesElement(root.evalNode("typeAliases"));
// 扫描插件(拦截器)
pluginsElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 添加setting的属性到configuration中
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 数据源相关
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlersElement(root.evalNode("typeHandlers"));
// 解析mapper接口
mappersElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
从这里可以看到解析配置文件的顺序和dtd中定义的是一样的。下面逐步解析每一个标签
properties
作用: 定义框架的全局属性
java
private void propertiesElement(XNode context) throws Exception {
if (context == null) {
return;
}
Properties defaults = context.getChildrenAsProperties();
// 从文件中拿
String resource = context.getStringAttribute("resource");
// 从网络中拿
String url = context.getStringAttribute("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.");
}
if (resource != null) {
// 磁盘io文件
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
// 网络io中获取, 例如调用某个接口获取的属性
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
context.getChildrenAsProperties()
方法会把properties
中的标签- 得到子节点的
resource
和url
属性, 但是它俩不能同时存在哦, 使用工具类Resources
加载进来 - 注意这句话
parser.setVariables(defaults);
它将加载的属性们放到xml文件解析器里, 那么我们在定义其它属性的时候就可以直接使用${属性名}的方式引用定义的属性们 - 将属性设置到
configuration
中, 供框架随时读取
可选属性
属性名 | 说明 | 限制 |
---|---|---|
property | 定义单个属性(子节点元素); 以name和value分别对应属性名和属性值 | 0个或多个 |
resource | 属性文件的位置(属性元素),磁盘地址 | 0个或1个 |
url | 属性文件的url(属性元素), 网络地址 | 0个或1个 |
例如
xml
<properties resource="org/apache/ibatis/propertiesdir/variables.properties">
<property name="username" value="root"/>
</properties>
说明
- properties的resource和url只能二选一,或者都不存在, property子标签可以配置多个
- properties配置的属性是全局的, 优先级是 构造config时的属性 > resource=url > property子节点
- 默认获取属性值的方式是
${属性名}, 如果需要启动默认值获取方式`$
{属性名:默认值}`的方式获取, 需要开启改特性
xml
<properties resource="org/mybatis/example/config.properties">
<!-- ... -->
<property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/> <!-- 启用默认值特性 -->
</properties>
- 如果属性名使用了
:
字符(如:db:username),或者在 SQL 映射中使用了 OGNL 表达式的三元运算符(如: ${tableName != null ? tableName : 'global_constants'}),就需要设置特定的属性来修改分隔属性名和默认值的字符。例如:
xml
<properties resource="org/mybatis/example/config.properties">
<!-- ... -->
<property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="?:"/> <!-- 修改默认值的分隔符 -->
</properties>
那么此时你的获取默认值的方式就变成了下面这样
xml
<dataSource type="POOLED">
<!-- ... -->
<property name="username" value="${db:username?:ut_user}"/>
</dataSource>
db:username
是属性名?:
是默认值分隔符ut_user
是默认值
setting
setting用来定义mybatis框架中核心的配置, 可以改变框架运行的行为, 下表描述了设置中各项设置的含义、默认值等。
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true | false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 | true | false | false |
aggressiveLazyLoading | 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。 | true | false | false (在 3.4.1 及之前的版本中默认为 true) |
multipleResultSetsEnabled | 是否允许单个语句返回多结果集(需要数据库驱动支持)。 | true | false | true |
useColumnLabel | 使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察。 | true | false | true |
useGeneratedKeys | 允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。 | true | false | false |
autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。 | NONE, PARTIAL, FULL | PARTIAL |
autoMappingUnknownColumnBehavior | 指定发现自动映射目标未知列(或未知属性类型)的行为。 NONE: 不做任何反应 WARNING: 输出警告日志('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等级必须设置为 WARN) FAILING: 映射失败 (抛出 SqlSessionException) | NONE, WARNING, FAILING | NONE |
defaultExecutorType | 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement); BATCH 执行器不仅重用语句还会执行批量更新。 | SIMPLE, REUSE, BATCH | SIMPLE |
defaultStatementTimeout | 设置超时时间,它决定数据库驱动等待数据库响应的秒数。 | 任意正整数 | 未设置 (null) |
defaultFetchSize | 为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。 | 任意正整数 | 未设置 (null) |
defaultResultSetType | 指定语句默认的滚动策略。(新增于 3.5.2) | FORWARD_ONLY, SCROLL_SENSITIVE, SCROLL_INSENSITIVE, DEFAULT(等同于未设置) | 未设置 (null) |
safeRowBoundsEnabled | 是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。 | true | false | false |
safeResultHandlerEnabled | 是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false。 | true | false | true |
mapUnderscoreToCamelCase | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 | true | false | false |
localCacheScope | MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。 | SESSION, STATEMENT | SESSION |
jdbcTypeForNull | 当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。 某些数据库驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 | JdbcType 常量,常用值:NULL、VARCHAR 或 OTHER。 | OTHER |
lazyLoadTriggerMethods | 指定对象的哪些方法触发一次延迟加载。 | 用逗号分隔的方法列表。 | equals, clone, hashCode, toString |
defaultScriptingLanguage | 指定动态 SQL 生成使用的默认脚本语言。 | 一个类型别名或全限定类名。 | org.apache.ibatis.scripting.xmltags.XMLLanguageDriver |
defaultEnumTypeHandler | 指定 Enum 使用的默认 TypeHandler 。(新增于 3.4.5) | 一个类型别名或全限定类名。 | org.apache.ibatis.type.EnumTypeHandler |
callSettersOnNulls | 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值进行初始化时比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。 | true | false | false |
returnInstanceForEmptyRow | 当返回行的所有列都是空时,MyBatis 默认返回 null。 当开启这个设置时,MyBatis 会返回一个空实例。 请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于 3.4.2) | true | false | false |
logPrefix | 指定 MyBatis 增加到日志名称的前缀。 | 任何字符串 | 未设置 |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J, LOG4J(3.5.9 起废弃), LOG4J2, JDK_LOGGING, COMMONS_LOGGING, STDOUT_LOGGING, NO_LOGGING | 未设置 |
proxyFactory | 指定 Mybatis 创建可延迟加载对象所用到的代理工具。 | CGLIB(3.5.10 起废弃), JAVASSIST | JAVASSIST (MyBatis 3.3 以上) |
vfsImpl | 指定 VFS 的实现 | 自定义 VFS 的实现的类全限定名,以逗号分隔。 | 未设置 |
useActualParamName | 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1) | true | false | true |
configurationFactory | 指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为 static Configuration getConfiguration() 的方法。(新增于 3.2.3) | 一个类型别名或完全限定类名。 | 未设置 |
shrinkWhitespacesInSql | 从 SQL 中删除多余的空格字符。请注意,这也会影响 SQL 中的文字字符串。 (新增于 3.5.5) | true | false | false |
defaultSqlProviderType | 指定一个拥有 provider 方法的 sql provider 类 (新增于 3.5.6)。 这个类适用于指定 sql provider 注解上的 type(或 value) 属性(当这些属性在注解中被忽略时)。 (e.g. @SelectProvider) | 类型别名或者全限定名 | 未设置 |
nullableOnForEach | 为 'foreach' 标签的 'nullable' 属性指定默认值。(新增于 3.5.9) | true | false | false |
argNameBasedConstructorAutoMapping | 当应用构造器自动映射时,参数名称被用来搜索要映射的列,而不再依赖列的顺序。(新增于 3.5.10) | true | false | false |
一个配置完整的 settings 元素的示例如下:
xml
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="safeResultHandlerEnabled" value="true"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
<setting name="defaultScriptingLanguage" value="org.apache.ibatis.scripting.xmltags.XMLLanguageDriver"/>
<setting name="defaultEnumTypeHandler" value="org.apache.ibatis.type.EnumTypeHandler"/>
<setting name="callSettersOnNulls" value="false"/>
<setting name="returnInstanceForEmptyRow" value="false"/>
<setting name="logPrefix" value="exampleLogPreFix_"/>
<setting name="logImpl" value="SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING"/>
<setting name="proxyFactory" value="CGLIB | JAVASSIST"/>
<setting name="vfsImpl" value="org.mybatis.example.YourselfVfsImpl"/>
<setting name="useActualParamName" value="true"/>
<setting name="configurationFactory" value="org.mybatis.example.ConfigurationFactory"/>
</settings>
这里注意下vfsImpl属性, 全称是*Virtual File System, *是一种用于抽象文件系统操作的机制。它允许应用程序以统一的方式访问不同的文件系统和存储设备,而不需要了解其底层实现细节。通过 VFS,开发者可以访问本地文件系统、网络文件系统(如 NFS)、虚拟文件系统(如内存文件系统)等
还有logImpl
, 可以指定使用的日志实现。
setting相关属性的具体应用, 会在后面的文章中介绍,这里有个基础的认识即可。
类型别名(typeAliases)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:
xml
<typeAliases>
<!-- 这里typeAlias和package是有序的,要先配置typeAlias再配置package -->
<typeAlias type="org.apache.ibatis.mytest.learntest.entity.Student" alias="student"/>
<package name="org.apache.ibatis.mytest.learntest.entity"/>
</typeAliases>
它有两种加载方式typeAlias
和package
, typeAlias要定义在package之前。默认使用TypeAliasRegistry
类进行注册, TypeAliasRegistry
类是Configuration
类中的一个常量,不可以修改的那种哦, 自定义Configuration
就另当别论了。
package
用于扫描指定包下的所有类, 别名可以通过类上的@Alias注解定义
typeAlias
可以指定具体的类的别名, 如果没有配置alias
属性,那么可以通过类上的@Alias注解定义别名, 否则默认使用类的简单类名作为别名
注意, 别名所属的类不能是匿名类
、接口
、内部类(非静态内部类)
mybatis默认注册的别名如下
别名 | 映射的类型 |
---|---|
_byte | byte |
_char (since 3.5.10) | char |
_character (since 3.5.10) | char |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
char (since 3.5.10) | Character |
character (since 3.5.10) | Character |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
biginteger | BigInteger |
object | Object |
date[] | Date[] |
decimal[] | BigDecimal[] |
bigdecimal[] | BigDecimal[] |
biginteger[] | BigInteger[] |
object[] | Object[] |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
扫描插件(plugins)
也就是我们常说的拦截器
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
拦截器定义如下
xml
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
拦截器必须要有默认无参构造, 并且需要实现Interceptor
接口
一个拦截器定义如下
java
// ExamplePlugin.java
@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
private Properties properties = new Properties();
@Override
public Object intercept(Invocation invocation) throws Throwable {
// implement pre processing if need
Object returnObject = invocation.proceed();
// implement post processing if need
return returnObject;
}
@Override
public void setProperties(Properties properties) {
this.properties = properties;
}
}
上面的插件将会拦截在 Executor 实例中所有的 update
方法调用, 这里的 Executor 是负责执行底层映射语句的内部对象。拦截器解析完后会添加到configuration
中。
后面会详细的介绍拦截器, 这里了解一下就好。
解析objectFactory(对象工厂)
每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。 如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现。比如:
java
// ExampleObjectFactory.java
public class ExampleObjectFactory extends DefaultObjectFactory {
@Override
public <T> T create(Class<T> type) {
return super.create(type);
}
@Override
public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
return super.create(type, constructorArgTypes, constructorArgs);
}
@Override
public void setProperties(Properties properties) {
super.setProperties(properties);
}
@Override
public <T> boolean isCollection(Class<T> type) {
return Collection.class.isAssignableFrom(type);
}}
配置如下
xml
<!-- mybatis-config.xml -->
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
<property name="someProperty" value="100"/>
</objectFactory>
type
属性用来定义对象工厂类型
property
子节点用来给对象工厂设置值
ObjectFactory 接口很简单,它包含两个创建实例用的方法,一个是处理默认无参构造方法的(必须),另外一个是处理带参数的构造方法的。 另外,setProperties 方法可以被用来配置 ObjectFactory,在初始化你的 ObjectFactory 实例后, objectFactory 元素体中定义的属性会被传递给 setProperties 方法。
解析完后会设置到configuration
中
解析objectWrapperFactory
用来包装对象的工厂
配置如下
xml
<objectWrapperFactory type="org.apache.ibatis.mytest.learntest.objectwrapperfactory.MyObjectWrapperFactory"/>
定义demo如下
java
public class MyObjectWrapperFactory implements ObjectWrapperFactory {
@Override
public boolean hasWrapperFor(Object object) {
return false;
}
@Override
public ObjectWrapper getWrapperFor(MetaObject metaObject, Object object) {
return null;
}
}
它也必须有无参构造, 必须实现ObjectWrapperFactory
接口,作用不大
解析完后会设置到configuration
中
解析reflectorFactory
用于创建对象的反射器Reflector
, 以便操作对象的行为, 对象反射器Reflector
在mybatis框架中用的非常多
配置如下
xml
<reflectorFactory type="org.apache.ibatis.mytest.core.MyReflectorFactory"/>
type
属性用来指定反射工厂的类
类定义
java
public class MyReflectorFactory extends DefaultReflectorFactory {
}
注意 反射工厂必须是ReflectorFactory
的子类
解析完后会设置到configuration
中
settingsElement()方法
该方法将setting
配置的属性或者默认值添加到configuration
中
环境配置(environments)
environmentsElement(root.evalNode("environments")) 方法用于解析db环境
java
private void environmentsElement(XNode context) throws Exception {
// 可以不配置
if (context == null) {
return;
}
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
// 只解析environments标签的default属性对应的配置项
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());
break;
}
}
}
解析流程不难看出, 当没有指定environment
参数时, 默认使用的是default
对应的环境(必须由参数指定一个或者用default
属性指定一个)。子节点是个数组, 说明我们可以配置多个environment
节点, 但是这里只有启用一个.
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推,记起来很简单: 每个数据库对应一个 SqlSessionFactory 实例
为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。可以接受环境配置的两个方法签名是:
java
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);
environments 元素定义了如何配置环境。
xml
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
注意一些关键点:
- 默认使用的环境 ID(比如:default="development")。
- 每个 environment 元素定义的环境 ID(比如:id="development")。
- 事务管理器的配置(比如:type="JDBC")。
- 数据源的配置(比如:type="POOLED")。
事务管理器(transactionManager)
在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):
JDBC -- 这个配置直接使用了 JDBC 的提交和回滚功能,它依赖从数据源获得的连接来管理事务作用域。默认情况下,为了与某些驱动程序兼容,它在关闭连接时启用自动提交。然而,对于某些驱动程序来说,启用自动提交不仅是不必要的,而且是一个代价高昂的操作。因此,从 3.5.10 版本开始,你可以通过将 "skipSetAutoCommitOnClose" 属性设置为 "true" 来跳过这个步骤。例如:
xml
<transactionManager type="JDBC">
<property name="skipSetAutoCommitOnClose" value="true"/>
</transactionManager>
MANAGED -- 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。例如:
xml
<transactionManager type="MANAGED">
<property name="closeConnection" value="false"/>
</transactionManager>
提示 如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。
这两种事务管理器类型都不需要设置任何属性。它们其实是类型别名,换句话说,你可以用 TransactionFactory 接口实现类的全限定名或类型别名代替它们。
java
public interface TransactionFactory {
default void setProperties(Properties props) { // 从 3.5.2 开始,该方法为默认方法
// 空实现
}
Transaction newTransaction(Connection conn);
Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}
在事务管理器实例化后,所有在 XML 中配置的属性将会被传递给 setProperties() 方法。你的实现还需要创建一个 Transaction 接口的实现类,这个接口也很简单:
java
public interface Transaction {
Connection getConnection() throws SQLException;
void commit() throws SQLException;
void rollback() throws SQLException;
void close() throws SQLException;
Integer getTimeout() throws SQLException;
}
使用这两个接口,你可以完全自定义 MyBatis 对事务的处理。
数据源(dataSource)
dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
- 大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。
mybatis有三种内置的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]") UNPOOLED -- 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。UNPOOLED 类型的数据源仅仅需要配置以下 5 种属性:
driver
-- 这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)。
url
-- 这是数据库的 JDBC URL 地址。
username
-- 登录数据库的用户名。
password
-- 登录数据库的密码。
defaultTransactionIsolationLevel
-- 默认的连接事务隔离级别。
defaultNetworkTimeout
-- 等待数据库操作完成的默认网络超时时间(单位:毫秒)。查看 java.sql.Connection#setNetworkTimeout() 的 API 文档以获取更多信息。
作为可选项,你也可以传递属性给数据库驱动。只需在属性名加上"driver."前缀即可,例如:
- driver.encoding=UTF8
这将通过 DriverManager.getConnection(url, driverProperties) 方法传递值为 UTF8 的 encoding 属性给数据库驱动。
POOLED
-- 这种数据源的实现利用"池"的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。
除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源:
poolMaximumActiveConnections
-- 在任意时间可存在的活动(正在使用)连接数量,默认值:10poolMaximumIdleConnections
-- 任意时间可能存在的空闲连接数。poolMaximumCheckoutTime
-- 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)poolTimeToWait
-- 这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。poolMaximumLocalBadConnectionTolerance
-- 这是一个关于坏连接容忍度的底层设置, 作用于每一个尝试从缓存池获取连接的线程。 如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过poolMaximumIdleConnections
与poolMaximumLocalBadConnectionTolerance
之和。 默认值:3(新增于 3.4.5)poolPingQuery
-- 发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是"NO PING QUERY SET",这会导致多数数据库驱动出错时返回恰当的错误消息。poolPingEnabled
-- 是否启用侦测查询。若开启,需要设置poolPingQuery
属性为一个可执行的 SQL 语句(最好是一个速度非常快的 SQL 语句),默认值:false。poolPingConnectionsNotUsedFor
-- 配置 poolPingQuery 的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 --- 当然仅当 poolPingEnabled 为 true 时适用)。
数据库厂商标识(databaseIdProvider)
MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 MyBatis 会加载带有匹配当前数据库 databaseId 属性和所有不带 databaseId 属性的语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。 为支持多厂商特性,只要像下面这样在 mybatis-config.xml 文件中加入 databaseIdProvider 即可:
xml
<databaseIdProvider type="DB_VENDOR" />
databaseIdProvider 对应的 DB_VENDOR 实现会将 databaseId 设置为 DatabaseMetaData#getDatabaseProductName() 返回的字符串。 由于通常情况下这些字符串都非常长,而且相同产品的不同版本会返回不同的值,你可能想通过设置属性别名来使其变短:
xml
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
</databaseIdProvider>
在提供了属性别名时,databaseIdProvider 的 DB_VENDOR 实现会将 databaseId 设置为数据库产品名与属性中的名称第一个相匹配的值,如果没有匹配的属性,将会设置为 "null"。 在这个例子中,如果 getDatabaseProductName() 返回"Oracle (DataDirect)",databaseId 将被设置为"oracle"。
java
public interface DatabaseIdProvider {
default void setProperties(Properties p) { // 从 3.5.2 开始,该方法为默认方法
// 空实现
}
String getDatabaseId(DataSource dataSource) throws SQLException;
}
类型处理器(typeHandlers)
MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器。
类型处理器 | Java 类型 | JDBC 类型 |
---|---|---|
BooleanTypeHandler | java.lang.Boolean, boolean | 数据库兼容的 BOOLEAN |
ByteTypeHandler | java.lang.Byte, byte | 数据库兼容的 NUMERIC 或 BYTE |
ShortTypeHandler | java.lang.Short, short | 数据库兼容的 NUMERIC 或 SMALLINT |
IntegerTypeHandler | java.lang.Integer, int | 数据库兼容的 NUMERIC 或 INTEGER |
LongTypeHandler | java.lang.Long, long | 数据库兼容的 NUMERIC 或 BIGINT |
FloatTypeHandler | java.lang.Float, float | 数据库兼容的 NUMERIC 或 FLOAT |
DoubleTypeHandler | java.lang.Double, double | 数据库兼容的 NUMERIC 或 DOUBLE |
BigDecimalTypeHandler | java.math.BigDecimal | 数据库兼容的 NUMERIC 或 DECIMAL |
StringTypeHandler | java.lang.String | CHAR, VARCHAR |
ClobReaderTypeHandler | java.io.Reader | - |
ClobTypeHandler | java.lang.String | CLOB, LONGVARCHAR |
NStringTypeHandler | java.lang.String | NVARCHAR, NCHAR |
NClobTypeHandler | java.lang.String | NCLOB |
BlobInputStreamTypeHandler | java.io.InputStream | - |
ByteArrayTypeHandler | byte[] | 数据库兼容的字节流类型 |
BlobTypeHandler | byte[] | BLOB, LONGVARBINARY |
DateTypeHandler | java.util.Date | TIMESTAMP |
DateOnlyTypeHandler | java.util.Date | DATE |
TimeOnlyTypeHandler | java.util.Date | TIME |
SqlTimestampTypeHandler | java.sql.Timestamp | TIMESTAMP |
SqlDateTypeHandler | java.sql.Date | DATE |
SqlTimeTypeHandler | java.sql.Time | TIME |
ObjectTypeHandler | Any | OTHER 或未指定类型 |
EnumTypeHandler | Enumeration Type | VARCHAR 或任何兼容的字符串类型,用来存储枚举的名称(而不是索引序数值) |
EnumOrdinalTypeHandler | Enumeration Type | 任何兼容的 NUMERIC 或 DOUBLE 类型,用来存储枚举的序数值(而不是名称) |
SqlxmlTypeHandler | java.lang.String | SQLXML |
InstantTypeHandler | java.time.Instant | TIMESTAMP |
LocalDateTimeTypeHandler | java.time.LocalDateTime | TIMESTAMP |
LocalDateTypeHandler | java.time.LocalDate | DATE |
LocalTimeTypeHandler | java.time.LocalTime | TIME |
OffsetDateTimeTypeHandler | java.time.OffsetDateTime | TIMESTAMP |
OffsetTimeTypeHandler | java.time.OffsetTime | TIME |
ZonedDateTimeTypeHandler | java.time.ZonedDateTime | TIMESTAMP |
YearTypeHandler | java.time.Year | INTEGER |
MonthTypeHandler | java.time.Month | INTEGER |
YearMonthTypeHandler | java.time.YearMonth | VARCHAR 或 LONGVARCHAR |
JapaneseDateTypeHandler | java.time.chrono.JapaneseDate | DATE |
你可以重写已有的类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。 具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 并且可以(可选地)将它映射到一个 JDBC 类型。比如:
java
// ExampleTypeHandler.java
@MappedJdbcTypes(JdbcType.VARCHAR)
public class ExampleTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getString(columnName);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getString(columnIndex);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getString(columnIndex);
}
}
配置文件中添加
xml
<!-- mybatis-config.xml -->
<typeHandlers>
<typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>
使用上述的类型处理器将会覆盖已有的处理 Java String 类型的属性以及 VARCHAR 类型的参数和结果的类型处理器。 要注意 MyBatis 不会通过检测数据库元信息来决定使用哪种类型,所以你必须在参数和结果映射中指明字段是 VARCHAR 类型, 以使其能够绑定到正确的类型处理器上。这是因为 MyBatis 直到语句被执行时才清楚数据类型。
通过类型处理器的泛型,MyBatis 可以得知该类型处理器处理的 Java 类型,不过这种行为可以通过两种方法改变:
- 在类型处理器的配置元素(typeHandler 元素)上增加一个
javaType
属性(比如:javaType="String"
); - 在类型处理器的类上增加一个
@MappedTypes
注解指定与其关联的 Java 类型列表。 如果在 javaType 属性中也同时指定,则注解上的配置将被忽略。
可以通过两种方式来指定关联的 JDBC 类型:
- 在类型处理器的配置元素上增加一个
jdbcType
属性(比如:jdbcType="VARCHAR"
); - 在类型处理器的类上增加一个
@MappedJdbcTypes
注解指定与其关联的 JDBC 类型列表。 如果在 jdbcType 属性中也同时指定,则注解上的配置将被忽略。
当在 ResultMap
中决定使用哪种类型处理器时,此时 Java 类型是已知的(从结果类型中获得),但是 JDBC 类型是未知的。 因此 Mybatis 使用 javaType=[Java 类型], jdbcType=null
的组合来选择一个类型处理器。 这意味着使用 @MappedJdbcTypes
注解可以限制类型处理器的作用范围,并且可以确保,除非显式地设置,否则类型处理器在 ResultMap
中将不会生效。 如果希望能在 ResultMap
中隐式地使用类型处理器,那么设置 @MappedJdbcTypes
注解的 includeNullJdbcType=true
即可。 然而从 Mybatis 3.4.0 开始,如果某个 Java 类型只有一个注册的类型处理器,即使没有设置 includeNullJdbcType=true
,那么这个类型处理器也会是 ResultMap
使用 Java 类型时的默认处理器。
xml
<!-- mybatis-config.xml -->
<typeHandlers>
<package name="org.mybatis.example"/>
</typeHandlers>
注意在使用自动发现功能的时候,只能通过注解方式来指定 JDBC 的类型。
你可以创建能够处理多个类的泛型类型处理器。为了使用泛型类型处理器, 需要增加一个接受该类的 class 作为参数的构造器,这样 MyBatis 会在构造一个类型处理器实例的时候传入一个具体的类。
java
//GenericTypeHandler.java
public class GenericTypeHandler<E extends MyObject> extends BaseTypeHandler<E> {
private Class<E> type;
public GenericTypeHandler(Class<E> type) {
if (type == null) throw new IllegalArgumentException("Type argument cannot be null");
this.type = type;
}
...
映射器(mappers)
既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。例如:
xml
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
xml
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
xml
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
xml
<!-- 将包内的映射器接口全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
对于全局配置文件的解析到这里就结束了, 该篇文章绝大部分直接使用了官网的说明, 官网地址, 解析完的所有数据都添加到了configuration
中,别忘了我们创建XMLConfigBuilder
的时候是可以传入自定的configuration
的, 所以我们完全可以没有mybatis-config.xml
配置文件。
这一章就结束了, 下一章会对其中某些部分单独拎出来演示效果。