Mybatis源码解读-初始化过程详解

在使用Mybatis时,我们通常将其配置在Spring容器中,当Spring启动的时候会自动加载Mybatis的所有配置文件然后生成注入到Spring中的Bean,本文从实用的角度进行Mybatis源码解读,会关注以下一些方面:

  1. Mybatis都有哪些配置文件和配置项
  2. Mybatis初始化的源码流程;
  3. Mybatis初始化后,产生了哪些对象;

Mybatis初始化环境并且执行SQL语句的JAVA代码

先看一段初始化Mybatis环境并且执行SQL语句的Java代码:

package org.apache.ibatis.session;

import java.io.Reader;
import org.apache.ibatis.io.Resources;

public class MyTest {
	public static void main(String[] args) throws Exception {
		// 开始初始化
		final String resource = "org/apache/ibatis/builder/MapperConfig.xml";
		final Reader reader = Resources.getResourceAsReader(resource);
		SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);

		// 开始执行SQL
		SqlSession session = sqlMapper.openSession();
		Integer count = session.selectOne("org.apache.ibatis.domain.blog.mappers.BlogMapper.selectCountOfPosts");
		System.out.println(count);
	}
}

这段代码完成了这些事情:

1、读取Mybatis的配置文件

2、构建SqlSessionFactory

3、从SqlSessionFactory中创建一个SqlSession

4、使用SqlSession执行一个select语句,参数是Mapper.java的一个方法名

5、打印结果

在这里前三行代码包括读取配置文件和创建SqlSessionFactory,这就是Mybatis的一次初始化过程。

如果查看一下Spring配置Mybatis的文件,就会发现它使用mybatis-spring的包也主要是初始化了这个SqlSessionFactory对象:

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:conf/MapperConfig.xml" />
        <property name="mapperLocations">
            <list>
                <value>classpath*:mapper/*.xml</value>
            </list>
        </property>
    </bean>

该Spring配置sqlSessionFactory接收了三个参数,分别是数据源dataSource、Mybatis的主配置文件MapperConfig.xml、mapper.xml文件的扫描路径。

可以看出Mybatis的初始化过程就是读取配置文件然后构建出sqlSessionFactory的过程。

Mybatis都有哪些配置文件和配置项?

上面的Java代码中初始化Mybatis只使用了配置文件MapperConfig.xml,然而在Spring配置文件中构建sqlSessionFactory时也使用了mapper.xml配置文件,其实Mybatis最多也就这两类文件,主配置文件MapperConfig.xml可以通过<mappers>XML元素包含普通的mapper.xml配置文件。

主配置文件:MapperConfig.xml

一个包含了所有属性的MapperConfig.xml实例:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

	<properties resource="org/apache/ibatis/databases/blog/blog-derby.properties" />

	<settings>
		<setting name="cacheEnabled" value="true" />
		<setting name="lazyLoadingEnabled" value="false" />
		<setting name="multipleResultSetsEnabled" value="true" />
		<setting name="useColumnLabel" value="true" />
		<setting name="useGeneratedKeys" value="false" />
		<setting name="defaultExecutorType" value="SIMPLE" />
		<setting name="defaultStatementTimeout" value="25" />
	</settings>

	<typeAliases>
		<typeAlias alias="Author" type="org.apache.ibatis.domain.blog.Author" />
		<typeAlias alias="Blog" type="org.apache.ibatis.domain.blog.Blog" />
	</typeAliases>

	<typeHandlers>
		<typeHandler javaType="String" jdbcType="VARCHAR"
			handler="org.apache.ibatis.builder.CustomStringTypeHandler" />
	</typeHandlers>

	<objectFactory type="org.apache.ibatis.builder.ExampleObjectFactory">
		<property name="objectFactoryProperty" value="100" />
	</objectFactory>

	<plugins>
		<plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
			<property name="pluginProperty" value="100" />
		</plugin>
	</plugins>

	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC">
				<property name="" value="" />
			</transactionManager>
			<dataSource type="UNPOOLED">
				<property name="driver" value="${driver}" />
				<property name="url" value="${url}" />
				<property name="username" value="${username}" />
				<property name="password" value="${password}" />
			</dataSource>
		</environment>
	</environments>

	<databaseIdProvider type="DB_VENDOR">
		<property name="SQL Server" value="sqlserver" />
		<property name="DB2" value="db2" />
		<property name="Oracle" value="oracle" />
	</databaseIdProvider>

	<mappers>
		<mapper resource="org/apache/ibatis/builder/AuthorMapper.xml" />
		<mapper resource="org/apache/ibatis/builder/BlogMapper.xml" />
	</mappers>
</configuration>

主配置文件只有一个XML节点,就是configuration,它包含9种配置项:

  1. properties 属性:在这里配置的属性可以在整个配置文件中使用来替换需要动态配置的属性值
  2. settings 设置:MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为,比如是否使用缓存和日志记录的方式
  3. typeAliases 类型命名:类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。
  4. typeHandlers 类型处理器:无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
  5. objectFactory 对象工厂:MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。
  6. plugins 插件:MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用,比如增加分页功能、格式化输出最终的SQL等扩展;
  7. environments 环境:MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中,比如设置不同的开发、测试、线上配置,在每个配置中可以配置事务管理器和数据源对象;
  8. databaseIdProvider 数据库厂商标识:MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。
  9. mappers 映射器:MyBatis 的行为已经由上述元素配置完了,通过这里配置的mappers文件,我们可以去查找定义 的SQL 映射语句用于执行;

可以看出,前8个配置项用户设定Mybatis运行的一些环境,而第9个mappers映射器才是需要执行的SQL的配置,在正常情况下,我们只需要配置第9个mapper映射器的地址即可,前面的的Mybatis行为配置都有默认值正常情况下不需要设定。

包含最终SQL的mapper映射器配置文件

虽然我们经常写mapper文件,知道有select/insert/update/delete四种元素,还有sql/resultmap等配置项,就感觉配置项好多好多,但其实mapper总共也就8种配置,我们常用的6种就包含在内:

  1. cache – 给定命名空间的缓存配置。
  2. cache-ref – 其他命名空间缓存配置的引用。
  3. resultMap – 是最复杂也是最强大的元素,用于实现数据库表列和Java Bean的属性名的映射配置;
  4. sql – 可被其他语句引用的可重用语句块。
  5. insert – 映射插入语句
  6. update – 映射更新语句
  7. delete – 映射删除语句
  8. select – 映射查询语句

正常情况下,我们很少使用Mybatis提供的cache机制而是使用外部的Redis等缓存,所以这里的1和2的cache配置几乎不会使用,主要也就是我们平时使用的6种配置。

以上就是Mybatis所有提供给我们配置的地方,改变Mybatis行为的有8个配置项,每个XML配置文件刚好也最多有8个配置项,总共有16个配置项。

Mybatis初始化的源码流程

阅读Mybatis源码最好的方式,就是从源码中的单测作为入口,然后DEBUG一步步的执行,在自己关注的地方多多停留一会仔细查看。

以下以代码的流程进行解析,只贴出主要的代码块:

Mybatis代码初始化入口

	@BeforeClass
	public static void setup() throws Exception {
		createBlogDataSource();
		final String resource = "org/apache/ibatis/builder/MapperConfig.xml";
		final Reader reader = Resources.getResourceAsReader(resource);
		sqlMapper = new SqlSessionFactoryBuilder().build(reader);
	}

这里看到,进入了new SqlSessionFactoryBuilder().build(reader)方法。

进入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) {
				// Intentionally ignore. Prefer previous error.
			}
		}
	}

主要两行在try块内,第一行的内容是调用XPathParser加载了Mybatis的主配置文件,而第二步包含两个步骤,parser.parse()方法返回的是一个Configuration对象,包裹它的build方法只有一行代码:

	public SqlSessionFactory build(Configuration config) {
		return new DefaultSqlSessionFactory(config);
	}

这就可以看出,其实初始化过程就是创建Configuration对象的过程,对照MapperConfig.xml的根元素是<configuration>,不难猜测到Configuration是一个非常重要的、包含了Mybatis所有数据配置的对象。

Mybatis核心对象Configuration的构建过程

接下来进入了XMLConfigBuilder.parse()方法,该方法解析XML文件的/configuration节点,然后挨个解析了上面配置文件中提到的9大配置:

	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
			propertiesElement(root.evalNode("properties"));
			Properties settings = settingsAsProperties(root.evalNode("settings"));
			loadCustomVfs(settings);
			typeAliasesElement(root.evalNode("typeAliases"));
			pluginElement(root.evalNode("plugins"));
			objectFactoryElement(root.evalNode("objectFactory"));
			objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
			reflectorFactoryElement(root.evalNode("reflectorFactory"));
			settingsElement(settings);
			// read it after objectFactory and objectWrapperFactory issue #631
			environmentsElement(root.evalNode("environments"));
			databaseIdProviderElement(root.evalNode("databaseIdProvider"));
			typeHandlerElement(root.evalNode("typeHandlers"));
			mapperElement(root.evalNode("mappers"));
		} catch (Exception e) {
			throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
		}
	}

我们挨个查看,这些配置项的解析,都产出了什么内容;

1、properties配置项的解析

进入propertiesElement方法,我们发现初始化了一个Properties对象,将XML中所有的子节点按照KEY-VALUE存入properties之后,和Configuration.variables变量进行了合并,而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(
						"The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
			}
			if (resource != null) {
				defaults.putAll(Resources.getResourceAsProperties(resource));
			} else if (url != null) {
				defaults.putAll(Resources.getUrlAsProperties(url));
			}
			Properties vars = configuration.getVariables();
			if (vars != null) {
				defaults.putAll(vars);
			}
			parser.setVariables(defaults);
			configuration.setVariables(defaults);
		}
	}

将properties配置解析后合并到Configuration.variables之后,后续的配置文件都可以使用这些变量。

2、setting的配置读取

setting配置的读取,包含两个步骤,第一步,将XML中所有的配置读取到properties对象:

	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
		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;
	}

这个函数读取了setting的配置项,通过反射访问Configuration.class,如果不存在某个配置项的set方法则报错;

然后在settingsElement方法中,将这些读取的配置项存入了Configuration中:

	private void settingsElement(Properties props) throws Exception {
		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.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.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"));
		@SuppressWarnings("unchecked")
		Class<? extends Log> logImpl = (Class<? extends Log>) resolveClass(props.getProperty("logImpl"));
		configuration.setLogImpl(logImpl);
		configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
	}

因为setting变量直接改变的是Mybatis的行为,所以配置项直接存于Confirguration的属性中。

3、typeAliases配置的解析

进入typeAliasesElement方法,用于对typeAliases配置的解析:

	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);
					}
				}
			}
		}
	}

该方法将typeAliases的配置项提取之后,存入了typeAliasRegistry这个对象,该对象是在BaseBuilder中初始化的:

public abstract class BaseBuilder {
	protected final Configuration configuration;
	protected final TypeAliasRegistry typeAliasRegistry;
	protected final TypeHandlerRegistry typeHandlerRegistry;

	public BaseBuilder(Configuration configuration) {
		this.configuration = configuration;
		this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
		this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
	}

在Configuration类中,我们看到了该对象的声明:

protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();

打开该类的代码,发现特别简单的,用一个MAP存储了别名和对应的类的映射:

public class TypeAliasRegistry {

	private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();

	public TypeAliasRegistry() {
		registerAlias("string", String.class);

		registerAlias("byte", Byte.class);
		registerAlias("long", Long.class);
		registerAlias("short", Short.class);
		registerAlias("int", Integer.class);
		registerAlias("integer", Integer.class);
		registerAlias("double", Double.class);
		registerAlias("float", Float.class);
		registerAlias("boolean", Boolean.class);

在构造函数中Mybatis已经默认注册了一些常用的别名和类的关系,所以我们可以在mappers的xml文件中使用这些短名字。

4、typeHandlers配置元素的解析

mybatis提供了大部分数据类型的typeHandlers,如果我们要定制自己的类型处理器比如实现数据库中0/1两个数字到中文男/女的映射,就可以自己实现typeHandler

	private void typeHandlerElement(XNode parent) throws Exception {
		if (parent != null) {
			for (XNode child : parent.getChildren()) {
				if ("package".equals(child.getName())) {
					String typeHandlerPackage = child.getStringAttribute("name");
					typeHandlerRegistry.register(typeHandlerPackage);
				} else {
					String javaTypeName = child.getStringAttribute("javaType");
					String jdbcTypeName = child.getStringAttribute("jdbcType");
					String handlerTypeName = child.getStringAttribute("handler");
					Class<?> javaTypeClass = resolveClass(javaTypeName);
					JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
					Class<?> typeHandlerClass = resolveClass(handlerTypeName);
					if (javaTypeClass != null) {
						if (jdbcType == null) {
							typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
						} else {
							typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
						}
					} else {
						typeHandlerRegistry.register(typeHandlerClass);
					}
				}
			}
		}
	}

在该方法中,通过反射得到了javaTypeClass、jdbcType、typeHandlerClass三个变量,这三个变量组成(javaType、jdbcType、typeHandler)三元组,当遇到javaType到jdbcType的转换,或者遇到jdbcType到javaType的转换时就会使用该typeHandler。

然后该方法调用了TypeHandlerRegistry.register进行注册,TypeHandlerRegistry对象是从BaseBuilder中的Configuration对象中获取的:

public abstract class BaseBuilder {
	protected final Configuration configuration;
	protected final TypeAliasRegistry typeAliasRegistry;
	protected final TypeHandlerRegistry typeHandlerRegistry;

	public BaseBuilder(Configuration configuration) {
		this.configuration = configuration;
		this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
		this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
	}

在TypeHandlerRegistry中,建立了几个Map映射:

public final class TypeHandlerRegistry {

	private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(
			JdbcType.class);
	private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new HashMap<Type, Map<JdbcType, TypeHandler<?>>>();
	private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);
	private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();

第一个是JdbcType为key的map,第二个是JavaType为key的map,第三个是未知的处理器、最后一个是包含全部的处理器;

当执行SQL的时候,会将javaBean的JavaType转换到DB的jdbcType,而查询出来数据的时候,又需要将jdbcType转换成javaType,在TypeHandlerRegistry的构造函数中,已经注册好了很多默认的typeHandler,大部分情况下不需要我们添加:

	public TypeHandlerRegistry() {
		register(Boolean.class, new BooleanTypeHandler());
		register(boolean.class, new BooleanTypeHandler());
		register(JdbcType.BOOLEAN, new BooleanTypeHandler());
		register(JdbcType.BIT, new BooleanTypeHandler());

		register(Byte.class, new ByteTypeHandler());
		register(byte.class, new ByteTypeHandler());
		register(JdbcType.TINYINT, new ByteTypeHandler());

		register(Short.class, new ShortTypeHandler());
		register(short.class, new ShortTypeHandler());
		register(JdbcType.SMALLINT, new ShortTypeHandler());

要实现一个typeHandler,需要实现接口,该接口提供的就是从javaType到jdbcType的setParameter方法,以及从jdbcType到javaType转换的getResult方法:

public interface TypeHandler<T> {

	void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

	T getResult(ResultSet rs, String columnName) throws SQLException;

	T getResult(ResultSet rs, int columnIndex) throws SQLException;

	T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

 

5、objectFactory配置项的解析

如果想自己控制查询数据库的结果到JavaBean映射的生成,则可以创建自己的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);
		}
	}

可以看到,该配置项包含type属性,以及properties子节点,创建好ObjectFactory对象后,就会设置到configuration中:

// Configuration对象的objectFactory成员变量
protected ObjectFactory objectFactory = new DefaultObjectFactory();

要实现ObjectFactory,需要继承该接口:

public interface ObjectFactory {
	void setProperties(Properties properties);
	<T> T create(Class<T> type);
	<T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs);
	<T> boolean isCollection(Class<T> type);
}

该工厂接口提供了设置属性列表,还有创建对象的工厂方法。

6、plugin元素的解析

plugin,即mybatis的插件,可以让我们自己进行开发用于扩展mybatis。

进入pluginElement方法进入解析:

	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);
			}
		}
	}

该段代码,首先获取intercepter元素作为拦截器,然后读取该节点的所有子节点作为配置项,最后调用configuration.addInterceptor方法添加到了configuration中的interceptorChain中,该对象是拦截器链的一个包装对象:

public class InterceptorChain {

	private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

	public Object pluginAll(Object target) {
		for (Interceptor interceptor : interceptors) {
                        // target变量每次也在变化着
			target = interceptor.plugin(target);
		}
		return target;
	}

	public void addInterceptor(Interceptor interceptor) {
		interceptors.add(interceptor);
	}

	public List<Interceptor> getInterceptors() {
		return Collections.unmodifiableList(interceptors);
	}

}

该类中使用List<Interceptor>存储了所有配置的拦截器,并提供了addInterceptor用于添加拦截器,提供了getInterceptors用于获取当前所有添加的插件列表,提供了pluginAll接口调用所有的Interceptor.plugin(Object)方法进行插件的执行。

7、environments的配置解析

environments可以配置多个环境配置,每个配置包含了数据源和事务管理器两项,如下所示代码:

	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());
				}
			}
		}
	}

代码中通过isSpecifiedEnvironment方法判断当前的id是不是指定要读取的environment,如果是的话通过反射获取事务管理器和数据源,然后用Environment.Builder创建Enviroment对象并设置到Configuration中,在Configuration中可以看到Enviroment成员变量:

protected Environment environment;

而Enviroment对象也只包含了这三个属性:

public final class Environment {
	private final String id;
	private final TransactionFactory transactionFactory;
	private final DataSource dataSource;

8、databaseIdProvider配置项的解析

mybatis当然不只是支持mysql,也会支持oracle、sqlserver等不同的数据库,解析代码如下:

	private void databaseIdProviderElement(XNode context) throws Exception {
		DatabaseIdProvider databaseIdProvider = null;
		if (context != null) {
			String type = context.getStringAttribute("type");
			// awful patch to keep backward compatibility
			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);
		}
	}

解析databaseIdProvider后里面的Properties中存储了各种数据库的映射,并且databaseIdProvider提供了一个根据dataSource获取对应的databseId的方法,以VendorDatabaseIdProvider为例,是通过connection.getMetaData().getDatabaseProductName()获取数据库的产品名称,然后从刚才databaseIdProvider中获取对应的databaseId:

public class VendorDatabaseIdProvider implements DatabaseIdProvider {

	private static final Log log = LogFactory.getLog(VendorDatabaseIdProvider.class);

	private Properties properties;

	@Override
	public String getDatabaseId(DataSource dataSource) {
		if (dataSource == null) {
			throw new NullPointerException("dataSource cannot be null");
		}
		try {
			return getDatabaseName(dataSource);
		} catch (Exception e) {
			log.error("Could not get a databaseId from dataSource", e);
		}
		return null;
	}

	@Override
	public void setProperties(Properties p) {
		this.properties = p;
	}

	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();
				}
			}
			// no match, return null
			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) {
					// ignored
				}
			}
		}
	}

}

在获取了databaseId之后,最后将databaseId设置到configuration,后续当执行SQL的时候会自动根据该databaseId来映射具体数据库的SQL。

9、mappers配置项的解析

mappers的解析最为复杂,我们假设mapper文件均是url指定的xml文件,来进行解析流程的查看:

	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加载mapper.xml,然后进入parse()方法
						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.");
					}
				}
			}
		}
	}

加注释部分显示,读取每个mapper.xml资源文件的地址后,进入了XMLMapperBuilder.parse()方法:

	public void parse() {
		if (!configuration.isResourceLoaded(resource)) {
			configurationElement(parser.evalNode("/mapper"));
			configuration.addLoadedResource(resource);
			bindMapperForNamespace();
		}

		parsePendingResultMaps();
		parsePendingChacheRefs();
		parsePendingStatements();
	}

	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("Error parsing Mapper XML. Cause: " + e, e);
		}
	}

然后进入了configurationElement(parser.evalNode(“/mapper”));方法后会读取所有xml中mapper下的子元素,在这里我们只查看buildStatementFromContext方法:

	private void buildStatementFromContext(List<XNode> list) {
		if (configuration.getDatabaseId() != null) {
			buildStatementFromContext(list, configuration.getDatabaseId());
		}
		buildStatementFromContext(list, null);
	}

	private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
		for (XNode context : list) {
			final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant,
					context, requiredDatabaseId);
			try {
				statementParser.parseStatementNode();
			} catch (IncompleteElementException e) {
				configuration.addIncompleteStatement(statementParser);
			}
		}
	}

该方法出现了一个XMLStatementBuilder用于select/insert/update/delete语句各自的解析,在XMLStatementBuilder.parseStatementNode方法中解析了各种语句的属性和参数以及动态SQL的处理,最后调用builderAssistant.addMappedStatement方法,所有的参数和内容被构建成MappedStatement,添加到了configuration中:

MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);

以下是configuration中的mappedStatements对象:

// Configuration的mappedStatements对象
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>(
			"Mapped Statements collection");

这里的StrictMap就是一个HashMap,在addMappedStatement方法中可以看到该map的Key是各个SQL的ID:

	public void addMappedStatement(MappedStatement ms) {
		mappedStatements.put(ms.getId(), ms);
	}

由此可以推测整个Mybatis执行SQL的过程:

  1. 业务代码调用SqlMapperInterface.method方法;
  2. Mybatis根据method的全限定名称作为ID,从mappedStatements找到构建好的MappedStatement对象;
  3. 使用MappedStatement中读取的SQL等各种配置,执行SQL;

Mybatis初始化的对象产出列表总结

以上就是对Mybatis初始化过程的详解,其最终产出了以下对象列表:

  1. 总产出:SqlSessionFactory,相当于ConnectionFactory用于生产SqlSession,而SqlSession相当于Connection用于实际的SQL查询;
  2. SQlSessionFactory的核心对象Configuration,所有的Mybatis配置项和Mapper配置列表,都会被解析并读取到该对象的属性中;
  3. Configuration中的各个对象:
    • Configuration.variables,类型为Properties,存储Mybatis配置的Properties对象;
    • Configuration.直接属性,setting的配置,因为直接影响Mybatis的行为,直接赋值到了Configuration对象的属性;
    • Configuration.TypeAliasRegistry对象,存储别名映射,KEY是别名,VALUE是别名对应的类;
    • Configuration.TypeHandlerRegistry对象,存储typeHandler映射,KEY是javaType或者jdbcType,VALUE是typeHandler类;
    • Configuration.ObjectFactory对象,存储单个的ObjectFactory对象,用于DB映射JAVABEAN对象的创建;
    • Configuration.InterceptorChain对象,存储Plugin对象列表,用户可以添加用于扩展Mybatis;
    • Configuration.Environment对象,指定开发/测试/线上环境,根据其dataSource也决定了databaseId对象的取值;
    • Configuration.databaseId,存储使用的DB的类型,Mybatis会根据不同的DB做SQL适配;
    • Configuration.mappedStatements,存储KEY为ID,VALUE为MappedStatement的MAP,执行SQL时从此处获取对象;

参考资料:

本文地址:http://crazyant.net/2089.html,转载请注明来源。

Mybatis源码解读-设计模式总结

虽然我们都知道有26个设计模式,但是大多停留在概念层面,真实开发中很少遇到,Mybatis源码中使用了大量的设计模式,阅读源码并观察设计模式在其中的应用,能够更深入的理解设计模式。

Mybatis至少遇到了以下的设计模式的使用:

  1. Builder模式,例如SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder;
  2. 工厂模式,例如SqlSessionFactory、ObjectFactory、MapperProxyFactory;
  3. 单例模式,例如ErrorContext和LogFactory;
  4. 代理模式,Mybatis实现的核心,比如MapperProxy、ConnectionLogger,用的jdk的动态代理;还有executor.loader包使用了cglib或者javassist达到延迟加载的效果;
  5. 组合模式,例如SqlNode和各个子类ChooseSqlNode等;
  6. 模板方法模式,例如BaseExecutor和SimpleExecutor,还有BaseTypeHandler和所有的子类例如IntegerTypeHandler;
  7. 适配器模式,例如Log的Mybatis接口和它对jdbc、log4j等各种日志框架的适配实现;
  8. 装饰者模式,例如Cache包中的cache.decorators子包中等各个装饰者的实现;
  9. 迭代器模式,例如迭代器模式PropertyTokenizer;

接下来挨个模式进行解读,先介绍模式自身的知识,然后解读在Mybatis中怎样应用了该模式。

 

1、Builder模式

Builder模式的定义是“将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。”,它属于创建类模式,一般来说,如果一个对象的构建比较复杂,超出了构造函数所能包含的范围,就可以使用工厂模式和Builder模式,相对于工厂模式会产出一个完整的产品,Builder应用于更加复杂的对象的构建,甚至只会构建产品的一个部分。

在Mybatis环境的初始化过程中,SqlSessionFactoryBuilder会调用XMLConfigBuilder读取所有的MybatisMapConfig.xml和所有的*Mapper.xml文件,构建Mybatis运行的核心对象Configuration对象,然后将该Configuration对象作为参数构建一个SqlSessionFactory对象。

其中XMLConfigBuilder在构建Configuration对象时,也会调用XMLMapperBuilder用于读取*Mapper文件,而XMLMapperBuilder会使用XMLStatementBuilder来读取和build所有的SQL语句。

在这个过程中,有一个相似的特点,就是这些Builder会读取文件或者配置,然后做大量的XpathParser解析、配置或语法的解析、反射生成对象、存入结果缓存等步骤,这么多的工作都不是一个构造函数所能包括的,因此大量采用了Builder模式来解决。

对于builder的具体类,方法都大都用build*开头,比如SqlSessionFactoryBuilder为例,它包含以下方法:

即根据不同的输入参数来构建SqlSessionFactory这个工厂对象。

2、工厂模式

在Mybatis中比如SqlSessionFactory使用的是工厂模式,该工厂没有那么复杂的逻辑,是一个简单工厂模式。

简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

SqlSession可以认为是一个Mybatis工作的核心的接口,通过这个接口可以执行执行SQL语句、获取Mappers、管理事务。类似于连接MySQL的Connection对象。
可以看到,该Factory的openSession方法重载了很多个,分别支持autoCommit、Executor、Transaction等参数的输入,来构建核心的SqlSession对象。
在DefaultSqlSessionFactory的默认工厂实现里,有一个方法可以看出工厂怎么产出一个产品:
	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();
		}
	}

这是一个openSession调用的底层方法,该方法先从configuration读取对应的环境配置,然后初始化TransactionFactory获得一个Transaction对象,然后通过Transaction获取一个Executor对象,最后通过configuration、Executor、是否autoCommit三个参数构建了SqlSession。

在这里其实也可以看到端倪,SqlSession的执行,其实是委托给对应的Executor来进行的。

而对于LogFactory,它的实现代码:

public final class LogFactory {
	private static Constructor<? extends Log> logConstructor;

	private LogFactory() {
		// disable construction
	}

	public static Log getLog(Class<?> aClass) {
		return getLog(aClass.getName());
	}

这里有个特别的地方,是Log变量的的类型是Constructor<? extends Log>,也就是说该工厂生产的不只是一个产品,而是具有Log公共接口的一系列产品,比如Log4jImpl、Slf4jImpl等很多具体的Log。

 

3、单例模式

单例模式(Singleton Pattern):单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。

单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。单例模式是一种对象创建型模式。单例模式又名单件模式或单态模式。

在Mybatis中有两个地方用到单例模式,ErrorContext和LogFactory,其中ErrorContext是用在每个线程范围内的单例,用于记录该线程的执行环境错误信息,而LogFactory则是提供给整个Mybatis使用的日志工厂,用于获得针对项目配置好的日志对象。

ErrorContext的单例实现代码:

public class ErrorContext {

	private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();

	private ErrorContext() {
	}

	public static ErrorContext instance() {
		ErrorContext context = LOCAL.get();
		if (context == null) {
			context = new ErrorContext();
			LOCAL.set(context);
		}
		return context;
	}

构造函数是private修饰,具有一个static的局部instance变量和一个获取instance变量的方法,在获取实例的方法中,先判断是否为空如果是的话就先创建,然后返回构造好的对象。

只是这里有个有趣的地方是,LOCAL的静态实例变量使用了ThreadLocal修饰,也就是说它属于每个线程各自的数据,而在instance()方法中,先获取本线程的该实例,如果没有就创建该线程独有的ErrorContext。

 

4、代理模式

代理模式可以认为是Mybatis的核心使用的模式,正是由于这个模式,我们只需要编写Mapper.java接口,不需要实现,由Mybatis后台帮我们完成具体SQL的执行。

代理模式(Proxy Pattern) :给某一个对象提供一个代 理,并由代理对象控制对原对象的引用。代理模式的英 文叫做Proxy或Surrogate,它是一种对象结构型模式。

代理模式包含如下角色:

  • Subject: 抽象主题角色
  • Proxy: 代理主题角色
  • RealSubject: 真实主题角色

这里有两个步骤,第一个是提前创建一个Proxy,第二个是使用的时候会自动请求Proxy,然后由Proxy来执行具体事务;

当我们使用Configuration的getMapper方法时,会调用mapperRegistry.getMapper方法,而该方法又会调用mapperProxyFactory.newInstance(sqlSession)来生成一个具体的代理:

/**
 * @author Lasse Voss
 */
public class MapperProxyFactory<T> {

	private final Class<T> mapperInterface;
	private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

	public MapperProxyFactory(Class<T> mapperInterface) {
		this.mapperInterface = mapperInterface;
	}

	public Class<T> getMapperInterface() {
		return mapperInterface;
	}

	public Map<Method, MapperMethod> getMethodCache() {
		return methodCache;
	}

	@SuppressWarnings("unchecked")
	protected T newInstance(MapperProxy<T> mapperProxy) {
		return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface },
				mapperProxy);
	}

	public T newInstance(SqlSession sqlSession) {
		final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
		return newInstance(mapperProxy);
	}

}

在这里,先通过T newInstance(SqlSession sqlSession)方法会得到一个MapperProxy对象,然后调用T newInstance(MapperProxy<T> mapperProxy)生成代理对象然后返回。

而查看MapperProxy的代码,可以看到如下内容:

public class MapperProxy<T> implements InvocationHandler, Serializable {

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		try {
			if (Object.class.equals(method.getDeclaringClass())) {
				return method.invoke(this, args);
			} else if (isDefaultMethod(method)) {
				return invokeDefaultMethod(proxy, method, args);
			}
		} catch (Throwable t) {
			throw ExceptionUtil.unwrapThrowable(t);
		}
		final MapperMethod mapperMethod = cachedMapperMethod(method);
		return mapperMethod.execute(sqlSession, args);
	}

非常典型的,该MapperProxy类实现了InvocationHandler接口,并且实现了该接口的invoke方法。

通过这种方式,我们只需要编写Mapper.java接口类,当真正执行一个Mapper接口的时候,就会转发给MapperProxy.invoke方法,而该方法则会调用后续的sqlSession.cud>executor.execute>prepareStatement等一系列方法,完成SQL的执行和返回。

5、组合模式

组合模式组合多个对象形成树形结构以表示“整体-部分”的结构层次。

组合模式对单个对象(叶子对象)和组合对象(组合对象)具有一致性,它将对象组织到树结构中,可以用来描述整体与部分的关系。同时它也模糊了简单元素(叶子对象)和复杂元素(容器对象)的概念,使得客户能够像处理简单元素一样来处理复杂元素,从而使客户程序能够与复杂元素的内部结构解耦。

在使用组合模式中需要注意一点也是组合模式最关键的地方:叶子对象和组合对象实现相同的接口。这就是组合模式能够将叶子节点和对象节点进行一致处理的原因。

Mybatis支持动态SQL的强大功能,比如下面的这个SQL:

<update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User">
    UPDATE users
    <trim prefix="SET" prefixOverrides=",">
        <if test="name != null and name != ''">
            name = #{name}
        </if>
        <if test="age != null and age != ''">
            , age = #{age}
        </if>
        <if test="birthday != null and birthday != ''">
            , birthday = #{birthday}
        </if>
    </trim>
    where id = ${id}
</update>

在这里面使用到了trim、if等动态元素,可以根据条件来生成不同情况下的SQL;

在DynamicSqlSource.getBoundSql方法里,调用了rootSqlNode.apply(context)方法,apply方法是所有的动态节点都实现的接口:

public interface SqlNode {
	boolean apply(DynamicContext context);
}

对于实现该SqlSource接口的所有节点,就是整个组合模式树的各个节点:

组合模式的简单之处在于,所有的子节点都是同一类节点,可以递归的向下执行,比如对于TextSqlNode,因为它是最底层的叶子节点,所以直接将对应的内容append到SQL语句中:

	@Override
	public boolean apply(DynamicContext context) {
		GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
		context.appendSql(parser.parse(text));
		return true;
	}

但是对于IfSqlNode,就需要先做判断,如果判断通过,仍然会调用子元素的SqlNode,即contents.apply方法,实现递归的解析。

	@Override
	public boolean apply(DynamicContext context) {
		if (evaluator.evaluateBoolean(test, context.getBindings())) {
			contents.apply(context);
			return true;
		}
		return false;
	}

 

6、模板方法模式

模板方法模式是所有模式中最为常见的几个模式之一,是基于继承的代码复用的基本技术。

模板方法模式需要开发抽象类和具体子类的设计师之间的协作。一个设计师负责给出一个算法的轮廓和骨架,另一些设计师则负责给出这个算法的各个逻辑步骤。代表这些具体逻辑步骤的方法称做基本方法(primitive method);而将这些基本方法汇总起来的方法叫做模板方法(template method),这个设计模式的名字就是从此而来。

模板类定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

在Mybatis中,sqlSession的SQL执行,都是委托给Executor实现的,Executor包含以下结构:

其中的BaseExecutor就采用了模板方法模式,它实现了大部分的SQL执行逻辑,然后把以下几个方法交给子类定制化完成:

	protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;

	protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;

	protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,
			ResultHandler resultHandler, BoundSql boundSql) throws SQLException;

 

该模板方法类有几个子类的具体实现,使用了不同的策略:

  • 简单SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。(可以是Statement或PrepareStatement对象)
  • 重用ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。(可以是Statement或PrepareStatement对象)
  • 批量BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理的;BatchExecutor相当于维护了多个桶,每个桶里都装了很多属于自己的SQL,就像苹果蓝里装了很多苹果,番茄蓝里装了很多番茄,最后,再统一倒进仓库。(可以是Statement或PrepareStatement对象)

比如在SimpleExecutor中这样实现update方法:

	@Override
	public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
		Statement stmt = null;
		try {
			Configuration configuration = ms.getConfiguration();
			StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null,
					null);
			stmt = prepareStatement(handler, ms.getStatementLog());
			return handler.update(stmt);
		} finally {
			closeStatement(stmt);
		}
	}

 

7、适配器模式

适配器模式(Adapter Pattern) :将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

 

在Mybatsi的logging包中,有一个Log接口:

/**
 * @author Clinton Begin
 */
public interface Log {

	boolean isDebugEnabled();

	boolean isTraceEnabled();

	void error(String s, Throwable e);

	void error(String s);

	void debug(String s);

	void trace(String s);

	void warn(String s);

}

该接口定义了Mybatis直接使用的日志方法,而Log接口具体由谁来实现呢?Mybatis提供了多种日志框架的实现,这些实现都匹配这个Log接口所定义的接口方法,最终实现了所有外部日志框架到Mybatis日志包的适配:

比如对于Log4jImpl的实现来说,该实现持有了org.apache.log4j.Logger的实例,然后所有的日志方法,均委托该实例来实现。

public class Log4jImpl implements Log {

	private static final String FQCN = Log4jImpl.class.getName();

	private Logger log;

	public Log4jImpl(String clazz) {
		log = Logger.getLogger(clazz);
	}

	@Override
	public boolean isDebugEnabled() {
		return log.isDebugEnabled();
	}

	@Override
	public boolean isTraceEnabled() {
		return log.isTraceEnabled();
	}

	@Override
	public void error(String s, Throwable e) {
		log.log(FQCN, Level.ERROR, s, e);
	}

	@Override
	public void error(String s) {
		log.log(FQCN, Level.ERROR, s, null);
	}

	@Override
	public void debug(String s) {
		log.log(FQCN, Level.DEBUG, s, null);
	}

	@Override
	public void trace(String s) {
		log.log(FQCN, Level.TRACE, s, null);
	}

	@Override
	public void warn(String s) {
		log.log(FQCN, Level.WARN, s, null);
	}

}

 

8、装饰者模式

装饰模式(Decorator Pattern) :动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。根据翻译的不同,装饰模式也有人称之为“油漆工模式”,它是一种对象结构型模式。

 

在mybatis中,缓存的功能由根接口Cache(org.apache.ibatis.cache.Cache)定义。整个体系采用装饰器设计模式,数据存储和缓存的基本功能由PerpetualCache(org.apache.ibatis.cache.impl.PerpetualCache)永久缓存实现,然后通过一系列的装饰器来对PerpetualCache永久缓存进行缓存策略等方便的控制。如下图:

用于装饰PerpetualCache的标准装饰器共有8个(全部在org.apache.ibatis.cache.decorators包中):

  1. FifoCache:先进先出算法,缓存回收策略
  2. LoggingCache:输出缓存命中的日志信息
  3. LruCache:最近最少使用算法,缓存回收策略
  4. ScheduledCache:调度缓存,负责定时清空缓存
  5. SerializedCache:缓存序列化和反序列化存储
  6. SoftCache:基于软引用实现的缓存管理策略
  7. SynchronizedCache:同步的缓存装饰器,用于防止多线程并发访问
  8. WeakCache:基于弱引用实现的缓存管理策略

另外,还有一个特殊的装饰器TransactionalCache:事务性的缓存

正如大多数持久层框架一样,mybatis缓存同样分为一级缓存和二级缓存

  • 一级缓存,又叫本地缓存,是PerpetualCache类型的永久缓存,保存在执行器中(BaseExecutor),而执行器又在SqlSession(DefaultSqlSession)中,所以一级缓存的生命周期与SqlSession是相同的。
  • 二级缓存,又叫自定义缓存,实现了Cache接口的类都可以作为二级缓存,所以可配置如encache等的第三方缓存。二级缓存以namespace名称空间为其唯一标识,被保存在Configuration核心配置对象中。

二级缓存对象的默认类型为PerpetualCache,如果配置的缓存是默认类型,则mybatis会根据配置自动追加一系列装饰器。

Cache对象之间的引用顺序为:

SynchronizedCache–>LoggingCache–>SerializedCache–>ScheduledCache–>LruCache–>PerpetualCache

9、迭代器模式

迭代器(Iterator)模式,又叫做游标(Cursor)模式。GOF给出的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。

 

Java的Iterator就是迭代器模式的接口,只要实现了该接口,就相当于应用了迭代器模式:

比如Mybatis的PropertyTokenizer是property包中的重量级类,该类会被reflection包中其他的类频繁的引用到。这个类实现了Iterator接口,在使用时经常被用到的是Iterator接口中的hasNext这个函数。

public class PropertyTokenizer implements Iterator<PropertyTokenizer> {
	private String name;
	private String indexedName;
	private String index;
	private String children;

	public PropertyTokenizer(String fullname) {
		int delim = fullname.indexOf('.');
		if (delim > -1) {
			name = fullname.substring(0, delim);
			children = fullname.substring(delim + 1);
		} else {
			name = fullname;
			children = null;
		}
		indexedName = name;
		delim = name.indexOf('[');
		if (delim > -1) {
			index = name.substring(delim + 1, name.length() - 1);
			name = name.substring(0, delim);
		}
	}

	public String getName() {
		return name;
	}

	public String getIndex() {
		return index;
	}

	public String getIndexedName() {
		return indexedName;
	}

	public String getChildren() {
		return children;
	}

	@Override
	public boolean hasNext() {
		return children != null;
	}

	@Override
	public PropertyTokenizer next() {
		return new PropertyTokenizer(children);
	}

	@Override
	public void remove() {
		throw new UnsupportedOperationException(
				"Remove is not supported, as it has no meaning in the context of properties.");
	}
}

可以看到,这个类传入一个字符串到构造函数,然后提供了iterator方法对解析后的子串进行遍历,是一个很常用的方法类。

 参考资料:

本文地址:http://crazyant.net/2022.html,转载请注明来源。