版本
jdk1.8.0_171
Mybatis-3.5.4
IDE:Eclipse
一、使用一个简单的示例来引入Mybatis容器的加载
主类:
/** * Copyright ${license.git.copyrightYears} the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wjy; import java.io.IOException; import java.io.Reader; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import com.wjy.pojo.User; public class MybatisHelloWorld { public static void main(String[] args) { String resource = "./Configuration.xml"; Reader reader; try { reader = Resources.getResourceAsReader(resource);//最终调用 应用类加载器AppClassLoader 加载配置文件 SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader); SqlSession session = sqlMapper.openSession(); try { User user = (User) session.selectOne("com.wjy.mapper.UserMapper.getUser", 1); System.out.println(user.getLfPartyId() + "," + user.getPartyName()); } finally { session.close(); } } catch (IOException e) { e.printStackTrace(); } } }
POJO类:
package com.wjy.pojo; public class User { private Integer lfPartyId; private String partyName; public Integer getLfPartyId() { return lfPartyId; } public void setLfPartyId(Integer lfPartyId) { this.lfPartyId = lfPartyId; } public String getPartyName() { return partyName; } public void setPartyName(String partyName) { this.partyName = partyName; } }
Mapper类:
package com.wjy.mapper; import com.wjy.pojo.User; public interface UserMapper { public User getUser(int lfPartyId); }
Mybatis配置文件-Configuration.xml:
<?xml version="1.0" encoding="UTF-8"?> <!-- Copyright ${license.git.copyrightYears} the original author or authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <!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="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <mappers> <mapper resource="UserMapper.xml"/> </mappers> </configuration>
Mapper配置文件-UserMapper.xml:
<?xml version="1.0" encoding="UTF-8"?> <!-- Copyright ${license.git.copyrightYears} the original author or authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.wjy.mapper.UserMapper"> <select id="getUser" parameterType="int" resultType="com.wjy.pojo.User"> select lfPartyId,partyName from LfParty where lfPartyId = #{id} </select> </mapper>
报错1:not class found:com.mysql.jdbc.Driver
原因:原先/mybatis/pom.xml中对mysql-connector-java依赖范围是test,只在测试编译、测试运行时起作用;在编译和运行时都不需要,因此不会被打包
解决:将
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
<scope>test</scope>
</dependency>
改为
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
<scope>runtime</scope>
</dependency>
报错2:The server time zone value '�й���ʱ��' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the 'serverTimezone' configuration property) to use a more specifc time zone value if you want to utilize time zone support.
原因是使用了Mysql Connector/J 6.x以上的版本,然后就报了时区的错误
解决:
在配置url的时候不能简单写成 :
jdbc:mysql://127.0.0.1:3306/test?useUnicode=true
而是要写成 (注意多个参数使用&而不是简单的&):
jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&serverTimezone=UTC
从测试代码可以看出,SqlSessionFactoryBuilder负责解析主配置文件,返回SqlSessionFactory,SqlSessionFactory生产SqlSession,SqlSessionFactory执行SQL操作。
所以SqlSessionFactoryBuilder是加载Mybatis的入口类.
二、执行流程概览
先来一张总体图,展示mybatis总体执行过程,然后逐步展开各个部分。
三、SqlSessionFactoryBuilder
SqlSessionFactoryBuilder负责mybatis配置文件的加载,解析,内部构建等。
容器的配置文件加载和初始化流程如下:
(1)SqlSessionFactoryBuilder将Configuration.xml配置文件交由XMLConfigBuilder来解析,实际使用的javax.xml包和com.sun.org.apache.xerces包下的类来解析;
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
(2)解析出配置类Configuration
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; //mybatis配置文件解析的主流程 parseConfiguration(parser.evalNode("/configuration"));//返回根节点 return configuration; } //根据mybatis的主要配置进行解析 private void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); loadCustomLogImpl(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); } }
(3)将Configuration.xml配置文件解析成org.apache.ibatis.session.Configuration类,将Configuration传给DefaultSqlSessionFactory即可生成SqlSessionFactory,SqlSessionFactory创建SqlSession进行相应的JDBC操作。
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); } public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); final Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
四、configuration配置
1、Configuration配置类
mybatis中所有环境配置、resultMap集合、sql语句集合、插件列表、缓存、加载的xml列表、类型别名、类型处理器等全部都维护在Configuration中。Configuration中包含了一个内部静态类StrictMap,它继承于HashMap,对HashMap的装饰在于增加了put时防重复的处理,get时取不到值时候的异常处理,这样核心应用层就不需要额外关心各种对象异常处理,简化应用层逻辑。
/** * Copyright ${license.git.copyrightYears} the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ibatis.session; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.function.BiFunction; import org.apache.ibatis.binding.MapperRegistry; import org.apache.ibatis.builder.CacheRefResolver; import org.apache.ibatis.builder.IncompleteElementException; import org.apache.ibatis.builder.ResultMapResolver; import org.apache.ibatis.builder.annotation.MethodResolver; import org.apache.ibatis.builder.xml.XMLStatementBuilder; import org.apache.ibatis.cache.Cache; import org.apache.ibatis.cache.decorators.FifoCache; import org.apache.ibatis.cache.decorators.LruCache; import org.apache.ibatis.cache.decorators.SoftCache; import org.apache.ibatis.cache.decorators.WeakCache; import org.apache.ibatis.cache.impl.PerpetualCache; import org.apache.ibatis.datasource.jndi.JndiDataSourceFactory; import org.apache.ibatis.datasource.pooled.PooledDataSourceFactory; import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory; import org.apache.ibatis.executor.BatchExecutor; import org.apache.ibatis.executor.CachingExecutor; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.executor.ReuseExecutor; import org.apache.ibatis.executor.SimpleExecutor; import org.apache.ibatis.executor.keygen.KeyGenerator; import org.apache.ibatis.executor.loader.ProxyFactory; import org.apache.ibatis.executor.loader.cglib.CglibProxyFactory; import org.apache.ibatis.executor.loader.javassist.JavassistProxyFactory; import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apache.ibatis.executor.resultset.DefaultResultSetHandler; import org.apache.ibatis.executor.resultset.ResultSetHandler; import org.apache.ibatis.executor.statement.RoutingStatementHandler; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.io.VFS; import org.apache.ibatis.logging.Log; import org.apache.ibatis.logging.LogFactory; import org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl; import org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl; import org.apache.ibatis.logging.log4j.Log4jImpl; import org.apache.ibatis.logging.log4j2.Log4j2Impl; import org.apache.ibatis.logging.nologging.NoLoggingImpl; import org.apache.ibatis.logging.slf4j.Slf4jImpl; import org.apache.ibatis.logging.stdout.StdOutImpl; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.Environment; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ParameterMap; import org.apache.ibatis.mapping.ResultMap; import org.apache.ibatis.mapping.ResultSetType; import org.apache.ibatis.mapping.VendorDatabaseIdProvider; import org.apache.ibatis.parsing.XNode; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.InterceptorChain; import org.apache.ibatis.reflection.DefaultReflectorFactory; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.ReflectorFactory; import org.apache.ibatis.reflection.factory.DefaultObjectFactory; import org.apache.ibatis.reflection.factory.ObjectFactory; import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory; import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory; import org.apache.ibatis.scripting.LanguageDriver; import org.apache.ibatis.scripting.LanguageDriverRegistry; import org.apache.ibatis.scripting.defaults.RawLanguageDriver; import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver; import org.apache.ibatis.transaction.Transaction; import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; import org.apache.ibatis.transaction.managed.ManagedTransactionFactory; import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.TypeAliasRegistry; import org.apache.ibatis.type.TypeHandler; import org.apache.ibatis.type.TypeHandlerRegistry; /** * @author Clinton Begin */ public class Configuration { protected Environment environment; // 允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为false。默认为false protected boolean safeRowBoundsEnabled; //允许在嵌套语句中使用分页(ResultHandler)。如果允许使用则设置为false。 protected boolean safeResultHandlerEnabled = true; //是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。默认false protected boolean mapUnderscoreToCamelCase; //当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载。默认值false (true in ≤3.4.1) protected boolean aggressiveLazyLoading; //是否允许单一语句返回多结果集(需要兼容驱动)。 protected boolean multipleResultSetsEnabled = true; //允许 JDBC 支持自动生成主键,需要驱动兼容。这就是insert时获取mysql自增主键/oracle sequence的开关。注:一般来说,这是希望的结果,应该默认值为true比较合适。 protected boolean useGeneratedKeys; //使用列标签代替列名,一般来说,这是希望的结果 protected boolean useColumnLabel = true; //是否启用缓存 protected boolean cacheEnabled = true; //指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的 protected boolean callSettersOnNulls; //允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的工程必须采用Java 8编译,并且加上-parameters选项。(从3.4.1开始) protected boolean useActualParamName = true; //当返回行的所有列都是空时,MyBatis默认返回null。 当开启这个设置时,MyBatis会返回一个空实例。 //请注意,它也适用于嵌套的结果集 (i.e. collectioin and association)。(从3.4.2开始) 注:这里应该拆分为两个参数比较合适, 一个用于结果集,一个用于单记录。通常来说,我们会希望结果集不是null,单记录仍然是null protected boolean returnInstanceForEmptyRow; //指定 MyBatis 增加到日志名称的前缀。 protected String logPrefix; //指定 MyBatis 所用日志的具体实现,未指定时将自动查找。一般建议指定为slf4j或log4j protected Class<? extends Log> logImpl; //指定VFS的实现, VFS是mybatis提供的用于访问AS内资源的一个简便接口 protected Class<? extends VFS> vfsImpl; // MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。 protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION; //当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 protected JdbcType jdbcTypeForNull = JdbcType.OTHER; //指定对象的哪个方法触发一次延迟加载。 protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString")); //设置超时时间,它决定驱动等待数据库响应的秒数。默认不超时 protected Integer defaultStatementTimeout; //为驱动的结果集设置默认获取数量。 protected Integer defaultFetchSize; protected ResultSetType defaultResultSetType; //SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。 protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE; //指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)。 protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL; //指定发现自动映射目标未知列(或者未知属性类型)的行为。这个值应该设置为WARNING比较合适 protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE; //settings下的properties属性 protected Properties variables = new Properties(); //默认的反射器工厂,用于操作属性、构造器方便 protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory(); //对象工厂, 所有的类resultMap类都需要依赖于对象工厂来实例化 protected ObjectFactory objectFactory = new DefaultObjectFactory(); //对象包装器工厂,主要用来在创建非原生对象,比如增加了某些监控或者特殊属性的代理类 protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory(); //延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。 protected boolean lazyLoadingEnabled = false; //指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。MyBatis 3.3+使用JAVASSIST protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL //MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 protected String databaseId; /** * Configuration factory class. * Used to create Configuration for loading deserialized unread properties. * * @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300 (google code)</a> * 指定一个提供Configuration实例的类. 这个被返回的Configuration实例是用来加载被反序列化对象的懒加载属性值. 这个类必须包含一个签名方法static Configuration getConfiguration(). (从 3.2.3 版本开始) */ protected Class<?> configurationFactory; protected final MapperRegistry mapperRegistry = new MapperRegistry(this); // mybatis插件列表 protected final InterceptorChain interceptorChain = new InterceptorChain(); protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this); //类型注册器, 用于在执行sql语句的出入参映射以及mybatis-config文件里的各种配置比如<transactionManager type="JDBC"/><dataSource type="POOLED">时使用简写, 后面会详细解释 protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry(); protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry(); protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection") .conflictMessageProducer((savedValue, targetValue) -> ". please check " + savedValue.getResource() + " and " + targetValue.getResource()); protected final Map<String, Cache> caches = new StrictMap<>("Caches collection"); protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection"); protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection"); protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection"); protected final Set<String> loadedResources = new HashSet<>(); protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers"); protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>(); protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>(); protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>(); protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>(); /* * A map holds cache-ref relationship. The key is the namespace that * references a cache bound to another namespace and the value is the * namespace which the actual cache is bound to. */ protected final Map<String, String> cacheRefMap = new HashMap<>(); public Configuration(Environment environment) { this(); this.environment = environment; } public Configuration() { // 内置别名注册 typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class); typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class); typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class); typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class); typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class); typeAliasRegistry.registerAlias("FIFO", FifoCache.class); typeAliasRegistry.registerAlias("LRU", LruCache.class); typeAliasRegistry.registerAlias("SOFT", SoftCache.class); typeAliasRegistry.registerAlias("WEAK", WeakCache.class); typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class); typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class); typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class); typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class); typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class); typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class); typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class); typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class); typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class); typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class); typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class); typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class); languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class); languageRegistry.register(RawLanguageDriver.class); } public String getLogPrefix() { return logPrefix; } public void setLogPrefix(String logPrefix) { this.logPrefix = logPrefix; } public Class<? extends Log> getLogImpl() { return logImpl; } public void setLogImpl(Class<? extends Log> logImpl) { if (logImpl != null) { this.logImpl = logImpl; LogFactory.useCustomLogging(this.logImpl); } } public Class<? extends VFS> getVfsImpl() { return this.vfsImpl; } public void setVfsImpl(Class<? extends VFS> vfsImpl) { if (vfsImpl != null) { this.vfsImpl = vfsImpl; VFS.addImplClass(this.vfsImpl); } } public boolean isCallSettersOnNulls() { return callSettersOnNulls; } public void setCallSettersOnNulls(boolean callSettersOnNulls) { this.callSettersOnNulls = callSettersOnNulls; } public boolean isUseActualParamName() { return useActualParamName; } public void setUseActualParamName(boolean useActualParamName) { this.useActualParamName = useActualParamName; } public boolean isReturnInstanceForEmptyRow() { return returnInstanceForEmptyRow; } public void setReturnInstanceForEmptyRow(boolean returnEmptyInstance) { this.returnInstanceForEmptyRow = returnEmptyInstance; } public String getDatabaseId() { return databaseId; } public void setDatabaseId(String databaseId) { this.databaseId = databaseId; } public Class<?> getConfigurationFactory() { return configurationFactory; } public void setConfigurationFactory(Class<?> configurationFactory) { this.configurationFactory = configurationFactory; } public boolean isSafeResultHandlerEnabled() { return safeResultHandlerEnabled; } public void setSafeResultHandlerEnabled(boolean safeResultHandlerEnabled) { this.safeResultHandlerEnabled = safeResultHandlerEnabled; } public boolean isSafeRowBoundsEnabled() { return safeRowBoundsEnabled; } public void setSafeRowBoundsEnabled(boolean safeRowBoundsEnabled) { this.safeRowBoundsEnabled = safeRowBoundsEnabled; } public boolean isMapUnderscoreToCamelCase() { return mapUnderscoreToCamelCase; } public void setMapUnderscoreToCamelCase(boolean mapUnderscoreToCamelCase) { this.mapUnderscoreToCamelCase = mapUnderscoreToCamelCase; } public void addLoadedResource(String resource) { loadedResources.add(resource); } public boolean isResourceLoaded(String resource) { return loadedResources.contains(resource); } public Environment getEnvironment() { return environment; } public void setEnvironment(Environment environment) { this.environment = environment; } public AutoMappingBehavior getAutoMappingBehavior() { return autoMappingBehavior; } public void setAutoMappingBehavior(AutoMappingBehavior autoMappingBehavior) { this.autoMappingBehavior = autoMappingBehavior; } /** * @since 3.4.0 */ public AutoMappingUnknownColumnBehavior getAutoMappingUnknownColumnBehavior() { return autoMappingUnknownColumnBehavior; } /** * @since 3.4.0 */ public void setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior) { this.autoMappingUnknownColumnBehavior = autoMappingUnknownColumnBehavior; } public boolean isLazyLoadingEnabled() { return lazyLoadingEnabled; } public void setLazyLoadingEnabled(boolean lazyLoadingEnabled) { this.lazyLoadingEnabled = lazyLoadingEnabled; } public ProxyFactory getProxyFactory() { return proxyFactory; } public void setProxyFactory(ProxyFactory proxyFactory) { if (proxyFactory == null) { proxyFactory = new JavassistProxyFactory(); } this.proxyFactory = proxyFactory; } public boolean isAggressiveLazyLoading() { return aggressiveLazyLoading; } public void setAggressiveLazyLoading(boolean aggressiveLazyLoading) { this.aggressiveLazyLoading = aggressiveLazyLoading; } public boolean isMultipleResultSetsEnabled() { return multipleResultSetsEnabled; } public void setMultipleResultSetsEnabled(boolean multipleResultSetsEnabled) { this.multipleResultSetsEnabled = multipleResultSetsEnabled; } public Set<String> getLazyLoadTriggerMethods() { return lazyLoadTriggerMethods; } public void setLazyLoadTriggerMethods(Set<String> lazyLoadTriggerMethods) { this.lazyLoadTriggerMethods = lazyLoadTriggerMethods; } public boolean isUseGeneratedKeys() { return useGeneratedKeys; } public void setUseGeneratedKeys(boolean useGeneratedKeys) { this.useGeneratedKeys = useGeneratedKeys; } public ExecutorType getDefaultExecutorType() { return defaultExecutorType; } public void setDefaultExecutorType(ExecutorType defaultExecutorType) { this.defaultExecutorType = defaultExecutorType; } public boolean isCacheEnabled() { return cacheEnabled; } public void setCacheEnabled(boolean cacheEnabled) { this.cacheEnabled = cacheEnabled; } public Integer getDefaultStatementTimeout() { return defaultStatementTimeout; } public void setDefaultStatementTimeout(Integer defaultStatementTimeout) { this.defaultStatementTimeout = defaultStatementTimeout; } /** * @since 3.3.0 */ public Integer getDefaultFetchSize() { return defaultFetchSize; } /** * @since 3.3.0 */ public void setDefaultFetchSize(Integer defaultFetchSize) { this.defaultFetchSize = defaultFetchSize; } /** * @since 3.5.2 */ public ResultSetType getDefaultResultSetType() { return defaultResultSetType; } /** * @since 3.5.2 */ public void setDefaultResultSetType(ResultSetType defaultResultSetType) { this.defaultResultSetType = defaultResultSetType; } public boolean isUseColumnLabel() { return useColumnLabel; } public void setUseColumnLabel(boolean useColumnLabel) { this.useColumnLabel = useColumnLabel; } public LocalCacheScope getLocalCacheScope() { return localCacheScope; } public void setLocalCacheScope(LocalCacheScope localCacheScope) { this.localCacheScope = localCacheScope; } public JdbcType getJdbcTypeForNull() { return jdbcTypeForNull; } public void setJdbcTypeForNull(JdbcType jdbcTypeForNull) { this.jdbcTypeForNull = jdbcTypeForNull; } public Properties getVariables() { return variables; } public void setVariables(Properties variables) { this.variables = variables; } public TypeHandlerRegistry getTypeHandlerRegistry() { return typeHandlerRegistry; } /** * Set a default {@link TypeHandler} class for {@link Enum}. * A default {@link TypeHandler} is {@link org.apache.ibatis.type.EnumTypeHandler}. * @param typeHandler a type handler class for {@link Enum} * @since 3.4.5 */ public void setDefaultEnumTypeHandler(Class<? extends TypeHandler> typeHandler) { if (typeHandler != null) { getTypeHandlerRegistry().setDefaultEnumTypeHandler(typeHandler); } } public TypeAliasRegistry getTypeAliasRegistry() { return typeAliasRegistry; } /** * @since 3.2.2 */ public MapperRegistry getMapperRegistry() { return mapperRegistry; } public ReflectorFactory getReflectorFactory() { return reflectorFactory; } public void setReflectorFactory(ReflectorFactory reflectorFactory) { this.reflectorFactory = reflectorFactory; } public ObjectFactory getObjectFactory() { return objectFactory; } public void setObjectFactory(ObjectFactory objectFactory) { this.objectFactory = objectFactory; } public ObjectWrapperFactory getObjectWrapperFactory() { return objectWrapperFactory; } public void setObjectWrapperFactory(ObjectWrapperFactory objectWrapperFactory) { this.objectWrapperFactory = objectWrapperFactory; } /** * @since 3.2.2 */ public List<Interceptor> getInterceptors() { return interceptorChain.getInterceptors(); } public LanguageDriverRegistry getLanguageRegistry() { return languageRegistry; } public void setDefaultScriptingLanguage(Class<? extends LanguageDriver> driver) { if (driver == null) { driver = XMLLanguageDriver.class; } getLanguageRegistry().setDefaultDriverClass(driver); } public LanguageDriver getDefaultScriptingLanguageInstance() { return languageRegistry.getDefaultDriver(); } /** * @since 3.5.1 */ public LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass) { if (langClass == null) { return languageRegistry.getDefaultDriver(); } languageRegistry.register(langClass); return languageRegistry.getDriver(langClass); } /** * @deprecated Use {@link #getDefaultScriptingLanguageInstance()} */ @Deprecated public LanguageDriver getDefaultScriptingLanuageInstance() { return getDefaultScriptingLanguageInstance(); } public MetaObject newMetaObject(Object object) { return MetaObject.forObject(object, objectFactory, objectWrapperFactory, reflectorFactory); } public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; } public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) { ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); return resultSetHandler; } public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; } public Executor newExecutor(Transaction transaction) { return newExecutor(transaction, defaultExecutorType); } public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } if (cacheEnabled) { executor = new CachingExecutor(executor); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; } public void addKeyGenerator(String id, KeyGenerator keyGenerator) { keyGenerators.put(id, keyGenerator); } public Collection<String> getKeyGeneratorNames() { return keyGenerators.keySet(); } public Collection<KeyGenerator> getKeyGenerators() { return keyGenerators.values(); } public KeyGenerator getKeyGenerator(String id) { return keyGenerators.get(id); } public boolean hasKeyGenerator(String id) { return keyGenerators.containsKey(id); } public void addCache(Cache cache) { caches.put(cache.getId(), cache); } public Collection<String> getCacheNames() { return caches.keySet(); } public Collection<Cache> getCaches() { return caches.values(); } public Cache getCache(String id) { return caches.get(id); } public boolean hasCache(String id) { return caches.containsKey(id); } public void addResultMap(ResultMap rm) { resultMaps.put(rm.getId(), rm); checkLocallyForDiscriminatedNestedResultMaps(rm); checkGloballyForDiscriminatedNestedResultMaps(rm); } public Collection<String> getResultMapNames() { return resultMaps.keySet(); } public Collection<ResultMap> getResultMaps() { return resultMaps.values(); } public ResultMap getResultMap(String id) { return resultMaps.get(id); } public boolean hasResultMap(String id) { return resultMaps.containsKey(id); } public void addParameterMap(ParameterMap pm) { parameterMaps.put(pm.getId(), pm); } public Collection<String> getParameterMapNames() { return parameterMaps.keySet(); } public Collection<ParameterMap> getParameterMaps() { return parameterMaps.values(); } public ParameterMap getParameterMap(String id) { return parameterMaps.get(id); } public boolean hasParameterMap(String id) { return parameterMaps.containsKey(id); } public void addMappedStatement(MappedStatement ms) { mappedStatements.put(ms.getId(), ms); } public Collection<String> getMappedStatementNames() { buildAllStatements(); return mappedStatements.keySet(); } public Collection<MappedStatement> getMappedStatements() { buildAllStatements(); return mappedStatements.values(); } public Collection<XMLStatementBuilder> getIncompleteStatements() { return incompleteStatements; } public void addIncompleteStatement(XMLStatementBuilder incompleteStatement) { incompleteStatements.add(incompleteStatement); } public Collection<CacheRefResolver> getIncompleteCacheRefs() { return incompleteCacheRefs; } public void addIncompleteCacheRef(CacheRefResolver incompleteCacheRef) { incompleteCacheRefs.add(incompleteCacheRef); } public Collection<ResultMapResolver> getIncompleteResultMaps() { return incompleteResultMaps; } public void addIncompleteResultMap(ResultMapResolver resultMapResolver) { incompleteResultMaps.add(resultMapResolver); } public void addIncompleteMethod(MethodResolver builder) { incompleteMethods.add(builder); } public Collection<MethodResolver> getIncompleteMethods() { return incompleteMethods; } public MappedStatement getMappedStatement(String id) { return this.getMappedStatement(id, true); } public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) { if (validateIncompleteStatements) { buildAllStatements(); } return mappedStatements.get(id); } public Map<String, XNode> getSqlFragments() { return sqlFragments; } public void addInterceptor(Interceptor interceptor) { interceptorChain.addInterceptor(interceptor); } public void addMappers(String packageName, Class<?> superType) { mapperRegistry.addMappers(packageName, superType); } public void addMappers(String packageName) { mapperRegistry.addMappers(packageName); } public <T> void addMapper(Class<T> type) { mapperRegistry.addMapper(type); } public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } public boolean hasMapper(Class<?> type) { return mapperRegistry.hasMapper(type); } public boolean hasStatement(String statementName) { return hasStatement(statementName, true); } public boolean hasStatement(String statementName, boolean validateIncompleteStatements) { if (validateIncompleteStatements) { buildAllStatements(); } return mappedStatements.containsKey(statementName); } public void addCacheRef(String namespace, String referencedNamespace) { cacheRefMap.put(namespace, referencedNamespace); } /* * Parses all the unprocessed statement nodes in the cache. It is recommended * to call this method once all the mappers are added as it provides fail-fast * statement validation. */ protected void buildAllStatements() { parsePendingResultMaps(); if (!incompleteCacheRefs.isEmpty()) { synchronized (incompleteCacheRefs) { incompleteCacheRefs.removeIf(x -> x.resolveCacheRef() != null); } } if (!incompleteStatements.isEmpty()) { synchronized (incompleteStatements) { incompleteStatements.removeIf(x -> { x.parseStatementNode(); return true; }); } } if (!incompleteMethods.isEmpty()) { synchronized (incompleteMethods) { incompleteMethods.removeIf(x -> { x.resolve(); return true; }); } } } private void parsePendingResultMaps() { if (incompleteResultMaps.isEmpty()) { return; } synchronized (incompleteResultMaps) { boolean resolved; IncompleteElementException ex = null; do { resolved = false; Iterator<ResultMapResolver> iterator = incompleteResultMaps.iterator(); while (iterator.hasNext()) { try { iterator.next().resolve(); iterator.remove(); resolved = true; } catch (IncompleteElementException e) { ex = e; } } } while (resolved); if (!incompleteResultMaps.isEmpty() && ex != null) { // At least one result map is unresolvable. throw ex; } } } /** * Extracts namespace from fully qualified statement id. * * @param statementId * @return namespace or null when id does not contain period. */ protected String extractNamespace(String statementId) { int lastPeriod = statementId.lastIndexOf('.'); return lastPeriod > 0 ? statementId.substring(0, lastPeriod) : null; } // Slow but a one time cost. A better solution is welcome. protected void checkGloballyForDiscriminatedNestedResultMaps(ResultMap rm) { if (rm.hasNestedResultMaps()) { for (Map.Entry<String, ResultMap> entry : resultMaps.entrySet()) { Object value = entry.getValue(); if (value instanceof ResultMap) { ResultMap entryResultMap = (ResultMap) value; if (!entryResultMap.hasNestedResultMaps() && entryResultMap.getDiscriminator() != null) { Collection<String> discriminatedResultMapNames = entryResultMap.getDiscriminator().getDiscriminatorMap().values(); if (discriminatedResultMapNames.contains(rm.getId())) { entryResultMap.forceNestedResultMaps(); } } } } } } // Slow but a one time cost. A better solution is welcome. protected void checkLocallyForDiscriminatedNestedResultMaps(ResultMap rm) { if (!rm.hasNestedResultMaps() && rm.getDiscriminator() != null) { for (Map.Entry<String, String> entry : rm.getDiscriminator().getDiscriminatorMap().entrySet()) { String discriminatedResultMapName = entry.getValue(); if (hasResultMap(discriminatedResultMapName)) { ResultMap discriminatedResultMap = resultMaps.get(discriminatedResultMapName); if (discriminatedResultMap.hasNestedResultMaps()) { rm.forceNestedResultMaps(); break; } } } } } protected static class StrictMap<V> extends HashMap<String, V> { private static final long serialVersionUID = -4950446264854982944L; private final String name; private BiFunction<V, V, String> conflictMessageProducer; public StrictMap(String name, int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); this.name = name; } public StrictMap(String name, int initialCapacity) { super(initialCapacity); this.name = name; } public StrictMap(String name) { super(); this.name = name; } public StrictMap(String name, Map<String, ? extends V> m) { super(m); this.name = name; } /** * Assign a function for producing a conflict error message when contains value with the same key. * <p> * function arguments are 1st is saved value and 2nd is target value. * @param conflictMessageProducer A function for producing a conflict error message * @return a conflict error message * @since 3.5.0 */ public StrictMap<V> conflictMessageProducer(BiFunction<V, V, String> conflictMessageProducer) { this.conflictMessageProducer = conflictMessageProducer; return this; } @Override @SuppressWarnings("unchecked") public V put(String key, V value) { if (containsKey(key)) { throw new IllegalArgumentException(name + " already contains value for " + key + (conflictMessageProducer == null ? "" : conflictMessageProducer.apply(super.get(key), value))); } if (key.contains(".")) { final String shortKey = getShortName(key); if (super.get(shortKey) == null) { super.put(shortKey, value); } else { super.put(shortKey, (V) new Ambiguity(shortKey)); } } return super.put(key, value); } @Override public V get(Object key) { V value = super.get(key); if (value == null) { throw new IllegalArgumentException(name + " does not contain value for " + key); } if (value instanceof Ambiguity) { throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name + " (try using the full name including the namespace, or rename one of the entries)"); } return value; } protected static class Ambiguity { final private String subject; public Ambiguity(String subject) { this.subject = subject; } public String getSubject() { return subject; } } private String getShortName(String key) { final String[] keyParts = key.split("\."); return keyParts[keyParts.length - 1]; } } }
2、Configuration.xml配置文件
Configuration是由配置文件Configuration.xml解析转化而来,那么首先要研究这个配置文件。
Configuration.xml是一个XML文件,里面使用DTD进行约束,遵循http://mybatis.org/dtd/mybatis-3-config.dtd约束
通过查看该DTD文件,该配置有
properties:Mybatis3属性;
settings:这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为;
typeAliases:类型别名,可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
typeHandlers:MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型
objectFactory:对象工厂,每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂实例来完成实例化工作。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。 如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现。
objectWrapperFactory: 对象加工工厂,MyBatis 提供在构造对象的时候,对于指定的对象进行特殊的加工
reflectorFactory: 反射工厂,MyBatis 用于缓存 Reflector 的功能
plugins:MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用
environments:MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。
databaseIdProvider:数据库厂商标识,MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性
mappers:定义 SQL 映射语句,告诉 MyBatis 到哪里去找映射文件;
关于该配置文件的详细介绍可以参考官方文档:XML配置
<?xml version="1.0" encoding="UTF-8" ?> <!-- Copyright ${license.git.copyrightYears} the original author or authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)> <!ELEMENT databaseIdProvider (property*)> <!ATTLIST databaseIdProvider type CDATA #REQUIRED > <!ELEMENT properties (property*)> <!ATTLIST properties resource CDATA #IMPLIED url CDATA #IMPLIED > <!ELEMENT property EMPTY> <!ATTLIST property name CDATA #REQUIRED value CDATA #REQUIRED > <!ELEMENT settings (setting+)> <!ELEMENT setting EMPTY> <!ATTLIST setting name CDATA #REQUIRED value CDATA #REQUIRED > <!ELEMENT typeAliases (typeAlias*,package*)> <!ELEMENT typeAlias EMPTY> <!ATTLIST typeAlias type CDATA #REQUIRED alias CDATA #IMPLIED > <!ELEMENT typeHandlers (typeHandler*,package*)> <!ELEMENT typeHandler EMPTY> <!ATTLIST typeHandler javaType CDATA #IMPLIED jdbcType CDATA #IMPLIED handler CDATA #REQUIRED > <!ELEMENT objectFactory (property*)> <!ATTLIST objectFactory type CDATA #REQUIRED > <!ELEMENT objectWrapperFactory EMPTY> <!ATTLIST objectWrapperFactory type CDATA #REQUIRED > <!ELEMENT reflectorFactory EMPTY> <!ATTLIST reflectorFactory type CDATA #REQUIRED > <!ELEMENT plugins (plugin+)> <!ELEMENT plugin (property*)> <!ATTLIST plugin interceptor CDATA #REQUIRED > <!ELEMENT environments (environment+)> <!ATTLIST environments default CDATA #REQUIRED > <!ELEMENT environment (transactionManager,dataSource)> <!ATTLIST environment id CDATA #REQUIRED > <!ELEMENT transactionManager (property*)> <!ATTLIST transactionManager type CDATA #REQUIRED > <!ELEMENT dataSource (property*)> <!ATTLIST dataSource type CDATA #REQUIRED > <!ELEMENT mappers (mapper*,package*)> <!ELEMENT mapper EMPTY> <!ATTLIST mapper resource CDATA #IMPLIED url CDATA #IMPLIED class CDATA #IMPLIED > <!ELEMENT package EMPTY> <!ATTLIST package name CDATA #REQUIRED >
3、config文件解析XMLConfigBuilder.parseConfiguration
/** * Copyright ${license.git.copyrightYears} the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ibatis.builder.xml; import java.io.InputStream; import java.io.Reader; import java.util.Properties; import javax.sql.DataSource; import org.apache.ibatis.builder.BaseBuilder; import org.apache.ibatis.builder.BuilderException; import org.apache.ibatis.datasource.DataSourceFactory; import org.apache.ibatis.executor.ErrorContext; import org.apache.ibatis.executor.loader.ProxyFactory; import org.apache.ibatis.io.Resources; import org.apache.ibatis.io.VFS; import org.apache.ibatis.logging.Log; import org.apache.ibatis.mapping.DatabaseIdProvider; import org.apache.ibatis.mapping.Environment; import org.apache.ibatis.parsing.XNode; import org.apache.ibatis.parsing.XPathParser; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.reflection.DefaultReflectorFactory; import org.apache.ibatis.reflection.MetaClass; import org.apache.ibatis.reflection.ReflectorFactory; import org.apache.ibatis.reflection.factory.ObjectFactory; import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory; import org.apache.ibatis.session.AutoMappingBehavior; import org.apache.ibatis.session.AutoMappingUnknownColumnBehavior; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.LocalCacheScope; import org.apache.ibatis.transaction.TransactionFactory; import org.apache.ibatis.type.JdbcType; /** * @author Clinton Begin * @author Kazuki Shimizu */ public class XMLConfigBuilder extends BaseBuilder { private boolean parsed; private final XPathParser parser; private String environment; private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory(); public XMLConfigBuilder(Reader reader) { this(reader, null, null); } public XMLConfigBuilder(Reader reader, String environment) { this(reader, environment, null); } public XMLConfigBuilder(Reader reader, String environment, Properties props) { this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props); } public XMLConfigBuilder(InputStream inputStream) { this(inputStream, null, null); } public XMLConfigBuilder(InputStream inputStream, String environment) { this(inputStream, environment, null); } 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; } public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; } private void parseConfiguration(XNode root) { try { //issue #117 read properties first // 解析properties propertiesElement(root.evalNode("properties")); //加载settings节点 Properties settings = settingsAsProperties(root.evalNode("settings")); //加载自定义VFS loadCustomVfs(settings); //加载自定义日志器 loadCustomLogImpl(settings); //解析类型别名 typeAliasesElement(root.evalNode("typeAliases")); //加载插件 最常用的插件应该算是分页插件PageHelper、druid连接池提供的各种监控、拦截、预发检查功能,在使用其它连接池比如dbcp的时候,在不修改连接池源码的情况下,就可以借助mybatis的插件体系实现 pluginElement(root.evalNode("plugins")); //加载对象工厂 objectFactoryElement(root.evalNode("objectFactory")); //创建对象包装器工厂 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); //加载反射工厂 reflectorFactoryElement(root.evalNode("reflectorFactory")); //Config设置settings settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 environmentsElement(root.evalNode("environments")); //数据库厂商标识加载 databaseIdProviderElement(root.evalNode("databaseIdProvider")); //加载类型处理器 typeHandlerElement(root.evalNode("typeHandlers")); //加载mapper文件 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } } private Properties settingsAsProperties(XNode context) { if (context == null) { return new Properties(); } Properties props = context.getChildrenAsProperties(); // Check that all settings are known to the configuration class //检查所有从settings加载的设置,确保它们都在Configuration定义的范围内 //首先加载settings下面的setting节点为property,然后检查所有属性,确保它们都在Configuration中已定义,而非未知的设置。 //注:MetaClass是一个保存对象定义比如getter/setter/构造器等的元数据类, //localReflectorFactory则是mybatis提供的默认反射工厂实现,这个ReflectorFactory主要采用了工厂类,其内部使用的Reflector采用了facade设计模式,简化反射的使用。 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; } /** * @Desc: VFS主要用来加载容器内的各种资源,比如jar或者class文件。 * mybatis提供了2个实现 JBoss6VFS 和 DefaultVFS,并提供了用户扩展点,用于自定义VFS实现, * 加载顺序是自定义VFS实现 > 默认VFS实现 取第一个加载成功的, * 默认情况下会先加载JBoss6VFS,如果classpath下找不到jboss的vfs实现才会加载默认VFS实现 */ 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); } } } } private void loadCustomLogImpl(Properties props) { Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl")); configuration.setLogImpl(logImpl); } 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) { typeAliasRegistry.registerAlias(clazz); } else { typeAliasRegistry.registerAlias(alias, clazz); } } catch (ClassNotFoundException e) { throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e); } } } } } private void pluginElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { String interceptor = child.getStringAttribute("interceptor"); Properties properties = child.getChildrenAsProperties(); //将interceptor指定的名称解析为Interceptor类型 Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance(); interceptorInstance.setProperties(properties); configuration.addInterceptor(interceptorInstance); } } } /** * @Desc: MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。 * 默认的对象工厂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).getDeclaredConstructor().newInstance(); factory.setProperties(properties); configuration.setObjectFactory(factory); } } /** * @Desc: 对象包装器工厂主要用来包装返回result对象,比如说可以用来设置某些敏感字段脱敏或者加密等。 * 默认对象包装器工厂是DefaultObjectWrapperFactory,也就是不使用包装器工厂 * BeanWrapper是BaseWrapper的默认实现。其中的两个关键接口是getBeanProperty和setBeanProperty,它们是实现包装的主要位置 * 要实现自定义的对象包装器工厂,只要实现ObjectWrapperFactory中的两个接口hasWrapperFor和getWrapperFor即可 */ private void objectWrapperFactoryElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance(); configuration.setObjectWrapperFactory(factory); } } /** * @Desc: 因为加载配置文件中的各种插件类等等,为了提供更好的灵活性,mybatis支持用户自定义反射工厂, * 不过总体来说,用的不多,要实现反射工厂,只要实现ReflectorFactory接口即可。 * 默认的反射工厂是DefaultReflectorFactory。一般来说,使用默认的反射工厂就可以了 */ private void reflectorFactoryElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); ReflectorFactory factory = (ReflectorFactory) resolveClass(type).getDeclaredConstructor().newInstance(); configuration.setReflectorFactory(factory); } } private void propertiesElement(XNode context) throws Exception { if (context != null) { //首先加载properties节点下的property属性 Properties defaults = context.getChildrenAsProperties(); //从url或resource加载配置文件, String resource = context.getStringAttribute("resource"); String url = context.getStringAttribute("url"); if (resource != null && url != null) {//必须至少包含resource或者url属性之一 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)); } //都先和configuration.variables合并, Properties vars = configuration.getVariables(); if (vars != null) { defaults.putAll(vars); } //然后赋值到XMLConfigBuilder.parser和BaseBuilder.configuration。此时开始所有的属性就可以在随后的整个配置文件中使用了 parser.setVariables(defaults); configuration.setVariables(defaults); } } private void settingsElement(Properties props) { configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL"))); configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE"))); configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true)); configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory"))); configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false)); configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false)); configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true)); configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true)); configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false)); configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE"))); configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null)); configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null)); configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType"))); configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false)); configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false)); configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION"))); configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER"))); configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString")); configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true)); configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage"))); configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler"))); configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false)); configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true)); configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false)); configuration.setLogPrefix(props.getProperty("logPrefix")); configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory"))); } /** * @Desc: 环境可以说是mybatis-config配置文件中最重要的部分,它类似于spring和maven里面的profile,允许给开发、生产环境同时配置不同的environment,根据不同的环境加载不同的配置,这也是常见的做法, * 如果在SqlSessionFactoryBuilder调用期间没有传递使用哪个环境的话,默认会使用一个名为default”的环境。 * 找到对应的environment之后,就可以加载事务管理器和数据源了。 * 事务管理器和数据源类型这里都用到了类型别名,JDBC/POOLED都是在mybatis内置提供的,在Configuration构造器执行期间注册到TypeAliasRegister。 * mybatis内置提供JDBC和MANAGED两种事务管理方式,前者主要用于简单JDBC模式,后者主要用于容器管理事务,一般使用JDBC事务管理方式。 * mybatis内置提供JNDI、POOLED、UNPOOLED三种数据源工厂,一般情况下使用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)) { //事务配置并创建事务工厂 TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); //数据源配置加载并实例化数据源, 数据源是必备的 DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); DataSource dataSource = dsFactory.getDataSource(); //创建Environment.Builder Environment.Builder environmentBuilder = new Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); configuration.setEnvironment(environmentBuilder.build()); } } } } /** * @Desc: MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 * MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的所有语句。 * 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。 * 为支持多厂商特性只要像下面这样在 mybatis-config.xml 文件中加入 databaseIdProvider 即可 */ private void databaseIdProviderElement(XNode context) throws Exception { DatabaseIdProvider databaseIdProvider = null; if (context != null) { String type = context.getStringAttribute("type"); // awful patch to keep backward compatibility //这里的 DB_VENDOR 会通过 DatabaseMetaData#getDatabaseProductName() 返回的字符串进行设置。 //由于通常情况下这个字符串都非常长而且相同产品的不同版本会返回不同的值,所以最好通过设置属性别名来使其变短 if ("VENDOR".equals(type)) { type = "DB_VENDOR"; } Properties properties = context.getChildrenAsProperties(); databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance(); databaseIdProvider.setProperties(properties); } Environment environment = configuration.getEnvironment(); if (environment != null && databaseIdProvider != null) { String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource()); configuration.setDatabaseId(databaseId); } } private TransactionFactory transactionManagerElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); Properties props = context.getChildrenAsProperties(); TransactionFactory factory = (TransactionFactory) resolveClass(type).getDeclaredConstructor().newInstance(); factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a TransactionFactory."); } private DataSourceFactory dataSourceElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); Properties props = context.getChildrenAsProperties(); DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance(); factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a DataSourceFactory."); } /** * @Desc: 无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。 mybatis提供了两种方式注册类型处理器,package自动检索方式和显示定义方式。使用自动检索(autodiscovery)功能的时候,只能通过注解方式来指定 JDBC 的类型 为了简化使用,mybatis在初始化TypeHandlerRegistry期间,自动注册了大部分的常用的类型处理器比如字符串、数字、日期等。 对于非标准的类型,用户可以自定义类型处理器来处理。 要实现一个自定义类型处理器,只要实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个实用类 org.apache.ibatis.type.BaseTypeHandler, 并将它映射到一个 JDBC 类型即可 */ private void typeHandlerElement(XNode parent) { 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); if (javaTypeClass != null) { if (jdbcType == null) { typeHandlerRegistry.register(javaTypeClass, typeHandlerClass); } else { typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass); } } else { typeHandlerRegistry.register(typeHandlerClass); } } } } } private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } } private boolean isSpecifiedEnvironment(String id) { if (environment == null) { throw new BuilderException("No environment specified."); } else if (id == null) { throw new BuilderException("Environment requires an id attribute."); } else if (environment.equals(id)) { return true; } return false; } }
五、mapper配置