上一篇我们介绍了Configuration类的成员变量,这一篇我们通过XMLMapperBuilder&XMLMapperBuilder类来看下对mybatis xml配置文件的解析。
一、XMLConfigBuilder基本介绍
1、基本结构&成员变量
public class XMLConfigBuilder extends BaseBuilder {
private boolean parsed;
private XPathParser parser;
private String environment;
可以看到其是有继承BaseBuilder的,然后:parsed参数是用来表明这个已经解析过了、parser就是包含解析的数据流,以及解析方法
1)、XPathParser
public class XPathParser {
private Document document;
private boolean validation;
private EntityResolver entityResolver;
private Properties variables;
private XPath xpath;
这个Document就是将mybatis的初始化xml的输入流变为一个Document对象。
二、XMLConfigBuilder的解析方法
1、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")就是获取configuration节点。
<configuration>
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
<typeAliases>
<typeAlias alias="Person" type="org.apache.ibatis.submitted.cglib_lazy_error.Person"/>
</typeAliases>
<environments default="test">
<environment id="test">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="UNPOOLED">
<property name="driver" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:mem:cglib_lazy_error"/>
<property name="username" value="sa"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/apache/ibatis/submitted/cglib_lazy_error/Person.xml"/>
</mappers>
</configuration>
2、parseConfiguration(XNode root)
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
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);
// 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);
}
}
这个方法就是对对应节点的具体分析了。
3、propertiesElement() [properties]节点
<properties resource="org/apache/ibatis/submitted/nested_query_cache/mapper.properties">
<property name="driver" value="org.apache.derby.jdbc.EmbeddedDriver"/>
</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));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
public Properties getChildrenAsProperties() {
Properties properties = new Properties();
for (XNode child : getChildren()) {
String name = child.getStringAttribute("name");
String value = child.getStringAttribute("value");
if (name != null && value != null) {
properties.setProperty(name, value);
}
}
return properties;
}
这个方法就是先通过getChildrenAsProperties方法将properties节点的内容转换为Properties。
然后获取该节点的resource、url属性的值,可以看到然后有一个检查表明此节点只能使用一个属性。然后再会通过对应的方法将这些要加载的内容再转换为properties添加到defaults中,再将properties添加到configuration中。
4、settingsAsProperties [settings]
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
for (Object key : props.keySet()) {
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
return props;
}
这个方法也是将该节点转换为对应的Properties对象表示。但这里有使用一个新的类MetaClass,这个类可是对其的入参Configuration.class这个类的反射内容表示,其有一个localReflectorFactory 反射工厂参数,到时候有时间的话可以用番外篇来具体梳理下。这里的hasSetter方法就是用来看Configuration这个类有没有这个key对应的属性的set方法。我们在梳理Configuration类的成员变量有将,Configuration中的一些整体设置属性就是读取配置文件中的,这里等于是对settings节点中配置的内容的校验,可能你写错写了一些mybatis不能识别的节点属性内容。
5、loadCustomVfs&loadCustomLogImpl
private void loadCustomVfs(Properties props) throws ClassNotFoundException {
String value = props.getProperty("vfsImpl");
if (value != null) {
String[] clazzes = value.split(",");
for (String clazz : clazzes) {
if (!clazz.isEmpty()) {
@SuppressWarnings("unchecked")
Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
configuration.setVfsImpl(vfsImpl);
}
}
}
}
private void loadCustomLogImpl(Properties props) {
Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
}
这两个就是获取对应在settings中配置的log实现&vfs实现(vfs现在还不是很了解,其是一种数据格式好像)。
6、typeAliasesElement [typeAliases]节点
<typeAliases>
<package name="org.apache.ibatis.submitted.nestedresulthandler_gh1551"/>
</typeAliases>
<typeAliases>
<typeAlias alias="Person" type="org.apache.ibatis.submitted.map_class_name_conflict.Person"/>
</typeAliases>
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
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);
}
}
}
}
}
这个就是别名注册可以看到其有两种,一种就是写包名,其会将包名下的类都注册到这里。还有一种就是写别名及对应的类,然后根据type写的内容将其对应的类获取再进行注册。
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 (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
}
typeAliases.put(key, value);
}
可以看到这里是会将alias变为小写的key。
7、pluginElement [plugins]节点
<plugins>
<plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
<property name="pluginProperty" value="100"/>
</plugin>
</plugins>
<plugins>
<plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
<property name="pluginProperty" value="100"/>
</plugin>
</plugins>
public class ExamplePlugin implements Interceptor {
private Properties properties;
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 interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
}
}
}
这个就是用来解析获取添加自定义mybatis的Interceptor接口的实现类的,可以看到其会添加的configuration中,同时通过这个案例及对应代码可以看到其会将写的[property]节点的内容设置到这里创建的interceptorInstance中。
8、objectFactoryElement[objectFactory]节点
private void objectFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties properties = context.getChildrenAsProperties();
ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
factory.setProperties(properties);
configuration.setObjectFactory(factory);
}
}
这里就是获取objectFactory节点的内容去创建一个对应的对象工厂,然后设置到configuration中,可以看到这里应该也能添加[property]节点。
9、objectWrapperFactoryElement [objectWrapperFactory]节点
private void objectWrapperFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance();
configuration.setObjectWrapperFactory(factory);
}
}
对象包装对象创建,同时设置到configuration中,这里是没有[property]节点对应的赋值的。
10、reflectorFactoryElement [reflectorFactoryElement]节点
这个同[objectWrapperFactory]节点节点的内容。
11、settingsElement(settings)
private void settingsElement(Properties props) {
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.setDefaultSqlProviderType(resolveClass(props.getProperty("defaultSqlProviderType")));
}
这个方法就是将前面解析的[settings]节点的内容都赋值到configuration中的变量中。
12、environmentsElement [environments]
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
private boolean isSpecifiedEnvironment(String id) {
if (environment == null) {
throw new BuilderException("No environment specified.");
} else if (id == null) {
throw new BuilderException("Environment requires an id attribute.");
} else if (environment.equals(id)) {
return true;
}
return false;
}
这个就是用来解析创建对应的Environment的。
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
TransactionFactory factory = (TransactionFactory) resolveClass(type).getDeclaredConstructor().newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a TransactionFactory.");
}
可以看到这里主要有三个信息,一个是Environment的id(String id = child.getStringAttribute(“id”)),然后再根据对应的信息创建事务工厂&数据源工厂,这里都是将[property]节点的内容转换为了Properties。
13、databaseIdProviderElement [databaseIdProvider]
这个是与databaseId相关的内容,我们在前一篇Configuration中有简答提到,但还没有使用过,就不具体分析了。
14、typeHandlerElement [typeHandlers]节点
<typeHandlers>
<typeHandler javaType="String" jdbcType="VARCHAR" handler="org.apache.ibatis.builder.CustomStringTypeHandler"/>
</typeHandlers>
public class CustomStringTypeHandler implements TypeHandler<String> {
......
}
这个就是用来注册你自己的类型转换器的。
private void typeHandlerElement(XNode parent) {
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);
}
}
}
}
}
可以看到这里还可以直接扫描整个包下的TypeHandler。
15、mapperElement [mappers]节点
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 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.");
}
}
}
}
}
这里我们先简单分析下其的逻辑,遍历其子节点
1)、这里首先是判断是不是以package的形式
<mappers>
<package name="org.apache.ibatis.submitted.autodiscover.mappers" />
</mappers>
这种类型是调用configuration.addMappers(mapperPackage)方法。
2)、剩下的是[mapper]节点
可以看到这种节点有3种属性:类路径、类名、绝对路径
<mapper resource="org/apache/ibatis/submitted/cglib_lazy_error/Person.xml"/>
<mapper class="org.apache.ibatis.submitted.array_result_type.Mapper" />
<mapper url="file:./src/test/java/org/apache/ibatis/builder/NestedBlogMapper.xml"/>
可以看到resource&url这种由于是要读取xml文件的,梳理会将这两种加载形成InputStream在通过XMLMapperBuilder去解析,class能直接创建类,则调用configuration.addMapper(mapperInterface)方法。
三、<mapper>节点解析的一些类&方法
前面我们梳理了对于<mapper>节点解析的整体逻辑,现在我们来分析其节点解析相关的具体方法
1、addMappers(String packageName) Configuration
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
protected MapperRegistry mapperRegistry = new MapperRegistry(this);
可以看到这个方法是再调用mapperRegistry的addMappers方法。
1)、addMappers(String packageName) MapperRegistry
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
这里最终是通过ResolverUtil类来获取在该包下的所有类,再遍历进入到addMapper(mapperClass)。
2、addMapper(Class<T> type)
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<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 {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
public class MapperRegistry {
private Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
可以看到这里是先判断是不是接口,再通过hasMapper判断这个类有没有被注册添加到knownMappers,没有再添加到knownMappers中,同时通过Class&Configuration去构建MapperAnnotationBuilder。
public class MapperAnnotationBuilder {
private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<Class<? extends Annotation>>();
private final Set<Class<? extends Annotation>> sqlProviderAnnotationTypes = new HashSet<Class<? extends Annotation>>();
private Configuration configuration;
private MapperBuilderAssistant assistant;
private Class<?> type;
public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
String resource = type.getName().replace('.', '/') + ".java (best guess)";
this.assistant = new MapperBuilderAssistant(configuration, resource);
this.configuration = configuration;
this.type = type;
sqlAnnotationTypes.add(Select.class);
sqlAnnotationTypes.add(Insert.class);
sqlAnnotationTypes.add(Update.class);
sqlAnnotationTypes.add(Delete.class);
sqlProviderAnnotationTypes.add(SelectProvider.class);
sqlProviderAnnotationTypes.add(InsertProvider.class);
sqlProviderAnnotationTypes.add(UpdateProvider.class);
sqlProviderAnnotationTypes.add(DeleteProvider.class);
}
resource就是这个类对应的java文件所在的位置,然后这里的两种注解sqlAnnotationTypes&sqlProviderAnnotationTypes我们再上一篇有梳理,这里就不再赘叙了,不过下面还会提到,这个时候将这两个list添加对应的注解。之后再调用MapperAnnotationBuilder的parse()方法进行对应的解析。
1)、parse() MapperAnnotationBuilder
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
这里先是判断这个resource是否已经加载了。然后是通过loadXmlResource()方法去加载这个Mapper接口所对应的xml文件,去解析这个xml文件的信息。之后再通过parseStatement(method)方法去处理这个Class的方法。
2)、loadXmlResource() MapperAnnotationBuilder
private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e) {
// ignore, resource is not required
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
这个方法就是去加载这个Mapper接口对应的xml文件,将去转换为流去构建XMLMapperBuilder,再调用parse()方法去解析,这个方法我们到下面再梳理。需要注意的是,可以不需要这个xml文件 (// ignore, resource is not required)。
3)、parseStatement(Method method) MapperAnnotationBuilder
void parseStatement(Method method) {
Class<?> parameterTypeClass = getParameterType(method);
LanguageDriver languageDriver = getLanguageDriver(method);
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
Options options = method.getAnnotation(Options.class);
final String mappedStatementId = type.getName() + "." + method.getName();
......
StatementType statementType = StatementType.PREPARED;
ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
SqlCommandType sqlCommandType = getSqlCommandType(method);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = !isSelect;
boolean useCache = isSelect;
......
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
SelectKey selectKey = method.getAnnotation(SelectKey.class);
......
}
} else {
keyGenerator = new NoKeyGenerator();
}
String resultMapId = null;
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
......
assistant.addMappedStatement(mappedStatementId,......);
}
}
public MappedStatement addMappedStatement(
String id,SqlSource sqlSource,StatementType statementType,SqlCommandType sqlCommandType,Integer fetchSize,Integer timeout,String parameterMap,Class<?> parameterType,String resultMap,Class<?> resultType,ResultSetType resultSetType,boolean flushCache,boolean useCache,boolean resultOrdered,KeyGenerator keyGenerator,String keyProperty,String keyColumn,String databaseId,LanguageDriver lang,String resultSets) {
.......
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
public void addMappedStatement(MappedStatement ms) { mappedStatements.put(ms.getId(), ms);}
这个parseStatement(Method method)就是来解析Mpper接口中的方法的,再将围绕这个接口的的一些注解信息如@SelectKey、ResultMap、@Select、@Options等,及方法本身的信息去构建对应的MappedStatement,主要处理注解注入的sql相关的内容。
下面我们来具体分析下parseStatement(Method method)方法中的一些调用方法。
4)、getSqlSourceFromAnnotations
private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
try {
Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
if (sqlAnnotationType != null) {
if (sqlProviderAnnotationType != null) {
throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
}
Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
} else if (sqlProviderAnnotationType != null) {
Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation);
}
return null;
} catch (Exception e) {
throw new BuilderException("Could not find value method on SQL annotation. Cause: " + e, e);
}
}
private SqlSource buildSqlSourceFromStrings(String[] strings, Class<?> parameterTypeClass, LanguageDriver languageDriver) {
final StringBuilder sql = new StringBuilder();
for (String fragment : strings) {
sql.append(fragment);
sql.append(" ");
}
return languageDriver.createSqlSource(configuration, sql.toString(), parameterTypeClass);
}
private Class<? extends Annotation> getSqlAnnotationType(Method method) {
return chooseAnnotationType(method, sqlAnnotationTypes);
}
private Class<? extends Annotation> getSqlProviderAnnotationType(Method method) {
return chooseAnnotationType(method, sqlProviderAnnotationTypes);
}
private Class<? extends Annotation> chooseAnnotationType(Method method, Set<Class<? extends Annotation>> types) {
for (Class<? extends Annotation> type : types) {
Annotation annotation = method.getAnnotation(type);
if (annotation != null) {
return type;
}
}
return null;
}
可以看到这里是构建获取SqlSource(ProviderSqlSource),我们上一篇有提到有两种:sqlAnnotationTypes(@Select…) | sqlProviderAnnotationType(@SelectProvider) (在上面MapperAnnotationBuilder构建的时候添加到这两个list中),在这里也会判断不能两种注解都有,不然会报BindingException异常。
如果是@Select这种,可以看到其是通过buildSqlSourceFromStrings方法调用的languageDriver.createSqlSource去选择创建对应的SqlSource(RawSqlSource|DynamicSqlSource)。如果是@SelectProvider这种就直接创建ProviderSqlSource对象。
5)、getSqlCommandType
private SqlCommandType getSqlCommandType(Method method) {
Class<? extends Annotation> type = getSqlAnnotationType(method);
if (type == null) {
type = getSqlProviderAnnotationType(method);
if (type == null) {
return SqlCommandType.UNKNOWN;
}
if (type == SelectProvider.class) {
type = Select.class;
} else if (type == InsertProvider.class) {
type = Insert.class;
} else if (type == UpdateProvider.class) {
type = Update.class;
} else if (type == DeleteProvider.class) {
type = Delete.class;
}
}
return SqlCommandType.valueOf(type.getSimpleName().toUpperCase(Locale.ENGLISH));
}
这个方法是看当前是那种数据库操作创建对应的SqlCommandType。
2、parse() XMLMapperBuilder
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
......
}
现在我们来看下在前面提loadXmlResource() 时跳过的方法。
1)、configurationElement(XNode context)
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
这个方法就是解析整个<mapper>节点的内容。首先是获取命名空间的内容并进行为空校验。
2)、parameterMapElement(List<XNode> list)
private void parameterMapElement(List<XNode> list) {
for (XNode parameterMapNode : list) {
String id = parameterMapNode.getStringAttribute("id");
String type = parameterMapNode.getStringAttribute("type");
Class<?> parameterClass = resolveClass(type);
List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
List<ParameterMapping> parameterMappings = new ArrayList<>();
for (XNode parameterNode : parameterNodes) {
String property = parameterNode.getStringAttribute("property");
String javaType = parameterNode.getStringAttribute("javaType");
String jdbcType = parameterNode.getStringAttribute("jdbcType");
String resultMap = parameterNode.getStringAttribute("resultMap");
String mode = parameterNode.getStringAttribute("mode");
String typeHandler = parameterNode.getStringAttribute("typeHandler");
......
Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
parameterMappings.add(parameterMapping);
}
builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
}
}
public ParameterMap addParameterMap(String id, Class<?> parameterClass, List<ParameterMapping> parameterMappings) {
id = applyCurrentNamespace(id, false);
ParameterMap parameterMap = new ParameterMap.Builder(configuration, id, parameterClass, parameterMappings).build();
configuration.addParameterMap(parameterMap);
return parameterMap;
}
这里是提取参数映射,这个方法就是遍历提取所有的<parameterMap>节点,获取其的节点信息,将其添加到Configuration中的parameterMaps。
3)、resultMapElements(List<XNode> list)
这个与前面的parameterMapElement方法类似,提取返回属性的映射,设置到Configuration的resultMaps中。
4)、sqlElement(List<XNode> list)
private void sqlElement(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
sqlElement(list, configuration.getDatabaseId());
}
sqlElement(list, null);
}
private void sqlElement(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
id = builderAssistant.applyCurrentNamespace(id, false);
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
sqlFragments.put(id, context);
}
}
}
这里就是提取sql片段(看其看到其与dataSourceId有关联),这个sqlFragments遍历也是指向Configuration的,在其通过构造函数创建的时候传入。
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
super(configuration);
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
this.parser = parser;
this.sqlFragments = sqlFragments;
this.resource = resource;
}
5)、buildStatementFromContext(List<XNode> list)
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) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
这里最后是通过创建XMLStatementBuilder对象去解析。
6)、parseStatementNode() XMLStatementBuilder
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
......
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
这里其实与前面的MapperAnnotationBuilder类似,只是那里是解析Mapper接口中方法去构建MappedStatement,这里是直接读取节点中的内容去解析创建MappedStatement。
public class MapperBuilderAssistant extends BaseBuilder {
private String currentNamespace;
private final String resource;
private Cache currentCache;
private boolean unresolvedCacheRef; // issue #676
public MapperBuilderAssistant(Configuration configuration, String resource) {
super(configuration);
ErrorContext.instance().resource(resource);
this.resource = resource;
}
至此,我们就梳理了Mybatis对于Mapper接口的注解提取对应内容创建MappedStatement以及提取xml文件提取对应节点的内容创建
MappedStatement的过程,以及将这些内容设置到Configuration中。