一:简述:
本篇文章主要用于介绍关于mybatis配置解析的部分代码。
二:时序图
三:代码分析
因为构建SqlSessionFactory需要读取用户的配置来构建,所以解析配置文件必然是在构建SqlSessionFactory之前进行的,所以这部分源码的入口应该是在SqlSessionFactoryBuilder中的build()方法。
//开始对读取的配置文件进行解析
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
1.全局配置的解析
mybatis会一步一步解析全局配置文件的各个element,其中包括有别名的解析与注册,类型转化器的解析与注册,插件的注册,ObjectFactory的解析(在处理结果集的时候用于生成对应的实体)
//解析配置
private void parseConfiguration(XNode root) {
try {
//分步骤解析
//issue #117 read properties first
//1.properties
propertiesElement(root.evalNode("properties"));
//2.类型别名
typeAliasesElement(root.evalNode("typeAliases"));
//3.插件
pluginElement(root.evalNode("plugins"));
//4.对象工厂
objectFactoryElement(root.evalNode("objectFactory"));
//5.对象包装工厂
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//6.设置
settingsElement(root.evalNode("settings"));
// read it after objectFactory and objectWrapperFactory issue #631
//7.环境
environmentsElement(root.evalNode("environments"));
//8.databaseIdProvider
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//9.类型处理器
typeHandlerElement(root.evalNode("typeHandlers"));
//10.Mapper映射
mapperElement(root.evalNode("mappers"));
在mapperElement(root.evalNode(“mappers”))中,mybatis会根据所配置的扫描包或路径对mapper接口进行解析保存在MapperRegistry的knownMappers中。
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
//10.4自动扫描包下所有映射器
String mapperPackage = child.getStringAttribute("name");
//获取包下的mapper接口保存起来
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) {
//10.1使用类路径
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
//映射器比较复杂,调用XMLMapperBuilder
//注意在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
//10.2使用绝对url路径
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) {
//10.3使用java类名
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.");
}
}
}
}
}
这段代码比较长,重点关注configuration.addMapper()方法,它会调用mapperRegistry.addMapper(type),将mapper接口保存起来,存在一个叫做knownMappers的map中,其中key是Mapper接口的Class,value是一个生成对应Mapper代理类的工厂.
(这点很关键,后面我们无论是通过sqlSession.getMapper()或者spring容器中获取的mapper都是通过这个代理工厂生成的代理类)
public <T> void addMapper(Class<T> type) {
//mapper必须是接口!才会添加
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<T>(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从mybatis中删除
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
2.解析mapper映射文件
这部分主要是解析mapper.xml中的标签,建立对应的sql与方法之间的关系并且保存起来(MappedStatement对象保存了这个信息)。
首先解析xml最外部的标签,包括namespace,cache等
private void configurationElement(XNode context) {
try {
//1.配置namespace
String namespace = context.getStringAttribute("namespace");
if (namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
//2.配置cache-ref
cacheRefElement(context.evalNode("cache-ref"));
//3.配置cache 二级缓存
cacheElement(context.evalNode("cache"));
//4.配置parameterMap
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//5.配置resultMap
resultMapElements(context.evalNodes("/mapper/resultMap"));
//6.配置sql(定义可重用的 SQL 代码段)
sqlElement(context.evalNodes("/mapper/sql"));
//7.配置select|insert|update|delete TODO
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
接下来解析select,update,insert,delete等标签
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) {
//构建所有语句,一个mapper下可以有很多select
//语句比较复杂,核心都在这里面,所以调用XMLStatementBuilder
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//核心XMLStatementBuilder.parseStatementNode
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
//如果出现SQL语句不完整,把它记下来,塞到configuration去
configuration.addIncompleteStatement(statementParser);
}
}
具体的解析代码在XMLStatementBuilder类中的parseStatementNode()方法中,代码比较长就不贴了,有兴趣的同学可以自己去源码中看看,这段代码主要就是将sql解析出来并且将namespace,sql,相对应的标签信息(比如resultMap,resultType,sqlSource等等),利用一个超级大的构造器构造出一个MappedStatement对象,并且将这个对象保存到configuration中的mappedStatements,mappedStatements是一个map,key是这个mappedStatement的id,即namespace加方法名,value就是mappedStatement。
四:总结
这部分内容相当于mybatis的一个初始化,将最重要的两个配置文件进行解析,并且保存。
主要涉及的一些关键对象
SqlSessionFactoryBuilder:用来构建SqlSessionFactory的
SqlSessionFactory:用于生成sqlSession对象的
Configuration:用于保存解析出来的配置文件信息的,包括全局配置,mapper映射关系,别名,类型转化器等等。
MapppedStatement:用于保存sql与对应方法的信息的。