转载自 mybatis源码阅读(二):mybatis初始化上
1.初始化入口
//Mybatis 通过SqlSessionFactory获取SqlSession, 然后才能通过SqlSession与数据库进行交互
private static SqlSessionFactory getSessionFactory() {
SqlSessionFactory sessionFactory = null;
String resource = "configuration.xml";
try {
sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));
} catch (IOException e) {
e.printStackTrace();
}
return sessionFactory;
}
那么,我们就先从SqlSessionFactoryBuilder入手, 咱们先看看源码是怎么实现的
SqlSessionFactoryBuilder
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 读取配置文件
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//解析配置得到Configuration对象,创建DefaultSqlSessionFactory对象
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
//关闭读取配置文件的输入流对象
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
XMLConfigBuilder
XMLConfigBuilder是BaseBuilder的众多子类之一,核心字段如下
//表示是否已经解析过了
private boolean parsed;
//用于解析配置文件的对象
private final XPathParser parser;
//配置文件中表示<environment>的名称 默认读取default属性
private String environment;
// 负责和创建Reflector对象
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
XMLConfigBuilder.parse()方法是解析mybatis-config.xml配置文件的如。它调用parseConfiguration()方法实现整个解析过程。具体实现如下:
/**
* 解析配置文件的入口
* @return
*/
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
/**
* 对配置文件每个节点具体的解析过程
* configuration节点为根节点。
* 在configuration节点之下,我们可以配置11 个子节点,
* 分别为:properties、settings、typeAliases、plugins、objectFactory、objectWrapperFactory、reflectorFactory、
* environments、databaseIdProvider、typeHandlers、mappers。
* @param root 根节点
*/
private void parseConfiguration(XNode root) {
try {
// 解析properties节点
propertiesElement(root.evalNode("properties"));
//解析settings节点
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);//设置vfsImpl字段
//解析typeAliases节点
typeAliasesElement(root.evalNode("typeAliases"));
//解析plugins节点
pluginElement(root.evalNode("plugins"));
//解析objectFactory节点
objectFactoryElement(root.evalNode("objectFactory"));
//解析objectWrapperFactory节点
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//解析reflectorFactory节点
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
//解析environments节点
environmentsElement(root.evalNode("environments"));
//解析databaseIdProvider节点
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//解析typeHandlers节点
typeHandlerElement(root.evalNode("typeHandlers"));
//解析mappers节点
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
propertiesElement()方法会解析配置文件中的properties节点并形成Java.util.Properties对象,之后将改对象设置到XpathParse和Configguration的variables字段中,占位符就是用Properties中的信息替换的,具体实现如下:
/**
* 解析properties的具体方法
* @param context
* @throws Exception
*/
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
// 将子节点的 name 以及value属性set进properties对象
// 这儿可以注意一下顺序,xml配置优先, 外部指定properties配置其次
Properties defaults = context.getChildrenAsProperties();
// 获取properties节点上 resource属性的值
String resource = context.getStringAttribute("resource");
// 获取properties节点上 url属性的值, resource和url不能同时配置
String url = context.getStringAttribute("url");
if (resource != null && url != null) {//url 和resource不能同时配置
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
// 把解析出的properties文件set进Properties对象
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
// 将configuration对象中已配置的Properties属性与刚刚解析的融合
// configuration这个对象会装载所解析mybatis配置文件的所有节点元素,以后也会频频提到这个对象
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
// 把装有解析配置propertis对象set进解析器, 因为后面可能会用到
parser.setVariables(defaults);
// set进configuration对象
configuration.setVariables(defaults);
}
}
settings节点下的配饰是mybatis的全局性配置,修改的是configuration对象的属性,具体说明参考官方文档
/**
* settings标签就是设置configuration对象的各种属性,
* 具体属性说明可以参考mybatis官方文档
* @param props
* @throws Exception
*/
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")));
@SuppressWarnings("unchecked")
Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler"));
configuration.setDefaultEnumTypeHandler(typeHandler);
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")));
}
environments元素节点主要配置数据库事物,数据源。可以配置多个environment子节点,假如我们系统的开发环境和正式环境所用的数据库不一样(这是肯定的), 那么可以设置两个environment, 两个id分别对应开发环境(development)和正式环境(final),那么通过配置environments的default属性就能选择对应的environment了, 例如,我将environments的deault属性的值配置为development, 那么就会选择dev的environment。具体实现如下
/**
* 解析enviroments元素节点的方法
* @param context
* @throws Exception
*/
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
//获取 <environments default="development"> 中的default值
environment = context.getStringAttribute("default");
}
// 循环environments的子节点
for (XNode child : context.getChildren()) {
// 获取 <environment id="development"> z中的id
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {//根据由environments的default属性去选择对应的enviroment
// 事物 mybatis有两种:JDBC 和 MANAGED, 配置为JDBC则直接使用JDBC的事务,配置为MANAGED则是将事务托管给容器
// <transactionManager type="JDBC"/>
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
//enviroment节点下面就是dataSource节点了,解析dataSource节点
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// 将dataSource设置进configuration对象
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
typeAliases节点主要用来设置别名,其实这是挺好用的一个功能, 通过配置别名,我们不用再指定完整的包名
/**
* 解析typeAliases 节点
* <typeAliases>
* <!--<package name="com.lpf.entity"></package>-->
* <typeAlias alias="UserEntity" type="com.lpf.entity.User"/>
* </typeAliases>
* @param parent
*/
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//如果子节点是package, 那么就获取package节点的name属性, mybatis会扫描指定的package
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
//TypeAliasRegistry 负责管理别名, 这儿就是通过TypeAliasRegistry 进行别名注册
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
//如果子节点是typeAlias节点,那么就获取alias属性和type的属性值
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);
}
}
}
}
}
具体的别名注册类
public class TypeAliasRegistry {
// 别名通过一个HashMap来实现, key为别名, value就是别名对应的类型(class对象)
private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();
/**
* mybatis默认为我们注册的别名
*/
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);
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);
registerAlias("_byte", byte.class);
registerAlias("_long", long.class);
registerAlias("_short", short.class);
registerAlias("_int", int.class);
registerAlias("_integer", int.class);
registerAlias("_double", double.class);
registerAlias("_float", float.class);
registerAlias("_boolean", boolean.class);
registerAlias("_byte[]", byte[].class);
registerAlias("_long[]", long[].class);
registerAlias("_short[]", short[].class);
registerAlias("_int[]", int[].class);
registerAlias("_integer[]", int[].class);
registerAlias("_double[]", double[].class);
registerAlias("_float[]", float[].class);
registerAlias("_boolean[]", boolean[].class);
registerAlias("date", Date.class);
registerAlias("decimal", BigDecimal.class);
registerAlias("bigdecimal", BigDecimal.class);
registerAlias("biginteger", BigInteger.class);
registerAlias("object", Object.class);
registerAlias("date[]", Date[].class);
registerAlias("decimal[]", BigDecimal[].class);
registerAlias("bigdecimal[]", BigDecimal[].class);
registerAlias("biginteger[]", BigInteger[].class);
registerAlias("object[]", Object[].class);
registerAlias("map", Map.class);
registerAlias("hashmap", HashMap.class);
registerAlias("list", List.class);
registerAlias("arraylist", ArrayList.class);
registerAlias("collection", Collection.class);
registerAlias("iterator", Iterator.class);
registerAlias("ResultSet", ResultSet.class);
}
/**
* 处理别名, 直接从保存有别名的hashMap中取出即可
*/
@SuppressWarnings("unchecked")
// throws class cast exception as well if types cannot be assigned
public <T> Class<T> resolveAlias(String string) {
try {
if (string == null) {
return null;
}
// issue #748
String key = string.toLowerCase(Locale.ENGLISH);
Class<T> value;
if (TYPE_ALIASES.containsKey(key)) {
value = (Class<T>) TYPE_ALIASES.get(key);
} else {
value = (Class<T>) Resources.classForName(string);
}
return value;
} catch (ClassNotFoundException e) {
throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);
}
}
/**
* 配置文件中配置为package的时候,扫描包下的Javabean ,然后自动注册别名
* 默认会使用 Bean 的首字母小写的非限定类名来作为它的别名
* 也可在javabean 加上注解@Alias 来自定义别名, 例如: @Alias(user)
*/
public void registerAliases(String packageName){
registerAliases(packageName, Object.class);
}
public void registerAliases(String packageName, Class<?> superType){
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
for(Class<?> type : typeSet){
// Ignore inner classes and interfaces (including package-info.java)
// Skip also inner classes. See issue #6
if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
registerAlias(type);
}
}
}
public void registerAlias(Class<?> type) {
String alias = type.getSimpleName();
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
alias = aliasAnnotation.value();
}
registerAlias(alias, type);
}
//向hashMap中注册别名
public void registerAlias(String alias, Class<?> value) {
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
}
// issue #748
String key = alias.toLowerCase(Locale.ENGLISH);
if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
}
TYPE_ALIASES.put(key, value);
}
public void registerAlias(String alias, String value) {
try {
registerAlias(alias, Resources.classForName(value));
} catch (ClassNotFoundException e) {
throw new TypeException("Error registering type alias "+alias+" for "+value+". Cause: " + e, e);
}
}
/**
* 获取保存别名的HashMap, Configuration对象持有对TypeAliasRegistry的引用,
* 因此,如果需要,我们可以通过Configuration对象获取
*/
public Map<String, Class<?>> getTypeAliases() {
return Collections.unmodifiableMap(TYPE_ALIASES);
}
}
typeHandlers节点的解析和typeAlianses节点的解析类似
/**
* 解析typeHandlers节点
* 无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,
* 还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
* Mybatis默认为我们实现了许多TypeHandler, 当我们没有配置指定TypeHandler时,
* Mybatis会根据参数或者返回结果的不同,默认为我们选择合适的TypeHandler处理。
* @param parent
* @throws Exception
*/
private void typeHandlerElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//子节点为package时,获取其name属性的值,然后自动扫描package下的自定义typeHandler
if ("package".equals(child.getName())) {
String typeHandlerPackage = child.getStringAttribute("name");
typeHandlerRegistry.register(typeHandlerPackage);
} else {
//子节点为typeHandler时, 可以指定javaType属性, 也可以指定jdbcType, 也可两者都指定
//javaType 是指定java类型
//jdbcType 是指定jdbc类型(数据库类型: 如varchar)
String javaTypeName = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
//handler就是我们配置的typeHandler
String handlerTypeName = child.getStringAttribute("handler");
Class<?> javaTypeClass = resolveClass(javaTypeName);
//JdbcType是一个枚举类型,resolveJdbcType方法是在获取枚举类型的值
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
//注册typeHandler, typeHandler通过TypeHandlerRegistry这个类管理
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
插件是mybatis提供的扩展机制,用户可以通过添加自定义插件在SQL语句执行的过程中某一环节进行拦截,mybatis中的自定义插件只需实现Interceptor接口,并通过注解指定拦截的方法签名,这个后面具体介绍。
/**
* 解析plugins标签
* mybatis中的plugin其实就是个interceptor,
* 它可以拦截Executor 、ParameterHandler 、ResultSetHandler 、StatementHandler 的部分方法,处理我们自己的逻辑。
* @param parent
* @throws Exception
*/
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
// 我们在定义一个interceptor的时候,需要去实现Interceptor
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
interceptorInstance.setProperties(properties);
// 向configuration对象中注册拦截器
configuration.addInterceptor(interceptorInstance);
}
}
}
mybatis初始化时,出了加载mybatis-config.xml的全局配置文件,还会加载全部的映射配置文件,即mappers节点配置的mapper.
/**
* 解析mapper文件,mapper可以理解为dao的实现
* @param parent
* @throws Exception
*/
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//如果mappers节点的子节点是package, 那么就扫描package下的文件, 注入进configuration
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");
//resource, url, class 三选一
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
//mapper映射文件都是通过XMLMapperBuilder解析
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.");
}
}
}
}
}
mybatis初始化过程中对mybatis-config.xml配置文件的解析过程到这吧,下一个就叫啥mapper配置文件的解析过程。