mybatis作为一个orm框架,在几乎所有的公司都使用这一款orm框架,因为它非常的灵活易用,MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
mybatis使用通常会有一个配置文件,即mybatis-config.xml,文件中包含了mybatis的所有的配置信息,比如数据库连接信息,别名,插件等等一系列的相关信息,来看看mybatis是如何来解析这些信息。首先回忆一下,mybatis的使用过程。由SqlSessionFactoryBulider去build一个SqlSessionFactory,在右SqlSessionFactory去创建一个SqlSession,SqlSession可以去执行sql语句,也可以去获取Mapper的对象,然后让Mapper对象去执行然后获取结果。
首先SqlSessionFactoryBulider定义了一系列的build方法,目的就是为了不同的加载配置文件的方式或者配置信息,而创建出对象。字符流的方法与字节流的方法类似,重点看以下两个方法。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//解析配置文件
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//调用重载的方法,创建出SqlSessionFactory
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
}
}
}
public SqlSessionFactory build(Configuration config) {
//创建SqlSessionFactory对象
return new DefaultSqlSessionFactory(config);
}
先创建XMLConfigBuilder对象,并且将传入相关的配置信息,然后调用parser.parse()创建Configuartion对象,Configuartion意思就是配置,也就是这个类的所有属性所存的就是mybatis相关配置信息。
XMLConfigBuilder的构造器的第一件事就是先进行验证,验证这个文件的内容是不是符合mybatis的文件,所以先进行验证,即根据mybatis的dtd文件进行验证。
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
//new XPathParser(inputStream, true, props, new XMLMapperEntityResolver())先进行验证
//XMLMapperEntityResolver对象就是mybatis的dtd文件,即验证规则
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//开始解析
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
//将configuration中设置Properties对象,因为bulid方法可以传递Properties 对象,
//注Properties 一般是设置数据源的相关信息
this.configuration.setVariables(props);
//这个属性标记文件是否已经解析过,配置文件只解析一次
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
Configuration对象的创建
typeAliasRegistry:类型别名注册器,里面储存着所有的类型与别名的对应关系,这里首先加载了mybatis中默认的,也就是在配置文件中你所填的那些配置信息的与它们的实现类的对应。
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
第一步验证文件,以及加载一些默认信息到Configuration的任务就已经完成了,下一步就是加载配置文件的信息,回到SqlSessionFactoryBulider的bulid方法,在这里XMLConfigBuilder调用了parse进行配置文件的加载。然后先判断文件是否已经读取过,没有则解析内容,将所有的标签中的内容进行获取并且填充到Configuration中。由于内容都类似,后续就看看Properties的方法以及数据源的那个方法。
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 {
//解析properties标签中的内容,
propertiesElement(root.evalNode("properties"));
//设置mybatis的一些运行时的配置,比如懒加载等等之类
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
//解析别名,一般在实体类使用别名
typeAliasesElement(root.evalNode("typeAliases"));
//插件
pluginElement(root.evalNode("plugins"));
//MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。
objectFactoryElement(root.evalNode("objectFactory"));
// 为了统一不同数据库返回key值大小写不一致的问题,特自定义objectWrapperFactory来做统一的处理
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//反射模块
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
//配置环境,这里一般会有数据源信息,以及事务管理类
environmentsElement(root.evalNode("environments"));
//数据库id
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//类型转换器(几乎所有的类型转换都已经有),即jdbc的数据类型,java的那种类型
typeHandlerElement(root.evalNode("typeHandlers"));
//解析并加装mapper文件
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
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));
}
//之前创建XMLConfigBuilder的时候已经传递过一次Properties文件的内容,
//是因为bulid方法可以传递这个Properties对象,所以要将这些内容都合并到一起
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
//配置文件信息类覆盖之前的值
configuration.setVariables(defaults);
}
}
设置数据源以及事务管理类方法,创建Environment对象
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
//如果没标记使用其他的数据源,则使用默认节点的数据源
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
//获取数据库id
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
//根据传递的数据,然后根据别名注册器中对应的类名,然后反射创建对象
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
//根据你传递的类型,创建数据源,比如是否使用连接池,jndi数据源等
DataSource dataSource = dsFactory.getDataSource();
//创建Environment的Builder对象,Environment就是环境,存着数据库id,数据源,以及事务管理类
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
//设置配置信息类的环境。
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
至此,mybatis的配置文件的解析,首先先验证文件内容是否合标准,再此加载一些默认的别名到别名注册器上,创建Configuration对象,然后在解析文件的内容,新增别名,插件,创建数据源等等,并且将文件填入Configuration对象的属性中。最终创建出SqlSessionFactory对象。