This is the 17th day of my participation in the August Text Challenge.More challenges in August
Sequence diagram
sequenceDiagram participant A as XMLConfigBuilder participant B as Configuration participant C as XMLMapperBuilder participant D as MapperRegistry participant E as MapperAnnotationBuilder A ->> A: The mapperElement Alt subtag is package A ->> B: addMappers B ->> D: addMappers D -> D: addMapper D ->> E: parse E ->> E: A ->> C: parse C ->> C: configurationElement C ->> C: configurationElement C ->> C: BindMapperForNamespace C ->> B: addMapper B ->> D: addMapper else Parse URL attributes A ->> C: Parse else parse class attributes A ->> B: addMapper B ->> D: addMapper D ->> E: parse end end
- The package configuration is similar to the class configuration execution logic, and only the package configuration code is followed here
- Resource configuration and url configuration of the way to execute the logic, here only with the configuration of the resource code
Steps,
mapperElement
methods
/** * Resolves the mappers node, for example: * <mappers> * <mapper resource="resources/xml/PurchaseMapper.xml"/> * <package name="org.apache.ibatis.z_run.mapper"/> * </mappers> *@paramParent mappers node *@throws Exception
*/
private void mapperElement(XNode parent) throws Exception {
if(parent ! =null) {
for (XNode child : parent.getChildren()) {
// Process the child nodes of Mappers, namely the mapper node or the package node
if ("package".equals(child.getName())) { / / package node
// Retrieve the package path
String mapperPackage = child.getStringAttribute("name");
// Take all Mapper interfaces under the package, parse the corresponding XML, and add them all to Mappers
configuration.addMappers(mapperPackage);
} else {
// Only one of the resource, URL, and class attributes takes effect
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);
// Get the input stream of the file
InputStream inputStream = Resources.getResourceAsStream(resource);
// Use XMLMapperBuilder to parse the mapping file
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null&& url ! =null && mapperClass == null) {
ErrorContext.instance().resource(url);
// Get input streams from the network
InputStream inputStream = Resources.getUrlAsStream(url);
// Use XMLMapperBuilder to parse the mapping file
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null&& mapperClass ! =null) {
// Load the configured mapping interface, get the corresponding XML, and add it to MappersClass<? > 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.");
}
}
}
}
}
Copy the code
How to configure packages
Configuration#addMappers
// Map the registry
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public void addMappers(String packageName) {
/** * The mapperRegistry is created in the Configuration object, and the current Configuration object is passed in *. Therefore, all subsequent operations are performed on the same Configuration object, i.e. the current object */
mapperRegistry.addMappers(packageName);
}
Copy the code
MapperRegistry#addMappers
/ * * *@since3.2.2 * /
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
/ * * *@since3.2.2 * /
/** * All files whose parent is Object *@paramPackageName package name *@param superType Object
*/
public void addMappers(String packageName, Class
superType) {
// Get the class file in the packageResolverUtil<Class<? >> resolverUtil =new ResolverUtil<>();
resolverUtil.find(newResolverUtil.IsA(superType), packageName); Set<Class<? extends Class<? >>> mapperSet = resolverUtil.getClasses();// Parse the interface and put the parse results into mappedStatements in Configuration
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
public <T> void addMapper(Class<T> type) {
// Only Class objects of interface type are handled
if (type.isInterface()) {
// Check whether the current interface has been resolved
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// Put the current interface into knownMappers to prevent repeated parsing
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);
// Start parsing
parser.parse();
loadCompleted = true;
} finally {
// After parsing fails, the current interface is removed from knownMappers
if(! loadCompleted) { knownMappers.remove(type); }}}}Copy the code
MapperAnnotationBuilder#parse
/** * Parse the interface document with annotations */
public void parse(a) {
String resource = type.toString();
// Prevent duplicate analysis
if(! configuration.isResourceLoaded(resource)) {// Find if there is XML configuration in the resource path corresponding to the class name. If there is XML configuration in the resource path, parse it directly, so that annotations and XML can be mixed
loadXmlResource();
// Record the resource path
configuration.addLoadedResource(resource);
// Set the namespace
assistant.setCurrentNamespace(type.getName());
// Handle the cache
// Parse CacheNamespace annotations
parseCache();
// Parse the CacheNamespaceRef annotation
parseCacheRef();
// Get all the methods in the interface
Method[] methods = type.getMethods();
// traversal parsing
for (Method method : methods) {
try {
// Exclude bridge methods. Bridge methods are automatically introduced by the compiler to match the type erasure of generics. They are not user-written methods, so exclude them.
// issue #237
if(! method.isBridge()) {// Parse the methodparseStatement(method); }}catch (IncompleteElementException e) {
// Exception methods are temporarily stored
configuration.addIncompleteMethod(new MethodResolver(this, method)); }}}// How to handle exceptions
parsePendingMethods();
}
Copy the code
MapperAnnotationBuilder#parseStatement
- Only annotations can be parsed, not XML
/** * parses the method, mainly parsing the annotation information on the method *@param method
*/
void parseStatement(Method method) {
// Use the word method to get the parameter typeClass<? > parameterTypeClass = getParameterType(method);// Get the scripting language channel for the method
LanguageDriver languageDriver = getLanguageDriver(method);
// Get the SqlSource via annotations
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if(sqlSource ! =null) {
// Get the configuration information that may exist on the method, specified by the @options annotation
Options options = method.getAnnotation(Options.class);
final String mappedStatementId = type.getName() + "." + method.getName();
Integer fetchSize = null;
Integer timeout = null;
StatementType statementType = StatementType.PREPARED;
ResultSetType resultSetType = configuration.getDefaultResultSetType();
SqlCommandType sqlCommandType = getSqlCommandType(method);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
booleanflushCache = ! isSelect;boolean useCache = isSelect;
KeyGenerator keyGenerator;
String keyProperty = null;
String keyColumn = null;
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
// first check for SelectKey annotation - that overrides everything else
SelectKey selectKey = method.getAnnotation(SelectKey.class);
if(selectKey ! =null) {
keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
keyProperty = selectKey.keyProperty();
} else if (options == null) {
keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
} else{ keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; keyProperty = options.keyProperty(); keyColumn = options.keyColumn(); }}else {
keyGenerator = NoKeyGenerator.INSTANCE;
}
if(options ! =null) {
if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
flushCache = true;
} else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
flushCache = false;
}
useCache = options.useCache();
fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
timeout = options.timeout() > -1 ? options.timeout() : null;
statementType = options.statementType();
if(options.resultSetType() ! = ResultSetType.DEFAULT) { resultSetType = options.resultSetType(); } } String resultMapId =null;
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
if(resultMapAnnotation ! =null) {
resultMapId = String.join(",", resultMapAnnotation.value());
} else if (isSelect) {
resultMapId = parseResultMap(method);
}
// Save the obtained information to configuration
assistant.addMappedStatement(
mappedStatementId,
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
// ParameterMapID
null,
parameterTypeClass,
resultMapId,
getReturnType(method),
resultSetType,
flushCache,
useCache,
// TODO gcode issue #577
false,
keyGenerator,
keyProperty,
keyColumn,
// DatabaseID
null,
languageDriver,
// ResultSetsoptions ! =null ? nullOrEmpty(options.resultSets()) : null); }}Copy the code
configurationresource
The way of
XMLMapperBuilder#parse
/** * parse the mapping file */
public void parse(a) {
// Whether the node has been parsed
if(! configuration.isResourceLoaded(resource)) {// Process the mapper node
configurationElement(parser.evalNode("/mapper"));
// Join the parsed list to prevent repeated parsing
configuration.addLoadedResource(resource);
// Register Mapper with Configuration
bindMapperForNamespace();
}
,
, SQL statements that fail
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
Copy the code
XMLMapperBuilder#configurationElement
- Parsing only XML
/** * Parse the mapping file's lower node *@paramContext mapping file root */
private void configurationElement(XNode context) {
try {
// Reads the namespace of the current mapping file
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
// Resolve other configuration nodes in the mapping file
// Parse the cache label
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
// Parse the parameter mapping
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// Parse the result set mapping
resultMapElements(context.evalNodes("/mapper/resultMap"));
// Parse SQL tags
sqlElement(context.evalNodes("/mapper/sql"));
// Process individual database operation statements
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); }}Copy the code
XMLMapperBuilder#buildStatementFromContext
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) {
// Create a parse object
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
/ / parsing
statementParser.parseStatementNode();
} catch(IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); }}}Copy the code
XMLStatementBuilder#parseStatementNode
/** * parse select, INSERT, update, delete nodes */
public void parseStatementNode(a) {
// Read the current node ID and databaseId
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
// Verify that id matches databaseId. MyBatis allows multiple database configurations, so some statements only apply to specific databases
if(! databaseIdMatchesCurrent(id, databaseId,this.requiredDatabaseId)) {
return;
}
// Read the node name
String nodeName = context.getNode().getNodeName();
// Read and determine the statement type SqlCommandType
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);
// Process the Include node in the statement
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parameter type
String parameterType = context.getStringAttribute("parameterType");
/** * 1. Find the corresponding class in the alias mapper according to the attribute value * 2. If not in the alias mapper, the Class object */ corresponding to the Class name is createdClass<? > parameterTypeClass = resolveClass(parameterType);// Statement type. The default value is XMLLanguageDriver
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
/ / processing SelectKey node where KeyGenerator will be added to the Configuration. The keyGenerators
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// At this point, the
and
nodes have been parsed and deleted to begin SQL parsing
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
// Determine if there is a resolved KeyGenerator
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
// Automatic key generation is used globally or whenever automatic key generation is enabled in this statement
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// Create the SqlSource based on the LanguageDriver obtained
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
// Read the configuration properties
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
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");
// Create the MappedStatement object with the help of the MapperBuilderAssistant and write it to the Configuration
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
Copy the code
XMLMapperBuilder#bindMapperForNamespace
private void bindMapperForNamespace(a) {
// Get the current namespace
String namespace = builderAssistant.getCurrentNamespace();
if(namespace ! =null) { Class<? > boundType =null;
try {
// Get the corresponding Class object according to the namespace
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if(boundType ! =null) {
if(! configuration.hasMapper(boundType)) {// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
// The current XML file has been parsed
configuration.addLoadedResource("namespace:" + namespace);
// Set the Class object corresponding to the current XML to the Configuration, and parse whether the interface corresponding to the current XML contains annotationsconfiguration.addMapper(boundType); }}}}Copy the code
Configuration#addMapper
public <T> void addMapper(Class<T> type) {
// Add the current interface to the mapping
mapperRegistry.addMapper(type);
}
Copy the code
MapperRegistry#addMapper
public <T> void addMapper(Class<T> type) {
// Only Class objects of interface type are handled
if (type.isInterface()) {
// Check whether the current interface has been resolved
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
/** * 1. Put the current interface into knownMappers to prevent repeated parsing * 2. After wrapping the interface with MapperProxyFactory, place it in the mapping */
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.
// A parser that parses annotations
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// Start parsing
parser.parse();
loadCompleted = true;
} finally {
// After parsing fails, the current interface is removed from knownMappers
if(! loadCompleted) { knownMappers.remove(type); }}}}Copy the code
This is how mappers tag parsing goes, and as you can see, configuration using package name and class only resolves annotation-based SQLSources, and configuration using URL and Resource only resolves XML-based SQLsources. For parameter mapping, result set mapping, and SqlSource object parsing, see next breakdown!