资源加载
mybatis资源加载的过程主要是处理xml里面的三种类型的标签
- Properties 在configuration对象中保存配置文件中
的值。在propertiesElement这个方法中被解析 - TypeHandlerRegistry 类型处理器注册的类,用来处理查询到的结果中jdbc类型和java类型不匹配进行映射。默认情况下Mybatis已经提供了许多类型匹配处理器
- TypeAliasRegistry 别名处理注册的类,用来收集别名与class对应的映射,默认Mybatis已经提供了许多类型映射例如list表示java.util.List
处理configuration标签
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
看一下SqlSessionFactoryBuilder类代码,可以知道没有构造方法直接采用默认的构造方法,然后使用build方法创建SqlSessionFactory对象。这个类采用了build模式,仿照了spring里面实例ClassPathXmlApplicationContext类的方式。多个方法最终都是调用一个方法类实例,如果没有传参,则采用默认方式。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
先看创建XMLConfigBuilder对象的过程
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
//XMLConfigBuilder对象持有XPathParser对象
//XPathParser对象持有Document对象
//Document对象持有inputStream对象解析出来的xml文档的完整数据。解析方式采用JDK中的javax.xml.parsers
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
继续看一下实例化XPathParser对象
//分析xml
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
//这个创建对象,比较简单,就是属性的赋值
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(inputStream));
}
以上实现方式采用的是java提供的xml解析方式,在javax.xml.parser这个包里面。目前通过上面的操作xml文件已经变成了Document对象。可以通个getElementByName等等方法获得Element元素。并且这个Document对象已经间接的被XMLConfigBuilder持有。继续调用XMLConfigBuilder.parse()方法。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
先看上面的parser.evalNode("/configuration")
我们在配置Mybatis的时候可以看到根节点就是configuration。可以猜测应该是拿到configuration这个节点的所有数据
加载configuration细节
继续看这句parseConfiguration(parser.evalNode("/configuration"));
这个方法里面是要处理configuration节点。在parser.evalNode("/configuration")
里面已经拿到了
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
//获得properties节点,保存到configuration.variables属性里面
propertiesElement(root.evalNode("properties"));
//将别名添加到configuration.TypeAliasRegistry 这个里面已经默认注册了常用的数据类
/**
* 下面简单列几个mybatis提供的默认类型别名。当然也可以自定义。会解析下面标签
*
* registerAlias("byte", Byte.class);
* registerAlias("byte[]", Byte[].class);
* registerAlias("_byte", byte.class);
* registerAlias("ResultSet", ResultSet.class);
* registerAlias("list", List.class);
*/
typeAliasesElement(root.evalNode("typeAliases"));
//这个是解析插件,暂时跳过
pluginElement(root.evalNode("plugins"));
//这个也没怎么用到,先跳过
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectionFactoryElement(root.evalNode("reflectionFactory"));
settingsElement(root.evalNode("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);
}
}
加载properties节点
上面只标注了一些经常使用的节点解析过程,一些不常用的就没有分析,例如objectFactory,objectWrapperFactory就不分析了。先来看一下properties这种键值对解析过程。过程很简单分为两种情况:
- 直接是键值对存在的直接生成Properties对象
- 使用Resource进行引入文件的,先读取文件变为inputStream流,然后从中读出键值对
最后解析完数据,将Properties对象保存到configuration对象中configuration.setVariables(defaults);
//将配置文件里面的properties解析出来,保存到configuration对象里面的variables里面
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
//直接解析类似: 种的键值对
Properties defaults = context.getChildrenAsProperties();
//处理Resource这种资源:
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);
}
}
加载typeAlias节点
继续分析第二个方法typeAliasesElement(root.evalNode("typeAliases"));
这个方法是处理别名。当时用
的时候就是在时用别名。这些别名回注册到configuration对象里面TypeAliasRegistry
对象。这个对象里面已经默认创建了一些常用类型的别名,这就是为什么在写sql的时候可以直接使用resultType="list"
因为这个类型已经默认注册到TypeAliasRegistry
而在执行sql之后返回类型会先到configuration对象里面找到TypeAliasRegistry
里面是否有这个别名的对象,如果没有就只用反射Class创建对象。下面简单看看TypeAliasRegistry
。所有的类型最终都是放在map里面
public class TypeAliasRegistry {
private final Map> TYPE_ALIASES = new HashMap>();
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
//省略...
}
}
加载typeHandler节点
注册的过程比较简单就是将name作为可以,class作为值放到map对象里面。细节就不介绍了。跳过不常用的属性objectFactory,objectWrapperFactory,reflectionFactory,可以简单看一下setting。在使用mybatis的时候只要有许多默认的setting配置。这些配置就是从settingsElement(root.evalNode("settings"));
里面注入进去的。如果没有设置那么统统都读取默认配置,设置了就用你自己的。再来看一下类型处理器TypeHandler这个是指查询sql之后返回的jdbs类型和java类型不匹配时可以使用你自定义的类型来处理返回数据。Mybatis里面已经设置了好多默认的类型处理器。
/**
* 处理这种类型标签。分别将javaType,jdbcType,handler三个属性注册到configuration.typeHandlerRegistry里面
*
* @param parent
* @throws Exception
*/
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);
}
}
}
}
}
上面的注册过程很简单,就是读取node下面的jdbcType和javaType和处理类handler。然后注册到typeHandlerRegistry里面。看一下默认的类型处理器
public TypeHandlerRegistry() {
register(Boolean.class, new BooleanTypeHandler());
register(boolean.class, new BooleanTypeHandler());
register(JdbcType.BOOLEAN, new BooleanTypeHandler());
register(JdbcType.BIT, new BooleanTypeHandler());
//省略.....
}