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文件中的usernamepassword两个属性使用了加密的字符串, 这个时候我们需要在生成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将按照下面的顺序来加载。

  1.   在properties元素体内指定的属性首先被读取。
  2.   根据properties元素中的resource属性读取类路径下属性文件,或者根据url属性指 定的路径读取属性文件,并覆盖已读取的同名属性。
  3.   读取作为方法参数传递的属性,并覆盖已读取的同名属性。

  因此,通过方法参数传递的属性具有最高优先级,resource/url属性中指定的配置文件 次之,最低优先级的是properties属性中指定的属性。因此,实际操作中我们需要注意以 3点。

  1. 不要使用混合的方式,这样会使得管理混乱。
  2. 首选的方式是使用properties文件。
  3. 如果我们需要对其进行加密或者其他加工以满足特殊的要求,不妨按示例的方法处 理。这样做的好处是使得配置都来自于同一个配置文件,就不容易产生没有必要的歧义, 也为日后统一管理提供了方便。

二、设置

  设置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

数据库兼容的NUMERICBYTE

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即可运行程序了,这 样自定义枚举就可以和数据库的字典项目对应起来了。

posted @ 2020-09-03 16:51  跃小云  阅读(181)  评论(0编辑  收藏  举报