基礎(chǔ)示例
一個(gè)Mybatis的項(xiàng)目基本配置信息是寫在一個(gè)xml文件中,指定數(shù)據(jù)庫(kù)類型、數(shù)據(jù)源、事務(wù)等相關(guān)信息,如下
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<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>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
程序執(zhí)行之初會(huì)解析配置文件,后續(xù)將根據(jù)配置的信息初始化數(shù)據(jù)連接池、Mapper等,下面根據(jù)源碼看看配置文件的解析。
下面代碼展示了根據(jù)配置文件讀取文件流,解析文件流并生成 configuration 并生成 SqlSessionFactory
// 配置文件的位置
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// SqlSessionFactoryBuilder 會(huì)根據(jù) 文件流信息解析成configuration
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
構(gòu)建 SqlSessionFactory
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
}
新建 XMLConfigBuilder,XMLConfigBuilder將負(fù)責(zé)將 xml 配置文件解析成 Configuration 實(shí)例,供其他實(shí)例使用。下面重點(diǎn)介紹Configuration 實(shí)例解析過(guò)程。
創(chuàng)建XMLConfigBuilder
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
調(diào)用構(gòu)造函數(shù)時(shí)會(huì)新建一個(gè) XPathParser,parser會(huì)解析配置文件的信息成 document;同時(shí)調(diào)用了父類的構(gòu)造函數(shù),傳入新建的一個(gè)Configuration,這個(gè)新建的 Configuration 就是 parse() 方法的返回值,后續(xù)對(duì)配置文件的解析也是將配置文件的數(shù)據(jù)放到此 configuration 中。
// XPathParser 的構(gòu)造函數(shù)
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(inputStream));
}
XMLConfigBuilder初始化完成,調(diào)用 parse() 方法解析配置文件的 configuration 節(jié)點(diǎn),這是整個(gè)配置文件的解析,也就是官方XML配置說(shuō)明的實(shí)現(xiàn)部分。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 拿到configuration節(jié)點(diǎn),調(diào)用parseConfiguration方法,將解析結(jié)果放在configuration變量中
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(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);
}
}
propertiesElement(root.evalNode("properties"));
properties節(jié)點(diǎn)示例,一般數(shù)據(jù)庫(kù)的連接配置會(huì)單獨(dú)拿出來(lái),放在外部配置中,resource 或者 url屬性指定
<properties resource="config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
// 讀取 properties 節(jié)點(diǎn)的子節(jié)點(diǎn),加載為 Properties
Properties defaults = context.getChildrenAsProperties();
// resource 和 url都是外部配置資源路徑
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) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
// 解析完畢,設(shè)置值
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
- 屬性放在configuration的variables中
- XPathParser 的 variables中,解析其他數(shù)據(jù)需要使用
settingsAsProperties(root.evalNode("settings"));
settings 節(jié)點(diǎn),這是 MyBatis 中極為重要的調(diào)整設(shè)置,它們會(huì)改變 MyBatis 的運(yùn)行時(shí)行為。實(shí)例 如下
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" 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="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
settingsAsProperties 方法代碼
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
Properties props = context.getChildrenAsProperties();
// 檢查屬性是否在Configuration類中存在
// Check that all settings are known to the configuration class
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
for (Object key : props.keySet()) {
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
return props;
}
獲取屬性值,并返回,返回的數(shù)據(jù)在 settingsElement(settings); 方法中設(shè)置到configuration中
loadCustomVfs(settings);
指定VFS的實(shí)現(xiàn),自定義VFS的實(shí)現(xiàn)的類全限定名,以逗號(hào)分隔。(不知道是啥)
private void loadCustomVfs(Properties props) throws ClassNotFoundException {
String value = props.getProperty("vfsImpl");
if (value != null) {
String[] clazzes = value.split(",");
for (String clazz : clazzes) {
if (!clazz.isEmpty()) {
@SuppressWarnings("unchecked")
Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
configuration.setVfsImpl(vfsImpl);
}
}
}
}
需要是 VFS 的子類,VFS類的注釋
提供一個(gè)非常簡(jiǎn)單的API,用于訪問(wèn)應(yīng)用程序服務(wù)器中的資源。
應(yīng)該是為了某種擴(kuò)展吧。
typeAliasesElement(root.evalNode("typeAliases"));
類型別名是為 Java 類型設(shè)置一個(gè)短的名字。它只和 XML 配置有關(guān),存在的意義僅在于用來(lái)減少類完全限定名的冗余。
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<!--也可以指定一個(gè)包名-->
<package name="domain.blog"/>
</typeAliases>
typeAliasesElement 代碼如下
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
// 這個(gè) typeAliasRegistry 就是 configuration 的 typeAliasRegistry屬性,在 BaseBuilder的構(gòu)造函數(shù)中賦值
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
幾個(gè)相關(guān)方法
public void registerAliases(String packageName){
registerAliases(packageName, Object.class);
}
public void registerAliases(String packageName, Class<?> superType){
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
for(Class<?> type : typeSet){
// Ignore inner classes and interfaces (including package-info.java)
// Skip also inner classes. See issue #6
if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
registerAlias(type);
}
}
}
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");
}
// 默認(rèn)全小寫
// issue #748
String key = alias.toLowerCase(Locale.ENGLISH);
if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
}
TYPE_ALIASES.put(key, value);
}
- 設(shè)置包名的將會(huì)掃描包下的類,設(shè)置別名
- 沒(méi)設(shè)置 alias 會(huì)以類名為別名
- 別名全小寫
- TypeAliasRegistry 類的構(gòu)造函數(shù)已經(jīng)設(shè)置了一些基礎(chǔ)數(shù)據(jù)類型以及集合數(shù)組類型等
將短名字添加到configuration的 typeAliasRegistry 屬性中
pluginElement(root.evalNode("plugins"));
MyBatis 允許你在已映射語(yǔ)句執(zhí)行過(guò)程中的某一點(diǎn)進(jìn)行攔截調(diào)用。
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
// 實(shí)例化
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
// 設(shè)置 properties
interceptorInstance.setProperties(properties);
// 添加到 interceptor 鏈中,@see InterceptorChain
configuration.addInterceptor(interceptorInstance);
}
}
}
objectFactoryElement(root.evalNode("objectFactory"));
MyBatis 每次創(chuàng)建結(jié)果對(duì)象的新實(shí)例時(shí),它都會(huì)使用一個(gè)對(duì)象工廠(ObjectFactory)實(shí)例來(lái)完成。 默認(rèn)的對(duì)象工廠需要做的僅僅是實(shí)例化目標(biāo)類,要么通過(guò)默認(rèn)構(gòu)造方法,要么在參數(shù)映射存在的時(shí)候通過(guò)參數(shù)構(gòu)造方法來(lái)實(shí)例化。 如果想覆蓋對(duì)象工廠的默認(rèn)行為,則可以通過(guò)創(chuàng)建自己的對(duì)象工廠來(lái)實(shí)現(xiàn)。 默認(rèn)聲明的是ObjectFactory objectFactory = new DefaultObjectFactory(); 。
private void objectFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties properties = context.getChildrenAsProperties();
ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();
factory.setProperties(properties);
configuration.setObjectFactory(factory);
}
}
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
將 settings 節(jié)點(diǎn)配置的參數(shù)設(shè)置到 configuration 中
environmentsElement(root.evalNode("environments"));
MyBatis 可以配置成適應(yīng)多種環(huán)境,這種機(jī)制有助于將 SQL 映射應(yīng)用于多種數(shù)據(jù)庫(kù)之中, 現(xiàn)實(shí)情況下有多種理由需要這么做。例如,開(kāi)發(fā)、測(cè)試和生產(chǎn)環(huán)境需要有不同的配置;或者共享相同 Schema 的多個(gè)生產(chǎn)數(shù)據(jù)庫(kù), 想使用相同的 SQL 映射。許多類似的用例。
每個(gè)數(shù)據(jù)庫(kù)對(duì)應(yīng)一個(gè) SqlSessionFactory 實(shí)例
<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>
注意這里的關(guān)鍵點(diǎn):
- 默認(rèn)的環(huán)境 ID(比如:default="development")。在構(gòu)建session工廠的時(shí)候指定,如未指定則為默認(rèn)值:
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment); - 每個(gè) environment 元素定義的環(huán)境 ID(比如:id="development")。
- 事務(wù)管理器的配置(比如:type="JDBC")。
- 數(shù)據(jù)源的配置(比如:type="POOLED")。
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
// 事務(wù)管理器
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 數(shù)據(jù)源
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
// builder 模式構(gòu)建 environment
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
內(nèi)置的數(shù)據(jù)源和事務(wù)管理器相關(guān)的類,在 Configuration 構(gòu)造函數(shù)中,添加到 typeAliasRegistry 中
- 事務(wù)管理器
- 數(shù)據(jù)源
- 根據(jù)事務(wù)管理器、數(shù)據(jù)源、id生成Environment實(shí)例,賦值到configuration的environment
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
MyBatis 可以根據(jù)不同的數(shù)據(jù)庫(kù)廠商執(zhí)行不同的語(yǔ)句,這種多廠商的支持是基于映射語(yǔ)句中的 databaseId 屬性。 MyBatis 會(huì)加載不帶 databaseId 屬性和帶有匹配當(dāng)前數(shù)據(jù)庫(kù) databaseId 屬性的所有語(yǔ)句。 如果同時(shí)找到帶有 databaseId 和不帶 databaseId 的相同語(yǔ)句,則后者會(huì)被舍棄。 為支持多廠商特性只要像下面這樣在 mybatis-config.xml 文件中加入 databaseIdProvider 即可:
<databaseIdProvider type="DB_VENDOR" />
typeHandlerElement(root.evalNode("typeHandlers"));
無(wú)論是 MyBatis 在預(yù)處理語(yǔ)句(PreparedStatement)中設(shè)置一個(gè)參數(shù)時(shí),還是從結(jié)果集中取出一個(gè)值時(shí), 都會(huì)用類型處理器將獲取的值以合適的方式轉(zhuǎn)換成 Java 類型。 你可以重寫類型處理器或創(chuàng)建你自己的類型處理器來(lái)處理不支持的或非標(biāo)準(zhǔn)的類型。 具體做法為:實(shí)現(xiàn) org.apache.ibatis.type.TypeHandler 接口, 或繼承一個(gè)很便利的類 org.apache.ibatis.type.BaseTypeHandler, 然后可以選擇性地將它映射到一個(gè) JDBC 類型。
配置文件聲明自定義handler
<typeHandlers>
<typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>
private void typeHandlerElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeHandlerPackage = child.getStringAttribute("name");
typeHandlerRegistry.register(typeHandlerPackage);
} else {
String javaTypeName = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
String handlerTypeName = child.getStringAttribute("handler");
Class<?> javaTypeClass = resolveClass(javaTypeName);
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
// 也就是說(shuō)配置不指定javaType只指定jdbcType是無(wú)效的
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
// register 最后都會(huì)通過(guò)調(diào)用TypeHandlerRegistry 的 下面方法執(zhí)行 注冊(cè)
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
if (javaType != null) {
Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
if (map == null || map == NULL_TYPE_HANDLER_MAP) {
map = new HashMap<JdbcType, TypeHandler<?>>();
TYPE_HANDLER_MAP.put(javaType, map);
}
map.put(jdbcType, handler);
}
ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
}
通過(guò)類型處理器的泛型,MyBatis 可以得知該類型處理器處理的 Java 類型,不過(guò)這種行為可以通過(guò)兩種方法改變:
- 在類型處理器的配置元素(typeHandler element)上增加一個(gè)
javaType屬性(比如:javaType="String"); - 在類型處理器的類上(TypeHandler class)增加一個(gè)
@MappedTypes注解來(lái)指定與其關(guān)聯(lián)的 Java 類型列表。 如果在javaType屬性中也同時(shí)指定,則注解方式將被忽略。
可以通過(guò)兩種方式來(lái)指定被關(guān)聯(lián)的 JDBC 類型:
- 在類型處理器的配置元素上增加一個(gè)
jdbcType屬性(比如:jdbcType="VARCHAR"); - 在類型處理器的類上(TypeHandler class)增加一個(gè)
@MappedJdbcTypes注解來(lái)指定與其關(guān)聯(lián)的 JDBC 類型列表。 如果在jdbcType屬性中也同時(shí)指定,則注解方式將被忽略。
handlers 添加到 configuration 的 typeHandlerRegistry 屬性中
mapperElement(root.evalNode("mappers"));
既然 MyBatis 的行為已經(jīng)由上述元素配置完了,我們現(xiàn)在就要定義 SQL 映射語(yǔ)句了。但是首先我們需要告訴 MyBatis 到哪里去找到這些語(yǔ)句。 Java 在自動(dòng)查找這方面沒(méi)有提供一個(gè)很好的方法,所以最佳的方式是告訴 MyBatis 到哪里去找映射文件。你可以使用相對(duì)于類路徑的資源引用, 或完全限定資源定位符(包括 file:/// 的 URL),或類名和包名等。例如:
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<!-- 或者
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
類全名
<mapper class="org.mybatis.builder.AuthorMapper"/>
包名
<package name="org.mybatis.builder"/>
-->
</mappers>
關(guān)于 mapper 的解析是一個(gè)比較大的工作,下一節(jié)講解。
總結(jié)
configuration解析還是比較簡(jiǎn)單的,主要將配置文件的屬性賦值到configuration中供后續(xù)使用。