核心处理层以基础支持层为基础,实现了MyBatis
的核心功能。这个部分将从MyBatis
的初始化、动态SQL
语句的解析、结果集的映射、参数解析以及SQL
语句的执行等几个方面分析MyBatis
的核心处理层,了解MyBatis
的核心原理。
本篇介绍MyBatis的初始化
在MyBatis
初始化的过程中,除了会读取mybatis-config.xml
配置文件以及映射配置文件,还会加载配置文件指定的类,处理类中的注解,创建一些配置对象,最终完成框架中各个模块的初始化。
另外,也可以使用JavaAPI
的方式对MyBatis
进行配置,这种硬编码的配置方式主要用在配置量比较少且配置信息不常变化的场景下。
建造者模式 在MyBatis
处理mybatis-config.xml
以及映射配置文件时,会在内存中创建相应的配置对象,该过程的设计使用到建造者模式 的相关知识。
建造者模式中的主要角色如下:
建造者(Builder)接口
Builder接口用于定义建造者构建产品对象的各部分的行为。
具体建造者(ConcreteBuilder)角色
在建造者模式中,直接创建产品对象的是具体建造者。 具体建造者类必须实现建造者接口所要求的两类方法:
一类是建造方法,如上图中的buildPart1()
、buildPart2()
等方法。
另一类是获取构建好的产品对象的方法,如上图中的getProduct()
方法。
导演(Director)角色
该角色会通过调用具体建造者, 创建需要的产品对象
产品(Product)角色
产品对象就是用户需要使用的复杂对象
建造者模式的优点:
建造者模式中的导演角色并不需要知晓产品类的内部细节,它只提供需要的信息给建造者,由具体建造者处理这些信息(这个处理过程可能会比较复杂)并完成产品构造。这就使产品对象的上层代码与产品对象的创建过程解耦。
建造者模式将复杂产品的创建过程分散到了不同的构造步骤中,这样可以对产品创建过程实现更加精细的控制,也会使创建过程更加清晰。
每个具体建造者都可以创建出完整的产品对象,而且具体建造者之间是相互独立的,因此系统就可以通过不同的具体建造者,得到不同的产品对象。当有新产品出现时,无须修改原有的代码,只需要添加新的具体建造者即可完成扩展 ,这符合开放-封闭 原则。
建造者模式也有一些缺点,它所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式。
如果产品种类较多,且内部变化复杂,就需要定义多个具体建造者类来实现这种变化,导致整个系统变得很复杂,不易于理解。
BaseBuilder MyBatis
初始化的主要工作是加载并解析mybatis-config.xml
配置文件、映射配置文件以及相关的注解信息。MyBatis
的初始化入口是SqlSessionFactoryBuilder.build()
方法,其具体实现如下:
public SqlSessionFactory build (Reader reader, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder (reader, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession." , e); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException e) { } } }
SqlSessionFactoryBuilder.build()
方法会创建XMLConfigBuilder
对象来解析mybatis-config.xml
配置文件,而XMLConfigBuilder
继承自BaseBuilder
抽象类。
MyBatis
的初始化过程使用了建造者模式,这里的BaseBuilder
抽象类就扮演着建造者接口的角色。BaseBuilder
中核心字段的含义如下:
protected final Configuration configuration;protected final TypeAliasRegistry typeAliasRegistry;protected final TypeHandlerRegistry typeHandlerRegistry;
BaseBuilder
中记录的TypeAliasRegistry
对象和TypeHandlerRegistry
对象,其实是全局唯一的,它们都是在Configuration
对象初始化时创建的。
Configuration
类中定义了这两个字段:
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry ();protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry ();
在BaseBuilder
构造函数中,通过相应的Configuration.get*()
方法得到TypeAliasRegistry
对象和TypeHandlerRegistry
对象,并赋值给BaseBuilder
相应字段。
BaseBuilder.resolveAlias()
方法依赖TypeAliasRegistry
解析别名,BaseBuilder.resolveTypeHandler()
方法依赖TypeHandlerRegistry
查找指定的TypeHandler
对象。
MyBatis
使用JdbcType
枚举类型表示JDBC
类型。MyBatis
中常用的枚举类型还有ResultSetType
和ParameterMode:ResultSetType
枚举类型表示结果集类型,使用ParameterMode
枚举类型表示存储过程中的参数类型。
在BaseBuilder
中提供了相应的resolveJdbcType()
、resolveResultSetType()
、resolveParameterMode()
方法,将String
转换成对应的枚举对象。
XMLConfigBuilder XMLConfigBuilder
是BaseBuilder
的众多子类之一,它扮演的是具体建造者的角色。**XMLConfigBuilder
主要负责解析mybatis-config.xml
配置。**
核心字段:
private boolean parsed;private final XPathParser parser;private String environment;private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory ();
XMLConfigBuilder.parse()
方法是解析mybatis-config.xml
配置文件的入口,它通过调用XMLConfigBuilder.parseConfiguration()
方法实现整个解析过程。
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 { 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); 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); } }
解析<properties>
节点
XMLConfigBuilder.propertiesElement()
方法会解析mybatis-config.xml
配置文件中的properties
节点并形成java.util.Properties
对象,之后将该Properties
对象设置到XPathParser
和Configuration
的variables
字段中。在后面的解析过程中,会使用该Properties
对象中的信息替换占位符。
private void propertiesElement (XNode context) throws Exception { if (context != null ) { Properties defaults = context.getChildrenAsProperties(); String resource = context.getStringAttribute("resource" ); String url = context.getStringAttribute("url" ); if (resource != null && url != null ) { throw new BuilderException ("..." ); } 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); } parser.setVariables(defaults); configuration.setVariables(defaults); } }
解析<settings>
节点
XMLConfigBuilder.settingsAsProperties()
方法负责解析settings
节点,在settings
点下的配置是MyBatis
全局性的配置,它们会改变MyBatis
的运行时行为,需要注意的是,在MyBatis
初始化时,这些全局配置信息都会被记录到Configuration
对象的对应属性中。
例如:开发人员可以通过配置autoMappingBehavior
修改MyBatis
是否开启自动映射的功能,具体配置如下
<settings > <!- autoMappingBehavior配置项 是决 定MyBatis是否幵 启 自动 映射功能的条 件之一 --> <setting name ="autoMappingBehavior" value ="PARTIAL" /> </settings >
在Configuration
中存在一个同名的相应字段:
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
settingsAsProperties()
方法的解析方式与propertiesElement()
方法类似,但是多了使用MetaClass
检测key
指定的属性在Configuration
类中是否有对应setter
方法的步骤。
private Properties settingsAsProperties (XNode context) { if (context == null ) { return new Properties (); } Properties props = context.getChildrenAsProperties(); MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory); for (Object key : props.keySet()) { if (!metaConfig.hasSetter(String.valueOf(key))) { throw new BuilderException ("..." ); } } return props; }
解析<typeAliases>
、<typeHandlers>
节点
XMLConfigBuilder.typeAliasesElement()
方法负责解析typeAliases
节点点及其子节点,并通过TypeAliasRegistry
完成别名的注册。
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 ("..." ); } } } } }
XMLConfigBuilder.typeHandlerElement()
方法负责解析typeHandlers
节点,并通过TypeHandlerRegistry
对象完成TypeHandler
的注册,该方法的实现与typeAliasesElement()
方法类似。
解析<plugins>
节点
插件是MyBatis
提供的扩展机制之一,用户可以通过添加自定义插件在SQL
语句执行过程中的某一点进行拦截。
MyBatis
中的自定义插件只需实现Interceptor
接口,并通过注解指定想要拦截的方法签名即可。这里分析MyBatis中如何加载和管理插件。XMLConfigBuilder.pluginElement()
方法负责解析plugins
节点中定义的插件,并完成实例化和配置操作。
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 interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); interceptorInstance.setProperties(properties); configuration.addInterceptor(interceptorInstance); } } }
所有配置的Interceptor
对象都是通过Configuration.interceptorChain
字段(InterceptorChain
类型)管理的,InterceptorChain
底层使用ArrayList<Interceptor>
实现。
public class InterceptorChain { private final List<Interceptor> interceptors = new ArrayList <Interceptor>(); public Object pluginAll (Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; } public void addInterceptor (Interceptor interceptor) { interceptors.add(interceptor); } public List<Interceptor> getInterceptors () { return Collections.unmodifiableList(interceptors); } }
解析<objectFactory>
节点
我们可以通过添加自定义Objectory
实现类、ObjectWrapperFactory
实现类以及ReflectorFactory
实现类对MyBatis
进行扩展。XMLConfigBuilder.objectFactoryElement()
方法负责解析并实例化<objectFactory>
节点指定的ObjectFactory
实现类,之后将自定义的ObjectFactory
对象记录到Configuration.objectFactory
字段中。
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); } }
XMLConfigBuilder
对<objectWrapperFactory>
节点、<reflectorFactory>
节点的解析与上述过程类似,最终会将解析得到的自定义对象记录到Configuration
的相应字段中。
解析<environments>
节点
在实际生产中,同一项目可能分为开发、测试和生产多个不同的环境,每个环境的配置可能也不尽相同。
MyBatis
可以配置多个<environment>
节点,每个<environment>
节点对应一种环境的配置。但需要注意的是,尽管可以配置多个环境,每个SqlSessionFactory
实例只能选择其一。
XMLConfigBuilder.environmentsElement()
方法负责解析<environments>
的相关配置,它会根据XMLConfigBuilder.environment
字段值确定要使用的<environment>
配置,之后创建对应的TransactionFactory
和DataSource
对象,并封装进Environment
对象中。
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 environmentBuilder = new Environment .Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); configuration.setEnvironment(environmentBuilder.build()); } } } }
解析<databaseldProvider>
节点
MyBatis
不能像Hibernate
那样,直接帮助开发人员屏蔽多种数据库产品在SQL
语言支持方面的差异。
但是在mybatis-config.xml
配置文件中,通过<databaseIdProvider>
定义所有支持的数据库产品的databaseld
,然后在映射配置文件中定义SQL
语句节点时,通过databaseld
指定该SQL
语句应用的数据库产品,这样也可以实现类似的功能。
在MyBatis
初始化时,会根据前面确定的DataSource
确定当前使用的数据库产品,然后在解析映射配置文件时,加载不带databaseld
属性和带有匹配当前数据库databaseld
属性的所有SQL语句。如果同时找到带有databaseld
和不带databaseld
的相同语句,则后者会被舍弃,使用前者。
XMLConfigBuilder.databaseIdProviderElement()
方法负责解析<databaseIdProvider>
节点,并创建指定的DatabaseldProvider
对象。DatabaseldProvider
会返回databaseld
值,MyBatis
会根据databaseld
选择合适的SQL
进行执行。
private void databaseIdProviderElement (XNode context) throws Exception { DatabaseIdProvider databaseIdProvider = null ; if (context != null ) { String type = context.getStringAttribute("type" ); if ("VENDOR" .equals(type)) { type = "DB_VENDOR" ; } Properties properties = context.getChildrenAsProperties(); databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance(); databaseIdProvider.setProperties(properties); } Environment environment = configuration.getEnvironment(); if (environment != null && databaseIdProvider != null ) { String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource()); configuration.setDatabaseId(databaseId); } }
MyBatis
提供的DatabaseldProvider
接口及其实现比较简单。
DatabaseldProvider
接口的核心方法是getDatabaseId()
方法,它主要负责通过给定的DataSource
来查找对应的databaseld
。
MyBatis
提供了VendorDatabaseldProvider
和DefaukDatabaseldProvider
两个实现,其中DefaultDatabaseldProvider
己过时。
VendorDatabaseIdProvider.getDatabaseId()
方法在接收到DataSource
对象时,会先解析DataSource
所连接的数据库产品名称,之后根据<databaseIdProvider>
节点配置的数据库产品名称与databaseld
的对应关系确定最终的databaseld
。
private String getDatabaseName (DataSource dataSource) throws SQLException { String productName = getDatabaseProductName(dataSource); if (this .properties != null ) { for (Map.Entry<Object, Object> property : properties.entrySet()) { if (productName.contains((String) property.getKey())) { return (String) property.getValue(); } } return null ; } return productName; } private String getDatabaseProductName (DataSource dataSource) throws SQLException { Connection con = null ; try { con = dataSource.getConnection(); DatabaseMetaData metaData = con.getMetaData(); return metaData.getDatabaseProductName(); } finally { if (con != null ) { try { con.close(); } catch (SQLException e) { } } } }
解析<mappers>
节点
在MyBatis
初始化时,除了加载mybatis-config.xml
配置文件,还会加载全部的映射配置文件,mybatis-config.xml
配置文件中的<mappers>
节点会告诉MyBatis
去哪些位置查找映射配置文件以及使用了配置注解标识的接口。XMLConfigBuilder.mapperElement()
方法负责解析<mappers>
节点,它会创建XMLMapperBuilder
对象加载映射文件,如果映射配置文件存在相应的Mapper
接口,也会加载相应的Mapper
接口,解析其中的注解并完成向MapperRegistry
的注册。
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 ("..." ); } } } } }
XMLMapperBuilder 通过对XMLConfigBuilder.mapperElement()
方法的介绍我们知道,XMLMapperBuilder
负责解析映射配置文件,它继承了BaseBuilder
抽象类,也是具体建造者的角色。XMLMapperBuilder.parse()
方法是解析映射文件的入口。
public void parse () { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper" )); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }
XMLMapperBuilder
也是将每个节点的解析过程封装成了一个方法,而这些方法由XMLMapperBuilder.configurationElement()
方法调用。
private void configurationElement (XNode context) { try { String namespace = context.getStringAttribute("namespace" ); if (namespace == null || namespace.equals("" )) { throw new BuilderException ("Mapper's namespace cannot be empty" ); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref" )); cacheElement(context.evalNode("cache" )); parameterMapElement(context.evalNodes("/mapper/parameterMap" )); resultMapElements(context.evalNodes("/mapper/resultMap" )); sqlElement(context.evalNodes("/mapper/sql" )); buildStatementFromContext(context.evalNodes("select|insert|update|delete" )); } catch (Exception e) { throw new BuilderException ("..." ); } }
1. 解析<cache>
节点
MyBatis
拥有非常强大的二级缓存功能,该功能可以非常方便地进行配置,**MyBatis
默认情**况下没有开启二级缓存,如果要为某命名空间开启二级缓存功能,则需要在相应映射配置文件中添加<cache>
节点,还可以通过配置<cache>
节点的相关属性,为二级缓存配置相应的特性(本质上就是添加相应的装饰器)。
XMLMapperBuilder.cacheElement()
方法主要负责解析<cache>
节点:
private void cacheElement (XNode context) throws Exception { if (context != null ) { String type = context.getStringAttribute("type" , "PERPETUAL" ); Class<? extends Cache > typeClass = typeAliasRegistry.resolveAlias(type); String eviction = context.getStringAttribute("eviction" , "LRU" ); Class<? extends Cache > evictionClass = typeAliasRegistry.resolveAlias(eviction); Long flushInterval = context.getLongAttribute("flushInterval" ); Integer size = context.getIntAttribute("size" ); boolean readWrite = !context.getBooleanAttribute("readOnly" , false ); boolean blocking = context.getBooleanAttribute("blocking" , false ); Properties props = context.getChildrenAsProperties(); builderAssistant.useNewCache( typeClass, evictionClass, flushInterval, size, readWrite, blocking, props); } }
MapperBuilderAssistant
是一个辅助类,其useNewCache()
方法负责创建Cache
对象,并将其添加到Configuration.caches
集合中保存。
Configuration
中的caches
字段是StrictMap<Cache>
类型的字段,它记录Cache
的id(默认是映射文件的namespace
)与Cache
对象(二级缓存)之间的对应关系。
StrictMap
继承了HashMap
,并在其基础上进行了少许修改,这里重点关注StrictMap.put()
方法,如果检测到重复的key
则抛出异常,如果没有重复的key
则添加key
以及value
,同时会根据key
产生shortKey
。
public V put (String key, V value) { if (containsKey(key)) { throw new IllegalArgumentException (name + " already contains value for " + key); } 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); }
Ambiguity
是StrictMap
中定义的静态内部类,它表示的是存在二义性的键值对 。Ambiguity
中使用subject
字段记录了存在二义性的key
,并提供了相应的getter
方法。
StrictMap.get()
方法会检测value
是否存在以及value
是否为Ambiguity
类型对象,如果满足这两个条件中的任意一个,则抛出异常。
public V get (Object key) { V value = super .get(key); if (value == null ) { throw new IllegalArgumentException ("..." ); } if (value instanceof Ambiguity) { throw new IllegalArgumentException ("..." ); } return value; }
MapperBuilderAssistant. useNewCache()
方法的实现如下:
public Cache useNewCache (Class<? extends Cache> typeClass, Class<? extends Cache> evictionClass, Long flushInterval, Integer size, boolean readWrite, boolean blocking, Properties props) { Cache cache = new CacheBuilder (currentNamespace) .implementation(valueOrDefault(typeClass, PerpetualCache.class)) .addDecorator(valueOrDefault(evictionClass, LruCache.class)) .clearInterval(flushInterval) .size(size) .readWrite(readWrite) .blocking(blocking) .properties(props) .build(); configuration.addCache(cache); currentCache = cache; return cache; }
CacheBuilder
是Cache
的建造者,其字段如下:
private final String id;private Class<? extends Cache > implementation;private final List<Class<? extends Cache >> decorators;private Integer size;private Long clearInterval;private boolean readWrite;private Properties properties;private boolean blocking;
CacheBuilder.build()
方法,根据CacheBuilder
中上述字段的值创建Cache
对象并添加合适的装饰器。
public Cache build () { setDefaultImplementations(); Cache cache = newBaseCacheInstance(implementation, id); setCacheProperties(cache); if (PerpetualCache.class.equals(cache.getClass())) { for (Class<? extends Cache > decorator : decorators) { cache = newCacheDecoratorInstance(decorator, cache); setCacheProperties(cache); } cache = setStandardDecorators(cache); } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) { cache = new LoggingCache (cache); } return cache; }
CacheBuilder.setCacheProperties()
方法会根据<cache>
节点下配置的<property>
信息,初始化Cache
对象。
private void setCacheProperties (Cache cache) { if (properties != null ) { MetaObject metaCache = SystemMetaObject.forObject(cache); for (Map.Entry<Object, Object> entry : properties.entrySet()) { String name = (String) entry.getKey(); String value = (String) entry.getValue(); if (metaCache.hasSetter(name)) { Class<?> type = metaCache.getSetterType(name); if (String.class == type) { metaCache.setValue(name, value); } else if (int .class == type || Integer.class == type) { metaCache.setValue(name, Integer.valueOf(value)); } else if (long .class == type || Long.class == type) { metaCache.setValue(name, Long.valueOf(value)); } else if (short .class == type || Short.class == type) { metaCache.setValue(name, Short.valueOf(value)); } else if (byte .class == type || Byte.class == type) { metaCache.setValue(name, Byte.valueOf(value)); } else if (float .class == type || Float.class == type) { metaCache.setValue(name, Float.valueOf(value)); } else if (boolean .class == type || Boolean.class == type) { metaCache.setValue(name, Boolean.valueOf(value)); } else if (double .class == type || Double.class == type) { metaCache.setValue(name, Double.valueOf(value)); } else { throw new CacheException ("..." ); } } } } if (InitializingObject.class.isAssignableFrom(cache.getClass())){ try { ((InitializingObject) cache).initialize(); } catch (Exception e) { throw new CacheException ("..." , e); } } }
CacheBuilder.setStandardDecorators()
方法会根据CacheBuilder
中各个字段的值,为cache
对象添加对应的装饰器。
private Cache setStandardDecorators (Cache cache) { try { MetaObject metaCache = SystemMetaObject.forObject(cache); if (size != null && metaCache.hasSetter("size" )) { metaCache.setValue("size" , size); } if (clearInterval != null ) { cache = new ScheduledCache (cache); ((ScheduledCache) cache).setClearInterval(clearInterval); } if (readWrite) { cache = new SerializedCache (cache); } cache = new LoggingCache (cache); cache = new SynchronizedCache (cache); if (blocking) { cache = new BlockingCache (cache); } return cache; } catch (Exception e) { throw new CacheException ("..." , e); } }
2. 解析<cache-ref>
节点
XMLMapperBuilder.cacheElement()
方法会为每个namespace
创建一个对应的Cache
对象,并在Configuration.caches
集合中记录namespace
与Cache
对象之间的对应关系。
如果希望多个namespace
共用同一个二级缓存,即同一个Cache
对象,则可以使用<cache-ref>
点进行配置。
XMLMapperBuilder.cacheReffilement()
方法负责解析<cache-ref>
节点。
这里首先需要了解的是Configuration.cacheRefMap
集合,该集合是HashMap<String,String>
类型,其中key
是<cache-ref>
节点所在的namespace
,value
是<cache-ref>
节点的namespace
属性所指定的namespace
。
也就是说,前者共用后者的Cache
对象,如下图,namespace2
共用了namespace1
的Cache
对象。
XMLMapperBuilder.cacheReffilement()
:
private void cacheRefElement (XNode context) { if (context != null ) { configuration.addCacheRef( builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace" )); CacheRefResolver cacheRefResolver = new CacheRefResolver ( builderAssistant, context.getStringAttribute("namespace" )); try { cacheRefResolver.resolveCacheRef(); } catch (IncompleteElementException e) { configuration.addIncompleteCacheRef(cacheRefResolver); } } }
CacheRefResolver
是一个简单的Cache
引用解析器,其中封装了被引用的namespace
以及当前XMLMapperBuilder
对应的MapperBuilderAssistant
对象。
CacheRefResolver.resolveCacheRef()
方法会调用MapperBuilderAssistant.useCacheRef()
方法。在MapperBuilderAssistant.useCacheRef()
方法中会通过namespace
查找被引用的Cache
对象。
public Cache useCacheRef (String namespace) { if (namespace == null ) { throw new BuilderException ("..." ); } try { unresolvedCacheRef = true ; Cache cache = configuration.getCache(namespace); if (cache == null ) { throw new IncompleteElementException ("..." ); } currentCache = cache; unresolvedCacheRef = false ; return cache; } catch (IllegalArgumentException e) { throw new IncompleteElementException ("..." , e); } }
另一个需要了解的Configuration
字段是incompleteCacheRefs
集合,它是LinkedList<CacheRefResolver>
类型,其中记录了当前解析出现异常的CacheRefResolver
对象。
3. 解析<resultMap>
节点
select
语句查询得到的结果集是一张二维表 ,水平方向上看是一个个字段,垂直方向上看是一条条记录 。
而Java
是面向对象的程序设计语言,对象是根据类定义创建的,类之间的引用关系可以认为是嵌套的结构。
在JDBC
编程中,为了将结果集中的数据映射成对象,我们需要自己写代码从结果集中获取数据,然后封装成对应的对象并设置对象之间的关系,而这些都是大量的重复性代码。
为了减少这些重复的代码,MyBatis
使用<resultMap>
节点定义了结果集与结果对象(JavaBean
对象)之间的映射规则,<resultMap>
节点可以满足绝大部分的映射需求,从而减少开发人员的重复性劳动,提高开发效率。
每个ResultMapping
对象记录了结果集中的一列与JavaBean
中一个属性之间的映射关系。<resultMap>
节点下除了<discriminator>
子节点的其他子节点,都会被解析成对应的ResultMapping
对象。核心字段如下:
private Configuration configuration;private String property;private String column;private Class<?> javaType;private JdbcType jdbcType;private TypeHandler<?> typeHandler;private String nestedResultMapId;private String nestedQueryId;private Set<String> notNullColumns;private String columnPrefix;private List<ResultFlag> flags;private List<ResultMapping> composites;private String resultSet;private String foreignColumn;private boolean lazy;
ResultMapping
中定义了一个内部Builder
类,也应用了建造者模式,该Builder
类主要用于数据整理和数据校验校验。
另一个比较重要的类是ResultMap
,每个<resultMap>
节点都会被解析成一个ResultMap
对象,其中每个节点所定义的映射关系,则使用ResultMapping
对象表示。
ResultMap
字段定义:
private Configuration configuration;private String id;private Class<?> type;private List<ResultMapping> resultMappings;private List<ResultMapping> idResultMappings;private List<ResultMapping> constructorResultMappings;private List<ResultMapping> propertyResultMappings;private Set<String> mappedColumns;private Set<String> mappedProperties;private Discriminator discriminator;private boolean hasNestedResultMaps;private boolean hasNestedQueries;private Boolean autoMapping;
在XMLMapperBuilder
中通过resultMapElements()
方法解析映射配置文件中的全部<resultMap>
节点,该方法会循环调用resultMapElement()
方法处理每个<resultMap>
节点。
private ResultMap resultMapElement (XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception { ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier()); String id = resultMapNode.getStringAttribute( "id" , resultMapNode.getValueBasedIdentifier()); String type = resultMapNode.getStringAttribute( "type" ,resultMapNode.getStringAttribute( "ofType" ,resultMapNode.getStringAttribute("resultType" , resultMapNode.getStringAttribute("javaType" )))); String extend = resultMapNode.getStringAttribute("extends" ); Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping" ); Class<?> typeClass = resolveClass(type); Discriminator discriminator = null ; List<ResultMapping> resultMappings = new ArrayList <ResultMapping>(); resultMappings.addAll(additionalResultMappings); List<XNode> resultChildren = resultMapNode.getChildren(); for (XNode resultChild : resultChildren) { if ("constructor" .equals(resultChild.getName())) { processConstructorElement(resultChild, typeClass, resultMappings); } else if ("discriminator" .equals(resultChild.getName())) { discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings); } else { List<ResultFlag> flags = new ArrayList <ResultFlag>(); if ("id" .equals(resultChild.getName())) { flags.add(ResultFlag.ID); } resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); } } ResultMapResolver resultMapResolver = new ResultMapResolver (builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); try { return resultMapResolver.resolve(); } catch (IncompleteElementException e) { configuration.addIncompleteResultMap(resultMapResolver); throw e; } }
在处理<resultMap>
节点的过程中,该过程在执行获取到id
属性和type
属性之后,就会通过XMLMapperBuilder.buildResultMappingFromContext()
方法为<result>
节点创建对应的ResultMapping
对象。
private ResultMapping buildResultMappingFromContext (XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception { String property; if (flags.contains(ResultFlag.CONSTRUCTOR)) { property = context.getStringAttribute("name" ); } else { property = context.getStringAttribute("property" ); } String column = context.getStringAttribute("column" ); String javaType = context.getStringAttribute("javaType" ); String jdbcType = context.getStringAttribute("jdbcType" ); String nestedSelect = context.getStringAttribute("select" ); String nestedResultMap = context.getStringAttribute( "resultMap" ,processNestedResultMappings(context, Collections.<ResultMapping>emptyList())); String notNullColumn = context.getStringAttribute("notNullColumn" ); String columnPrefix = context.getStringAttribute("columnPrefix" ); String typeHandler = context.getStringAttribute("typeHandler" ); String resultSet = context.getStringAttribute("resultSet" ); String foreignColumn = context.getStringAttribute("foreignColumn" ); boolean lazy = "lazy" .equals( context.getStringAttribute("fetchType" , configuration.isLazyLoadingEnabled() ? "lazy" : "eager" )); Class<?> javaTypeClass = resolveClass(javaType); @SuppressWarnings("unchecked") Class<? extends TypeHandler <?>> typeHandlerClass = (Class<? extends TypeHandler <?>>) resolveClass(typeHandler); JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType); return builderAssistant.buildResultMapping( resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy); }
MapperBuilderAssistant.buildResultMapping()
的具体实现:
public ResultMapping buildResultMapping ( Class<?> resultType, String property, String column, Class<?> javaType, JdbcType jdbcType, String nestedSelect, String nestedResultMap, String notNullColumn, String columnPrefix, Class<? extends TypeHandler<?>> typeHandler, List<ResultFlag> flags, String resultSet, String foreignColumn, boolean lazy) { Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType); TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler); List<ResultMapping> composites = parseCompositeColumnName(column); return new ResultMapping .Builder(configuration, property, column, javaTypeClass) .jdbcType(jdbcType) .nestedQueryId(applyCurrentNamespace(nestedSelect, true )) .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true )) .resultSet(resultSet) .typeHandler(typeHandlerInstance) .flags(flags == null ? new ArrayList <ResultFlag>() : flags) .composites(composites) .notNullColumns(parseMultipleColumnNames(notNullColumn)) .columnPrefix(columnPrefix) .foreignColumn(foreignColumn) .lazy(lazy) .build(); }
得到ResultMapping
对象集合之后,会调用ResultMapResolver.resolve()
方法,该方法会调用MapperBuilderAssistant.addResultMap()
方法创建ResultMap
对象,并将ResultMap
对象添加到Configuration.resultMaps
集合中保存。
public ResultMap addResultMap ( String id, Class<?> type, String extend, Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) { id = applyCurrentNamespace(id, false ); extend = applyCurrentNamespace(extend, true ); if (extend != null ) { if (!configuration.hasResultMap(extend)) { throw new IncompleteElementException ("..." ); } ResultMap resultMap = configuration.getResultMap(extend); List<ResultMapping> extendedResultMappings = new ArrayList <ResultMapping>(resultMap.getResultMappings()); extendedResultMappings.removeAll(resultMappings); boolean declaresConstructor = false ; for (ResultMapping resultMapping : resultMappings) { if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) { declaresConstructor = true ; break ; } } if (declaresConstructor) { Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator(); while (extendedResultMappingsIter.hasNext()) { if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) { extendedResultMappingsIter.remove(); } } } resultMappings.addAll(extendedResultMappings); } ResultMap resultMap = new ResultMap .Builder(configuration, id, type, resultMappings, autoMapping) .discriminator(discriminator) .build(); configuration.addResultMap(resultMap); return resultMap; }
<constructor>
节点的解析,由XMLMapperBuilder.processConstructorElement()
方法完成。
private void processConstructorElement (XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception { List<XNode> argChildren = resultChild.getChildren(); for (XNode argChild : argChildren) { List<ResultFlag> flags = new ArrayList <ResultFlag>(); flags.add(ResultFlag.CONSTRUCTOR); if ("idArg" .equals(argChild.getName())) { flags.add(ResultFlag.ID); } resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags)); } }
之后会解析<association>
节点,正如前面对XMLMapperBuilder.resultMapElement()
方法的介绍,<association>
节点也是在XMLMapperBuilder.buildResultMappingFromContext()
方法中完成解析的。
<discriminator>
节点的解析,该解析过程由XMLMapperBuilder.processDiscriminatorElement()
方法完成。
private Discriminator processDiscriminatorElement (XNode context, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception { String column = context.getStringAttribute("column" ); String javaType = context.getStringAttribute("javaType" ); String jdbcType = context.getStringAttribute("jdbcType" ); String typeHandler = context.getStringAttribute("typeHandler" ); Class<?> javaTypeClass = resolveClass(javaType); @SuppressWarnings("unchecked") Class<? extends TypeHandler <?>> typeHandlerClass = (Class<? extends TypeHandler <?>>) resolveClass(typeHandler); JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType); Map<String, String> discriminatorMap = new HashMap <String, String>(); for (XNode caseChild : context.getChildren()) { String value = caseChild.getStringAttribute("value" ); String resultMap = caseChild.getStringAttribute("resultMap" , processNestedResultMappings(caseChild, resultMappings)); discriminatorMap.put(value, resultMap); } return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap); }
4. 解析<sql>
节点
XMLMapperBuilder.sqlElement()
方法负责解析映射配置文件中定义的全部<sql>
节点。
private void sqlElement (List<XNode> list) throws Exception { if (configuration.getDatabaseId() != null ) { sqlElement(list, configuration.getDatabaseId()); } sqlElement(list, null ); } private void sqlElement (List<XNode> list, String requiredDatabaseId) throws Exception { for (XNode context : list) { String databaseId = context.getStringAttribute("databaseId" ); String id = context.getStringAttribute("id" ); id = builderAssistant.applyCurrentNamespace(id, false ); if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) { sqlFragments.put(id, context); } } }
XMLStatementBuilder 除了节点解析,映射文件中还有一类比较重要的节点需要解析,也就是SQL
节点。SQL
节点主要用于定义SQL
语句,SQL
节点由XMLStatementBuilder
负责解析。
MyBatis
使用SqlSource
接口表示映射文件或注解中定义的SQL
语句,但它表示的SQL
语句是不能直接被数据库执行的,因为其中可能含有动态SQL
语句相关的节点或是占位符等需要解析的元素。
public interface SqlSource { BoundSql getBoundSql (Object parameterObject) ; }
MyBatis
使用MappedStatement
表示映射配置文件中定义的SQL
节点,MappedStatement
包含了这些节点的很多属性。
private String resource;private Configuration configuration;private String id;private Integer fetchSize;private Integer timeout;private StatementType statementType;private ResultSetType resultSetType;private SqlSource sqlSource;private Cache cache;private ParameterMap parameterMap;private List<ResultMap> resultMaps;private boolean flushCacheRequired;private boolean useCache;private boolean resultOrdered;private SqlCommandType sqlCommandType;private KeyGenerator keyGenerator;private String[] keyProperties;private String[] keyColumns;private boolean hasNestedResultMaps;private String databaseId;private Log statementLog;private LanguageDriver lang;private String[] resultSets;
XMLStatementBuilder.parseStatementNode()
方法是解析SQL
节点的入口函数。
public void parseStatementNode () { String id = context.getStringAttribute("id" ); String databaseId = context.getStringAttribute("databaseId" ); if (!databaseIdMatchesCurrent(id, databaseId, this .requiredDatabaseId)) { return ; } Integer fetchSize = context.getIntAttribute("fetchSize" ); Integer timeout = context.getIntAttribute("timeout" ); String parameterMap = context.getStringAttribute("parameterMap" ); String parameterType = context.getStringAttribute("parameterType" ); Class<?> parameterTypeClass = resolveClass(parameterType); String resultMap = context.getStringAttribute("resultMap" ); String resultType = context.getStringAttribute("resultType" ); String lang = context.getStringAttribute("lang" ); LanguageDriver langDriver = getLanguageDriver(lang); Class<?> resultTypeClass = resolveClass(resultType); String resultSetType = context.getStringAttribute("resultSetType" ); StatementType statementType = StatementType.valueOf( context.getStringAttribute("statementType" , StatementType.PREPARED.toString())); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); String nodeName = context.getNode().getNodeName(); SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = context.getBooleanAttribute("flushCache" , !isSelect); boolean useCache = context.getBooleanAttribute("useCache" , isSelect); boolean resultOrdered = context.getBooleanAttribute("resultOrdered" , false ); XMLIncludeTransformer includeParser = new XMLIncludeTransformer (configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); processSelectKeyNodes(id, parameterTypeClass, langDriver); SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); String resultSets = context.getStringAttribute("resultSets" ); String keyProperty = context.getStringAttribute("keyProperty" ); String keyColumn = context.getStringAttribute("keyColumn" ); KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true ); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys" , configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } builderAssistant.addMappedStatement( id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }
1. 解析<include>
节点
在解析SQL
节点之前,首先通过XMLIncludeTransformer
解析SQL
语句中的<include>
节点,该过程会将<include>
节点替换成<sql>
节点中定义的SQL
片段,并将其中的${xxx}
占位符替换成真实的参数,该解析过程在XMLIncludeTransformer.applyIncludes()
方法中实现。
public void applyIncludes (Node source) { Properties variablesContext = new Properties (); Properties configurationVariables = configuration.getVariables(); if (configurationVariables != null ) { variablesContext.putAll(configurationVariables); } applyIncludes(source, variablesContext, false ); } private void applyIncludes (Node source, final Properties variablesContext, boolean included) { if (source.getNodeName().equals("include" )) { Node toInclude = findSqlFragment(getStringAttribute(source, "refid" ), variablesContext); Properties toIncludeContext = getVariablesContext(source, variablesContext); applyIncludes(toInclude, toIncludeContext, true ); if (toInclude.getOwnerDocument() != source.getOwnerDocument()) { toInclude = source.getOwnerDocument().importNode(toInclude, true ); } source.getParentNode().replaceChild(toInclude, source); while (toInclude.hasChildNodes()) { toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude); } toInclude.getParentNode().removeChild(toInclude); } else if (source.getNodeType() == Node.ELEMENT_NODE) { if (included && !variablesContext.isEmpty()) { NamedNodeMap attributes = source.getAttributes(); for (int i = 0 ; i < attributes.getLength(); i++) { Node attr = attributes.item(i); attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext)); } } NodeList children = source.getChildNodes(); for (int i = 0 ; i < children.getLength(); i++) { applyIncludes(children.item(i), variablesContext, included); } } else if (included && source.getNodeType() == Node.TEXT_NODE && !variablesContext.isEmpty()) { source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext)); } }
<inClude>
节点和<sql>
节点可以配合使用、多层嵌套,实现更加复杂的sql
片段的重用,这样的话,解析过程就会递归更多层,流程变得更加复杂。
2. 解析<selectKey>
节点
在<insert>
、<update>
节点中可以定义<selectKey>
节点来解决主键自增问题,<selectKey>
节点对应的KeyGenerator
接口在后面会详细介绍,现在重点关节点的解析。
XMLStatementBuilder.processSelectKeyNodes()
方法负责解析SQL
节点中子节点。
private void processSelectKeyNodes (String id, Class<?> parameterTypeClass, LanguageDriver langDriver) { List<XNode> selectKeyNodes = context.evalNodes("selectKey" ); if (configuration.getDatabaseId() != null ) { parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId()); } parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null ); removeSelectKeyNodes(selectKeyNodes); }
在parseSelectKeyNodes()
方法中会为<selectKey>
节点生成id
,检测databaseld
是否匹配以及是否己经加载过相同id
且databaseld
不为空的<selectKey>
节点,并调用parseSelectKeyNode()
方法处理每个<selectKey>
节点。 在parseSelectKeyNode()
方法中,首先读取<selectKey>
节点的一系列属性,然后调用LanguageDriver.createSqlSource()
方法创建对应的SqlSource
对象,最后创建MappedStatement
对象,并添加到Configuration.mappedStatements
集合中保存。
private void parseSelectKeyNode (String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) { String resultType = nodeToHandle.getStringAttribute("resultType" ); Class<?> resultTypeClass = resolveClass(resultType); StatementType statementType = StatementType.valueOf( nodeToHandle.getStringAttribute("statementType" , StatementType.PREPARED.toString())); String keyProperty = nodeToHandle.getStringAttribute("keyProperty" ); String keyColumn = nodeToHandle.getStringAttribute("keyColumn" ); boolean executeBefore = "BEFORE" .equals(nodeToHandle.getStringAttribute("order" , "AFTER" )); boolean useCache = false ; boolean resultOrdered = false ; KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE; Integer fetchSize = null ; Integer timeout = null ; boolean flushCache = false ; String parameterMap = null ; String resultMap = null ; ResultSetType resultSetTypeEnum = null ; SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass); SqlCommandType sqlCommandType = SqlCommandType.SELECT; builderAssistant.addMappedStatement( id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null ); id = builderAssistant.applyCurrentNamespace(id, false ); MappedStatement keyStatement = configuration.getMappedStatement(id, false ); configuration.addKeyGenerator(id, new SelectKeyGenerator (keyStatement, executeBefore)); }
LanguageDriver
接口有两个实现类。
在Configuration
的构造方法中,可以看到如下代码片段,我们由此可以判断默认使用的XMLLanguageDriver
实现类。
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
也可以提供自定义的LanguageDriver
实现,并在mybatis-config.xml
中通过defaultScriptingLanguage
配置指定使用该自定义实现。 在XMLLanguageDriver.createSqlSource()
方法中会创建XMLScriptBuilder
对象并XMLScriptBuilder.parseScriptNode()
方法创建SqlSource
对象。
public SqlSource parseScriptNode () { MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource = null ; if (isDynamic) { sqlSource = new DynamicSqlSource (configuration, rootSqlNode); } else { sqlSource = new RawSqlSource (configuration, rootSqlNode, parameterType); } return sqlSource; }
在XMLScriptBuilder.parseDynamicTags()
方法中,会遍历<selectKey>
下的每个节点,如果包含任何标签节点,则认为是动态SQL
语句;如果文本节点中含有${}
占位符,也认为其为动态SQL语句。
protected MixedSqlNode parseDynamicTags (XNode node) { List<SqlNode> contents = new ArrayList <SqlNode>(); NodeList children = node.getNode().getChildNodes(); for (int i = 0 ; i < children.getLength(); i++) { XNode child = node.newXNode(children.item(i)); if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { String data = child.getStringBody("" ); TextSqlNode textSqlNode = new TextSqlNode (data); if (textSqlNode.isDynamic()) { contents.add(textSqlNode); isDynamic = true ; } else { contents.add(new StaticTextSqlNode (data)); } } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { String nodeName = child.getNode().getNodeName(); NodeHandler handler = nodeHandlerMap.get(nodeName); if (handler == null ) { throw new BuilderException ("Unknown element <" + nodeName + "> in SQL statement." ); } handler.handleNode(child, contents); isDynamic = true ; } } return new MixedSqlNode (contents); }
上面遇到的TextSqlNode
、StaticTextSqlNode
等都是SqlNode
接口的实现,SqlNode
接口的 每个实现都对应于不同的动态SQL
节点类型,每个实现的具体代码后面遇到了再详细分析。
TextSqlNode.isDynamic()
方法中会通过GenericTokenParser
和DynamicCheckerTokenParser
配合解析文本节点,并判断它是否为动态SQL
。
public boolean isDynamic () { DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser (); GenericTokenParser parser = createParser(checker); parser.parse(text); return checker.isDynamic(); } @Override public String handleToken (String content) { this .isDynamic = true ; return null ; }
3. 解析SQL节点
经过上述两个解析过程之后,<include>
节点和<selectKey>
节点己经被解析并删除掉了。XMLStatementBuilder.parseStatementNode()
方法剩余的操作就是解析SQL
节点。
public void parseStatementNode () { String id = context.getStringAttribute("id" ); String databaseId = context.getStringAttribute("databaseId" ); if (!databaseIdMatchesCurrent(id, databaseId, this .requiredDatabaseId)) { return ; } Integer fetchSize = context.getIntAttribute("fetchSize" ); Integer timeout = context.getIntAttribute("timeout" ); String parameterMap = context.getStringAttribute("parameterMap" ); String parameterType = context.getStringAttribute("parameterType" ); Class<?> parameterTypeClass = resolveClass(parameterType); String resultMap = context.getStringAttribute("resultMap" ); String resultType = context.getStringAttribute("resultType" ); String lang = context.getStringAttribute("lang" ); LanguageDriver langDriver = getLanguageDriver(lang); Class<?> resultTypeClass = resolveClass(resultType); String resultSetType = context.getStringAttribute("resultSetType" ); StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType" , StatementType.PREPARED.toString())); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); String nodeName = context.getNode().getNodeName(); SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = context.getBooleanAttribute("flushCache" , !isSelect); boolean useCache = context.getBooleanAttribute("useCache" , isSelect); boolean resultOrdered = context.getBooleanAttribute("resultOrdered" , false ); XMLIncludeTransformer includeParser = new XMLIncludeTransformer (configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); processSelectKeyNodes(id, parameterTypeClass, langDriver); SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); String resultSets = context.getStringAttribute("resultSets" ); String keyProperty = context.getStringAttribute("keyProperty" ); String keyColumn = context.getStringAttribute("keyColumn" ); KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true ); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys" , configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }
绑定Mapper接口 每个映射配置文件的命名空间可以绑定一个Mapper
接口,并注册到MapperRegistry
中。
在XMLMapperBuilder.bindMapperForNamespace()
方法中,完成了映射配置文件与对应Mapper
接口的绑定。
private void bindMapperForNamespace () { String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null ) { Class<?> boundType = null ; try { boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { } if (boundType != null ) { if (!configuration.hasMapper(boundType)) { configuration.addLoadedResource("namespace:" + namespace); configuration.addMapper(boundType); } } } }
在介绍MapperRegistry.addMapper()
方法时,只提到了该方法会向MapperRegistry.knownMappers
集合注册指定的Mapper
接口,其实该方法还会创建MapperAnnotationBuilder
,并调用MapperAnnotationBuilder.parse()
方法解析Mapper
接口中的注解信息。
public void parse () { String resource = type.toString(); if (!configuration.isResourceLoaded(resource)) { loadXmlResource(); configuration.addLoadedResource(resource); assistant.setCurrentNamespace(type.getName()); parseCache(); parseCacheRef(); Method[] methods = type.getMethods(); for (Method method : methods) { try { if (!method.isBridge()) { parseStatement(method); } } catch (IncompleteElementException e) { configuration.addIncompleteMethod(new MethodResolver (this , method)); } } } parsePendingMethods(); }
处理incomplete*集合 XMLMapperBuilder.configurationElement()
方法解析映射配置文件时,是按照从文件头到文件尾的顺序解析的,但是有时候在解析一个节点时,会引用定义在该节点之后的、还未解析的节点,这就会导致解析失败并抛出IncompleteElementException
。
根据抛出异常的节点不同,MyBatis
会创建不同的*Resolver
对象,并添加到Configuration
的不同incomplete*
集合中。
例如,
解析Mapper
接口中的方法出现异常时,会创建MethodResolver
对象,并将其追加到Configuration.incompleteMethods
集合(LinkedList<MethodResolver>
类型)中暂存;
解析<resultMap>
节点时出现异常,则会将对应的ResultMapResolver
对象追加到incompleteResultMaps
(LinkedList<ResultMapResolver>
类型)集合中暂存;
解析<cache-ref>
节点时出现异常,则会将对应的CacheRefResolver
对象追加到incompleteCacheRefs
(LinkedList<CacheRefResolver>
类型)集合中暂存;
解析SQL
语句节点时出现异常,则会将对应的XMLStatementBuilder
对象追加到incompleteStatements
(LinkedList<XMLStatementBuilder>
类型)集合中暂存。
在XMLMapperBuilder.parse()
方法中可以看到,通过configurationElement()
方法完了一次映射配置文件的解析后,还会调用parsePendingResultMaps()
方法、parsePendingChacheRefs()
方法、parsePendingStatements()
方法三个parsePending*()
方法处理Configuration
中对应的三个incomplete*
集合。所有parsePending*()
方法的逻辑都是基本类似的,这里以parsePendingStatements()
方法为例进行分析
private void parsePendingStatements () { Collection<XMLStatementBuilder> incompleteStatements = configuration.getIncompleteStatements(); synchronized (incompleteStatements) { Iterator<XMLStatementBuilder> iter = incompleteStatements.iterator(); while (iter.hasNext()) { try { iter.next().parseStatementNode(); iter.remove(); } catch (IncompleteElementException e) { } } } }
到此为止,MyBatis
的初始化过程就全部介绍完了,其中分析了mybatis-config.xml
配置文件的解析过程、映射配置文件的解析过程以及Mapper
接口中相关注解的解析过程。
参考
《MyBatis技术内幕》
部分图片来源——《MyBatis技术内幕》