MyBatis系列三 配置(上)
第2章我们只是粗浅地讨论了 MyBatis的组成和它们大致的用法,这章的任务是详细 讨论MyBatis的配置。MyBatis的配置文件对整个MyBatis体系产生深远的影响,所以我们 需要认真学习它。先来看一下MyBatis配置XML文件的层次结构。注意,这些层次是不 能够颠倒顺序的,如果颠倒顺序,MyBatis在解析XML文件的时候就会出现异常。先来了 解一下MyBatis配置XML文件的层次结构,如代码清单所示。
这就是全部MyBatis的配置元素,看起来还是比较简单的。
我们需要了解它们具体的配置方法和使用方法,才能知道MyBatis有什么用,它们的 功能是什么。本章主要讨论它们的用法,后面谈论MyBatis运行原理的时候我们将会看到 它们在整个运行过程中是怎么调度的。这章中不讨论plugin元素的用法,在没有理解MyBatis的运行原理之前,是没有办法很清晰地理解插件的,而使用插件是一件十分危险的事情,你必须慎重使用它。
一、properties 元素
properties是一个配置属性的元素,让我们能在配置文件的上下文中使用它。
MyBatis提供3种配置方式。
- property 子元素。
- properties配置文件。
- 程序参数传递。
1、property 子元素
property子元素的配置方法如代码清单3.2所示。
这样我们就可以在上下文中使用已经配置好的属性值了。我们配置数据库时就可以按照代码清单3.3进行配置。
2、properties 配置文件
更多时候,我们希望使用properties配置文件来配置属性值,以方便我们在多个配置文 件中重复使用它们,也方便日后维护和随时修改,这些在MyBatis中是很容易做到的,我 们先来看一下properties文件,如代码清单3-4所示。
我们把这个properties文件放在源包下,只要这样引入这个配置文件即可。
<properties resource="jdbc.properties"/>
3、程序参数传递
在实际工作中,我们常常遇到这样的问题:系统是由运维人员去配置的,生产数据库的用户密码对于开发者而言是保密的,而且为了安全,运维人员要求对配置文件中的数据库用户和密码进行加密,这样我们的配置文件中往往配置的是加密过后的数据库信息, 而无法通过加密的字符串去连接数据库,这个时候可以通过编码的形式来满足我们遇到的场景。
下面假设jdbc.properties文件中的username和password两个属性使用了加密的字符串, 这个时候我们需要在生成SqlSessionFactory之前将它转化为明文,而系统已经提供了解密 的方法decode(str),让我们来看看如何使用代码的方式来完成SqlSessionFactory的创建。
Inputstream cfgStream = null; Reader cfgReader = null; Inputstream proStream = null; Reader proReader = null; Properties properties = null; try { //读入配置文件流 cfgStream = Resources.getResourceAsStream(nmybatis-config. xmlH); cfgReader = new InputStreamReader(cfgStream); //读入属性文件 proStream = Resources.getResourceAsStream(njdbc.propertiesn); proReader = new InputStreamReader(proStream); properties = new Properties (); properties.load(proReader); //解密为明文 properties.setProperty(nusernamen, decode (properties . getProperty ("username11))); properties.setProperty("password”, decode (properties . getProperty ("password''))); ) catch (lOException ex) ( Logger.getLogger(SqlSessionFactoryUtil.class.getName ()) .log(Level.SEVERE, null, ex); } synchronized(CLASS_LOCK) { if (sqlSessionFactory == null) ( //使用属性来创建SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(cfgReader, properties); } }
这样我们完全可以在jdbc.properties配置密文,满足对系统安全的要求。
4、优先级
MyBatis支持的3种配置方式可能同时出现,并且属性还会重复配置。这3种方式是 存在优先级的,MyBatis将按照下面的顺序来加载。
- 在properties元素体内指定的属性首先被读取。
- 根据properties元素中的resource属性读取类路径下属性文件,或者根据url属性指 定的路径读取属性文件,并覆盖已读取的同名属性。
- 读取作为方法参数传递的属性,并覆盖已读取的同名属性。
因此,通过方法参数传递的属性具有最高优先级,resource/url属性中指定的配置文件 次之,最低优先级的是properties属性中指定的属性。因此,实际操作中我们需要注意以 下3点。
- 不要使用混合的方式,这样会使得管理混乱。
- 首选的方式是使用properties文件。
- 如果我们需要对其进行加密或者其他加工以满足特殊的要求,不妨按示例的方法处 理。这样做的好处是使得配置都来自于同一个配置文件,就不容易产生没有必要的歧义, 也为日后统一管理提供了方便。
二、设置
设置(settings)在MyBatis中是最复杂的配置,同时也是最为重要的配置内容之一,它会改变MyBatis运行时的行为。即使不配置settings, MyBatis也可以正常的工作,不过 了解settings的配置内容,以及它们的作用仍然十分必要。Settings的配置内容如表3.1所示,它描述了设置中各项的意义、有效值和默认值等内 容。
表3-1 settings的配置内容
设置参数 |
描 述 |
有效值 |
默认值 |
cacheEnabled |
该配置影响所有映射器中配置的缓存全局开关 |
true、false |
true |
lazyLoadingEnabled |
延迟加载的全局开关。当它开启时,所有关联对 象都会延迟加载。特定关联关系中可通过设置 fetchType属性来覆盖该项的开关状态 |
true> false |
false |
aggressiveLazy Loading |
当启用时,对任意延迟属性的调用会使带有延迟 加载属性的对象完整加载;反之,每种属性将会按 需加载 |
true> false |
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 |
defaultExecutorType |
配置默认的执行器。 SIMPLE是普通的执行器; REUSE执行器会重用预处理语句(prepared statements); BATCH执行器将重用语句并执行批量更新 |
SIMPLE、REUSE、 BATCH |
SIMPLE |
defaultStatementTimeout |
设置超时时间,它决定驱动等待数据库响应的秒 数。当没有设置的时候它取的就是驱动默认的时间 |
Any positive integer |
Not set (null) |
safeRowBoundsEnabled |
允许在嵌套语句中使用分页(RowBounds) |
true、false |
False |
mapUnderscoreToCamelCase |
是否开启自动驼峰命名规则(camel case)映射, 即从经典数据库列名A_COLUMN到经典Java属性 名aColumn的类似映射 |
true、false |
false |
localCacheScope |
MyBatis利用本地缓存机制(Local Cache)防止 循环引用(circular references)和加速重复嵌套查询。 默认值为SESSION,这种情况下会缓存一个会话中 执行的所有査询。若设置值为STATEMENT,本地 会话仅用在语句执行上,对相同SqlSession的不同 调用将不会共享数据 |
SESSION、 STATEMENT |
SESSION |
jdbcTypeForNull |
当没有为参数提供特定的JDBC类型时,为空值 指定JDBC类型。某些驱动需要指定列的JDBC类 型,多数情况直接用一般类型即可,比如NULL、 VARCHAR. OTHER |
JdbcType枚举,最常见 的是NUL是VARCHAR和 OTHER |
OTHER |
设置参数 |
描 述 |
有效值 |
默认值 |
lazyLoadTriggerMethods |
指定对象的方法触发一次延迟加载 |
如果是一个方法列表, 我们则用逗号将它们 隔开 |
equals,clone,hashC ode,toString |
defaultScriptingLanguage |
指定动态SQL生成的默认语言 |
你可以配置类的别名 或者类的全限定名 |
org.apache.ibatis.se ripting.xmltags.XM LDynamicLanguag eDriver |
callSettersOnNulls |
指定当结果集中值为null的时候是否调用映射对 象的setter (map对象时为put)方法,这对于有 Map.keySet()依赖或null值初始化的时候是有用的。 注意基本类型(int、boolean等)是不能设置成null 的 |
true、 false |
false |
logPrefix |
指定MyBatis增加到日志名称的前缀 |
任何字符串 |
没有设置 |
loglmpl |
指定MyBatis所用日志的具体实现,未指定时将 自动査找 |
SLF4J、LOG4J、LOG4J2、 JDK_LOGGING、 COMMONS_LOGGING 、STDOUT_LOGGING> NO LOGGING |
没有设置 |
proxy Factory |
指定MyBatis创建具有延迟加载能力的对象所用 到的代理工具 |
CGLIB. JAVASSIST |
版本3.3.0 (含)以上 JMSSIST,否则 CGLIB |
配置不需要修改太多,一般来说我们只要修改少量的配置就可以了,未来我们还会接 触它,这里有点印象就可以了,我们后面会再详细讨论一些常用的功能。
我们来看一个完整的配置是怎么样的,如代码清单3-6所示。
在大部分时候我们都不需要去配置它,或者只需要配置少数几项即可。
三、别名
别名(typeAliases)是一个指代的名称。因为我们遇到的类全限定名过长,所以我们 希望用一个简短的名称去指代它,而这个名称可以在MyBatis上下文中使用。别名在 MyBatis里面分为系统定义别名和自定义别名两类。注意,在MyBatis中别名是不分大小写 的。一个typeAliases的实例是在解析配置文件时生成的,然后长期保存在Configuration对 象中,当我们使用它时,再把它拿出来,这样就没有必要运行的时候再次生成它的实例了。
1、系统定义别名
MyBatis系统定义了一些经常使用的类型的别名,例如,数值、字符串、日期和集合等,我们可以在MyBatis中直接使用它们,在使用时不要重复定义把它们给覆盖了。
让我们看看MyBatis已经定义好的别名(支持数组类型的只要加“[]”即可使用,比如Date数组别名可以用date[]代替),如表3-2所示,MyBatis已经在系统定义了 type Aliases。
表3-2 系统定义的typeAliases
别 名 |
映射的类型 |
支持数组 |
byte |
byte |
是 |
long |
long |
是 |
short |
short |
是 |
int |
int |
是 |
integer |
int |
是 |
double |
double |
是 |
float |
float |
是 |
boolean |
boolean |
是 |
string |
String |
否 |
别 名 |
映射的类型 |
支持数组 |
byte |
Byte |
是 |
long |
Long |
是 |
short |
Short |
是 |
int |
Integer |
是 |
integer |
Integer |
是 |
double |
Double |
是 |
float |
Float |
是 |
boolean |
Boolean |
是 |
date |
Date |
是 |
decimal |
BigDecimal |
是 |
bigdecimal |
BigDecimal |
是 |
object |
Object |
是 |
map |
Map |
否 |
hashmap | |
HashMap |
否 |
list |
List |
否 |
arraylist |
ArrayList |
否 |
collection |
Collection |
否 |
iterator |
Iterator |
否 |
ResultSet |
ResultSet |
否 |
我们通过MyBatis的源码org.apache.ibatis.type.TypeAliasRegistry可以看出其自定义注 册的信息,如代码清单3.7所示。
这些就是MyBatis系统定义的别名,我们无需重复注册它们。
2、自定义别名
系统所定义的别名往往是不够用的,因为不同的应用有着不同的需要,所以MyBatis 允许自定义别名。正如第2章的例子,我们可以用typeAliases配置别名,也可以用代码方 式注册别名,如代码清单3.8所示。
这样我们就可以在MyBatis的上下文中使用“role”来代替其全路径,减少配置的复 杂度。
如果POJO过多的时候,配置也是非常多的。比如我们可能面对的不单单是角色(role),还有用户(user)>单位(company)和部门(department)等信息'大的系统信息量巨大,所以这样的配置会大大的增加工作量。MyBatis也考虑到了这样的场景,因此允许我们通过自动扫描的形式自定义别名,如代码清单3・9所示。
我们需要自己定义别名,它是使用注解@Alias,这里我们定义角色类的别名为role, 实现的伪代码,如代码清单3.10所示。
当配合上面的配置,MyBatis就会自动扫描包,将扫描到的类装载到上下文中,以 便将来使用。这样就算是多个POJO也可以通过包扫描的方式装载到MyBatis的上下文 中。
当然配置了包扫描的路径,而没有注解©Alias的MyBatis也会装载,只是说它将把你 的类名的第一个字母变为小写,作为MyBatis的别名,要特别注意避免出现重名的场景, 建议使用部分包名加类名的限定。
四、typeHandler类型处理器
MyBatis在预处理语句(PreparedStatement)中设置一个参数时,或者从结果集 (ResultSet)中取出一个值时,都会用注册了的typeHandler进行处理。
由于数据库可能来自于不同的厂商,不同的厂商设置的参数可能有所不同,同时数据库也可以自定义数据类型,typeHandler允许根据项目的需要自定义设置Java传递到数据库 的参数中,或者从数据库读岀数据,我们也需要进行特殊的处理,这些都可以在自定义的 typeHandler中处理,尤其是在使用枚举的时候我们常常需要使用typeHandler进行转换。
typeHandler和别名一样,分为MyBatis系统定义和用户自定义两种。一般来说,使用 MyBatis系统定义就可以实现大部分的功能,如果使用用户自定义的typeHandler,我们在 处理的时候务必小心谨慎,以避免出现不必要的错误。
typeHandler 常用的配置为 Java 类型(javaType)> JDBC 类型(jdbcType)o typeHandler 的作用就是将参数从javaType转化为jdbcType,或者从数据库取出结果时把jdbcType转化 为 javaType。
1、系统定义的 typeHandler
MyBatis系统内部定义了一系列的typeHandler,如代码清单3-11所示。我们可以在源码查看它们,让我们看看org.apache.ibatis.type.TypeHandlerRegistry。
这便是系统为我们注册的typeHandlero目前MyBatis为我们注册了多个typeHander,让我们看看表3-3,从而了解typeHandler对应的Java类型和JDBC类型。
表3-3系统注册的typeHandler简介 |
||
类型处理器 |
Java类型 |
JDBC类型 |
BooleanTypeHandler |
java.lang.Boolean, boolean |
数据库兼容的BOOLEAN |
.ByteTypeHandler |
java.lang.Byte, byte |
数据库兼容的NUMERIC或BYTE |
ShortTypeHandler |
java.lang.Short, short |
数据库兼容的NUMERIC或SHORT INTEGER |
IntegerTypeHandler |
java.lang.Integer, int |
数据库兼容的NUMERIC或INTEGER |
LongTypeHandler |
java.Iang.Long, long |
数据库兼容的NUMERIC或LONG INTEGER |
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 |
ClobTypeHandler |
java. lang. String |
CLOB, LONGVARCHAR |
NStringTypeHandler |
java.lang.String |
NVARCHAR, NCHAR |
NClobTypeHandler |
java.lang.String |
NCLOB |
ByteArrayTypeHandler |
byte[] |
数据库兼容的字节流类型 |
BlobTypeHandler |
byte[] |
BLOB, LONGVARBINARY |
DateTypeHandler |
java.util.Date |
TIMESTAMP |
类型处理器 |
Java类型 |
JDBC类型 |
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类型,存 储枚举的索引(而不是名称) |
我们需要注意下面几点。
- 数值类型的精度,数据库int、double> decimal这些类型和java的精度、长度都是 不一样的。
- 时间精度,取数据到日用DateOnlyTypeHandler即可,用到精度为秒的用SqlTimestamp TypeHandler 等。
让我们选取一个MyBatis系统自定义的typeHandler,并了解它的具体内容。我们可以 看到 MyBatis 源码包 org.apache.ibatis.type 下面定义的 StringTypeHandler。StringTypeHandler 是一个最常用的typeHandler,负责处理String类型,如代码清单3.12所示。
说明一下上面的代码。
StringTypeHandler 继承了 BaseTypeHandlero而 BaseTypeHandler 实现了接口 typeHandler, 并且自己定义了4个抽象的方法。所以继承它的时候,正如本例一样需要实现其定义的4个 抽象方法,这些方法已经在StringTypeHandler中用@Oveiride注解注明了。
setParameter是PreparedStatement对象设置参数,它允许我们自己填写变换规则。
getResult 则分为 ResultSet 用列名(cohimnName)或者使用列下标(columnindex)来 获取结果数据。其中还包括了用CallableStatement (存储过程)获取结果及数据的方法。
2、自定义 typeHandler
一般而言,MyBatis系统定义的typeHandler已经能够应付大部分的场景了,但是我们 不能排除不够用的情况。首先需要明确两个问题:我们自定义的typeHandler需要处理什么 类型?现有的typeHandler适合我们使用吗?我们需要特殊的处理Java的那些类型 (JavaType)和对应处理数据库的那些类型(JdbcType),比如说字典项的枚举。
这里让我们重复覆盖一个字符串参数的typeHandler,我们首先配置XML文件,确定 我们需要处理什么类型的参数和结果,如代码清单3-13所示。
上面定义的数据库类型为VARCHAR型。当Java的参数为string型的时候,我们将使 用MyStringTypeHandler进行处理。但是只有这个配置MyBatis不会自动帮助你去使用这个 typeHandler去转化,你需要更多的配置。
对于 MyStringTypeHandler 的要求是必须实现接口: org.apache.ibatis.type. TypeHandler, 在 MyBatis 中,我们也可以继承 org.apache.ibatis.type.BaseTypeHandler 来实现,因为 BaseTypeHandler已经实现了 typeHandler接口。在自定义的typeHandler中我们看到了如何 继承BaseTypeHandler来实现。现在我们用typeHandler接口来实现,如代码清单3-14所示。
代码里涉及了使用预编译(PreparedStatement)设置参数,获取结果集的时候使用的方 法,并且给出日志。
自定义typeHandler里用注解配置JdbcType和JavaType。这两个注解是:
- @MappedTypes定义的是JavaType类型,可以指定哪些Java类型被拦截。
- @MappedJdbcTypes定义的是JdbcType类型,它需要满足枚举类org.apache. ibatis.type.JdbcType所列的枚举类型。
到了这里我们还不能测试,因为还需要去标识哪些参数或者结果类型去用typeHandler进行转换,在没有任何标识的情况下,MyBatis是不会启用你定义的typeHandler进行转换结果的, 所以还要给予对应的标识,比如配置jdbcType和javaType,或者直接用typeHandler属性指定,此我们需要修改映射器的XML配置。我们进行如下修改,如代码清单3.15所示。
我们这里引入了 resultMap,它提供了映射规则,这里给了 3种typeHandler的使用方法。
- 在配置文件里面配置,在结果集的roleName定义jdbcType和javaType。只有定义 的jdbcType、javaType和我们定义在配置里面的typeHandler是一致的,MyBatis才 能够知道用我们自定义的类型转化器进行转换。
- 映射集里面直接定义具体的typeHandler,这样就不需要再在配置里面定义了。
- 在参数中制定typeHandler,这样MyBatis就会用对应的typeHandler进行转换。这 样也不需要在配置里面定义了。
做了以上的修改,我们运行一下findRole方法看看结果。
从结果看,程序已经正确运行了,我们定义的typeHandler已经启动。如果在配置 typeHandler的时候也可以进行包配置,那么MyBatis就会扫描包里面的typeHandler,以减 少配置的工作,让我们看看如何配置,如代码清单3.16所示。
3、枚举类型 typeHandler
3.4.2节给出了自定义Java类型的typeHandler,但是在MyBatis中枚举类型的 typeHandler则有自己特殊的规则,MyBatis内部提供了两个转化枚举类型的typeHandler给 我们使用。
- org.apache.ibatis.type.EnumTypeHandler
- org.apache.ibatis.type.EnumOrdinalTypeHandler
其中,EnumTypeHandler是使用枚举字符串名称作为参数传递的,EnumOrdinalTypeHandler是使用整数下标作为参数传递的。如果枚举和数据库字典项保持一致,我们使用它们就可以了。然而这两个枚举类型应用却不是那么广泛,更多的时候我们希望使用自定义的typeHandler处理它们。所以在这里我们也会谈及自定义的typeHanlder实现枚举映射。
下面以性别为例,讲述如何实现枚举类。现在我们有一个性别枚举,它定义了字典: 男(male),女(female)o那么我们可以轻易得到一个枚举类,如代码清单3.17所示。
代码清单3-17: Sex.java性别枚举
package com.learn.chapters.enums; public enum Sex { MALE (1, ”男”),FEMALE (2, ”女”); private int id; private String name; private Sex (int id, String name) { this.id = id; this.name = name; } public int getld() ( return id; } public void setld(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) ( this.name = name; } public static Sex getSex (int id) { if (id == 1) ( return MALE; } else if (id == 2) ( return FEMALE; } return null; } }
(1)EnumOrdinalTypeHandler
在没有配置的时候,EnumOrdinalTypeHandler是MyBatis默认的枚举类型的处理器。 为了让EnumOrdinalTypeHandler能够处理它,我们在MyBatis做如下配置,如代码清单3-18 所示。
这样当MyBatis遇到这个枚举就可以识别这个枚举了,然后我们给出userMapper.xml, 请注意代码清单3-19中加粗的代码。
为了测试,我们需要一个接口,如代码清单3.20所示。
我们要确保引入这个映射器到MyBatis上下文中,请参考roleMapper.xml的引入方法, 然后就可以进行测试了,如代码清单3.21所示。
让我们看看结果。
运行成功了,我们看看数据库中的枚举插入结果,如图3.1所示。我们发现它插入的是枚举定义的下标,而取出也是根据下标进行转化的。
(2)EnumTypeHandler
EnumTypeHandler是使用枚举名称去处理Java枚举类型。EnumTypeHandler对应的是 一个字符串,让我们来看看它的用法。
首先定义一个字符串,VARCHAR型的字典项,例如将3.4.3.1节的性别(sex)修改为 VARCHAR型,然后修改映射XML文件。这时我们在映射文件里面做了全部的限定描述 (javaType、jdbcType、typeHandler全配置),这样就不需要在MyBatis配置文件里再进行配 置了,如代码清单3.22所示。
然后把POJO的属性sex从整数型修改为String型就可以了。EnumTypeHandler是通过Enum.name方法将其转化为字符串,通过Enum.valueOf方法将字符串转化为枚举的。
当我们做了这样的修改后,插入的结果如图3.2所示。
(3)自定义枚举类的typeHandler
在大部分的情况下我们都不想使用系统的枚举typeHandler而是釆用自定义。如果下标 和名称往往都不是我们想要的结果,那么我们就可以参考自定义的typeHandler来定义其映 射关系的规则。首先,我们增加配置,如代码清单3-23所示。
然后,给出SexEnumTypeHandler的定义,如代码清单3-24所示。
最后,把代码清单3-22修改为我们定义的SexEnumTypeHandler即可运行程序了,这 样自定义枚举就可以和数据库的字典项目对应起来了。