大家应该都使用过JDBC,传统的JDBC工作流程比较繁琐,需要先连接,然后处理JDBC底层事务,还需要操作Connection对象、Statement对象去拿取数据,并且还要捕捉各种可能出现的异常,十分复杂,因此我们一般使用ORM框架对数据进行处理,这就引出了我们今天的主题——Mybatis。
Mybatis相信熟悉Java的朋友们都不会陌生,是一个半自动映射ORM框架,使用的非常广泛,话不多说,我们来探究一下它的工作原理:
Mybtis主要的工作流程分为两个阶段:构建阶段和执行阶段
1 构建阶段:初始化来准备运行环境,根据xml文件构建Configuration对象,这一阶段的主要主要构建器如下:
SqlSessionFatcory:管理数据库会话、聚合Configuration对象
SqlSessionFactoryBuilder: SqlSessionFatcory的构造器,可以读取配置文件构建SqlSessionFactory
Configuration:存储Mybatis运行时的所有配置
BaseBuilder:构造器基类,用来处理Configuration、typeAlias与typeHandler对象
XMLConfigBuilder:解析XML文件构建的Configuration对象
XMLMapperBuilder:解析XML定义的SQL映射配置对象集合
public SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}
这是一段常用的获取SqlSessionFactory对象的代码,它和之前描述的一样,通过使用SqlSessionFactoryBuilder构造器来构建SqlSessionFactory对象,实际上这一段代码包含的逻辑还是比较繁琐的:
//该类包含很多重载的build方法,下面我只列出了主要调用的方法
public class SqlSessionFactoryBuilder {
//样例代码调用的构造器,会调用另一个构造方法
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
//被调用的构造方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//创建解析XML文件的XMLConfiguBuilder对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//将构建好的config回传给build方法
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类的实例,使用这个实例解析XML配置文件,下面我们来看如何构建XMLConfigBuilder对象:
//初始化一个XpathParser对象,该类是Mybatis对JDK自带DOM工具的封装
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()),
environment, props);
}
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(inputStream));
}
下一步我们看如何使用这个解析器解析XML配置文件,在这声明一下,因为源码的分析会涉及到很多的类,所有我会在代码上方标出类的全名;Config类的对象大部分构建都是在XMLConfigBuilder类实现的:
XMLConfigBuilder类
private boolean parsed;
private final XPathParser parser;
private String environment;
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
public Configuration parse() {
//避免重复解析
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
从配置文件的ConfigUration标签处开始解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
//开始解析工作
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties"));
//加载属性配置
Properties settings = settingsAsProperties(root.evalNode("settings"));
//加载vfs(我也不是很了解这个东西)
loadCustomVfs(settings);
loadCustomLogImpl(settings);
//加载别名处理器
typeAliasesElement(root.evalNode("typeAliases"));
//加载自定义扩展点
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
//加载环境标志
environmentsElement(root.evalNode("environments"));
//加载数据库厂商标志
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//加载类型处理器
typeHandlerElement(root.evalNode("typeHandlers"));
//加载SQL Mapper,也是下文会继续讲解的重点
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
到这一步大家就应该了解XML文件是如何解析的,至于更具体的解析各个标签甚至子标签,我就不仔细讲了,一来非常繁琐,二来我自己也不是很熟悉这块,大家有兴趣的话可以阅读源码进行学习;下一步我们讲解SQl Mapper的初始化
XMLConfigBuilder类
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对象
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
//对自定义Mapper执行addMapper方法
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
XMLConfigBuilder在解析SQL Mapper配置时支持扫描包和指定类的方式,这两种方式都会反射Mybatis的SQL Mapper注释:
MapperRegistry类
public void addMappers(String packageName, Class<?> superType) {
//扫描包
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
将包内的类处理成MappedStatement
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
public <T> void addMapper(Class<T> type) {
//只处理接口,我们的方法都是写在接口中
if (type.isInterface()) {
//判断重复加载
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
//加载失败则清理Mapper
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
暂时就写到这了,关于XMLBuilder如何加载配置内容,使MapperRegistry将构建好的Mapper注册到configuratin对象中以后有时间会继续更新的。