How is Mapper associated with XMl in Mybatis
From the source to analyze, through Mybatis all know, must specify nameSpace Mapper fully qualified class name. So you can relate it. The implementation of Mapper must be dynamic Pitching, enhanced in the InvocationHandler. So here’s how you do it. Okay? Analysis of the design of many things, easy to go astray. I tried to get back to the point.
1. Parse XML files
The XML parsing here is a bit tedious, so if you parse it line by line, it’s a lot, so let’s pick the main line here. Then it will be divided into sections and topics for analysis.
Parse the total configuration file
If you start with the classic Mybatis create SqlSessionFactory, you will see the following code
Some comments inside the code, I read the source code to write, some write more outrageous. There were some notes of confusion when I looked at them earlier. And then I read it again. So it’s going to stay here.
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
propertiesElement(root.evalNode("properties")); // Parse the properties tag and place it in the Variables of Parser and config
Properties settings = settingsAsProperties(root.evalNode("settings"));// Load the setting TAB
loadCustomVfs(settings); // what is the VFS in lcnote? How do I know this
VFS (virtual File System) abstracts several apis to access resources on the file system; For example, in
// Parse mapperElement(root.evalNode("mappers")); If you specify package, you can get all the class files under the package path through the VFS.
// It will be added to mappe, similar to the classPathSacnner in Spring, which can specify filters.
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
// From here on, we parse the specific tag, new out the object, set the attribute under the tag,
Mybatis (mybatis) objectFactory (mybatis) objectFactory (mybatis) The objectFactory, plugins
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectFactory"));
reflectorFactoryElement(root.evalNode("objectFactory"));
// Set the setting tag
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
//lcnote
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: "+ e, e); }}Copy the code
See how to parse mapper files directly.
As you can see from this code, there are two types of tags that can be written under the Mappers tag. Package and Mapper tags. There are different parsing methods for both.
Parsing the package tag
Here is only a partial intercept of the source code. Will also be several source splicing in a piece, easy to see
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
}
// Here is the configuration.addMappers(mapperPackage) method
public void addMappers(String packageName) {
//mapperRegistry is a registry that registers a mapper and maintains a large number of all mapper objects.
mapperRegistry.addMappers(packageName);
}
/ / here is mapperRegistry. AddMappers (packageName);
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
// addMappers(packageName, Object.class); Method,
//packageName Indicates the path of the package to be scanned
//superType indicates that the class to be sought is a subclass of that class.
public void addMappers(String packageName, Class
superType)
{
//resolverUtil is a utility class that finds subclasses of the specified class in the specified package.ResolverUtil<Class<? >> resolverUtil =new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
// Find the appropriate Class and add it to mapper.Set<Class<? extends Class<? >>> mapperSet = resolverUtil.getClasses();for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
//resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
// Scan all classes under the given package, including those under the subpath. Call the Test method to match the matched class, and call getClasses to retrieve the matched class.
public ResolverUtil<T> find(Test test, String packageName) {
String path = getPackagePath(packageName);
try {
List<String> children = VFS.getInstance().list(path);
for (String child : children) {
if (child.endsWith(".class")) {
// Load the class object and call the static inner class IsA (which implements the Test interface) in ResolverUtil for matching.addIfMatching(test, child); }}}catch (IOException ioe) {
log.error("Could not read package: " + packageName, ioe);
}
return this;
}
//addIfMatching(test, child);
protected void addIfMatching(Test test, String fqn) {
try {
String externalName = fqn.substring(0, fqn.indexOf('. ')).replace('/'.'. ');
ClassLoader loader = getClassLoader();
if (log.isDebugEnabled()) {
log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]"); } Class<? > type = loader.loadClass(externalName);if(test.matches(type)) { matches.add((Class<T>) type); }}catch (Throwable t) {
log.warn("Could not examine class '" + fqn + "'" + " due to a "
+ t.getClass().getName() + " with message: "+ t.getMessage()); }}/ / * * * * * * * * * * * * * * * * * * * * * * * * * * * * on * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// public
void addMapper(Class
type) to load the appropriate Class instantiation into mapperRegistry.
// And this method is in mapperRegistry.
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {Mapper can be registered only once
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// Use mapper new to create MapperProxyFactory and place it under knownMappers
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.
// It is important to add the type before parsing, so he will automatically try binding to parse mapper. If the type knows, it's okay,
// Mapper annotates mapper.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if(! loadCompleted) { knownMappers.remove(type); }}}}Copy the code
There are several important classes that appear in this
- MapperRegistry: A registry that holds all mapper files.
- Configuration: The Configuration object reported an error in all data available during Mybatis runtime.
- MapperProxyFactory: creates a proxy mapper factory. There is nothing special about this, it calls the method of creating proxy objects to create objects.
- MapperAnnotationBuilder: Parses annotations in mapper. These annotations do the same thing as XMl, but
Not recommended
.
Parse mapper labels
When I looked at the source code, I was like, wow, this works like this, this framework has this functionality.
{
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);
// The loading operation of resource and URL is always the same, but the source of resource is different.
// Load the resource, parse the mapper file, and build the mapperStatement object.
try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();//lcnote parsing is the same as configuration file parsing. Both build the XMLMapperBuilder and then call the parse method}}else if (resource == null&& url ! =null && mapperClass == null) {
ErrorContext.instance().resource(url);
try(InputStream inputStream = Resources.getUrlAsStream(url)){
XMLMapperBuilder mapperParser = newXMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); }}else if (resource == null && url == null&& mapperClass ! =null) {
// There is nothing special here, just the process of parsing the package tag to get the mapper,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."); }}Copy the code
As you can see, three attributes, resource, URL, and class, are supported, and the load order is resource first, URL, and class, and all three cannot be specified simultaneously.
Resource and URL loading operations are the same, that is, the source of resource is different. The loading of the class is the same as the parsing of the package tag to get the mapper, XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, Configuration, resource, configuration.getSqlFragments()); began
Build XMLMapperBuilder
BaseBuilder is a basic class in Mybatis. A lot of analysis is inherited with him, just began to do analysis.
Just a little bit more, MapperBuilderAssistant is really a utility class, so take a look at its construction
public class MapperBuilderAssistant extends BaseBuilder {
private String currentNamespace; // The nameSpace currently parsed
private final String resource; // The resource file of the current nameSpace
private Cache currentCache; // The current cache, corresponding to the cache tag in the mapper tag.
private boolean unresolvedCacheRef; // issue #676
}
Copy the code
This class corresponds to everything that is generated when a Mapper file is parsed. Such as resultMap, SQL, SELECT, Update, and so on. These are related things. Will be added to the BaseBuilder through this object.
Inherits from BaseBuilder, XMLMapperBuilder is primarily used to parse mapper tags in mappers in configuration files.
// Look at the constructor class
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
configuration, resource, sqlFragments);
}
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;
}
// the super constructor
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();// Obtain the contents of typeAliases tags from the configuration file
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();Fetch The typeHandlers from the configuration file} there is nothing to say about the constructor, but the following Parse methodCopy the code
Several important classes appear above
- XMLMapperEntityResolver: An entity resolver for xmlMapper. Inheritance and
EntityResolver
. It is an object in the org.xml.sax package. - BaseBuilder: The base class for all XML parsing.
Call the parse method of XMLMapperBuilder
public void parse(a) {
// The configuration file contains the loaded resource set
if(! configuration.isResourceLoaded(resource)) {// Go to a set in configuration
// This is the key, the key is to parse the mapper tag
configurationElement(parser.evalNode("/mapper"));// Parse the mapper tag
configuration.addLoadedResource(resource);// Add to the already loaded collection
bindMapperForNamespace(); // Try to load the configuration file through nameSpace.
// Note that the nameSpace does not have to be consistent with the Mapper interface.
}
// The following operation is also interesting.
/ / to parse the XML, if an error (IncompleteElementException) won't be thrown immediately, but the error will be cached, in the above all after parsing is complete, in give it a try.
//
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
Copy the code
The main thing here is to parse the Mapper tag and try to load the corresponding Mapper through nameSpace. If it is loaded, the MapperRegistry above is called to register the Mapper there.
The parse operation is similar to the previous one for the Configuration label. The parent label is parsed first and then the child label is parsed. Now let’s see how it’s resolved. Okay
private void configurationElement(XNode context) {
try {
/ / namespace
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"));// Parse each tag element
// Parse the cache label
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
/ / parse SQL
sqlElement(context.evalNodes("/mapper/sql"));
/ / waring here is very important, the real start parsing the select | insert | update | delete tags
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
instructions
Parse parameterMap. After parsing parameterMap above, build ParameterMapping and add it to builderAssistant. This is converted to ParameterMap in the builderAssistant and finally added to the Configuration object’s parameterMaps property. This Configuration is universal. And you can have multiple parameterMap tags in a mapper.
Build a cache object, add it to configuration, and assign currentCache in builderAssistant to the currentCache object. And a Mapper can have only one cache label.
Parsing a resultMap is a bit more complicated than the previous two because there are many tags underneath the resultMap.
for (XNode resultChild : resultChildren) { if ("constructor".equals(resultChild.getName())) { // This is very simple, through the constructor to set parameters processConstructorElement(resultChild, typeClass, resultMappings); } else if ("discriminator".equals(resultChild.getName())) { // The Discriminator object corresponding to lcNote. In reality, the Discriminator tag is not used. After a look, it seems that this tag can implement the function of Swtich Case and can also be paired with a resultMap // Let's do something fun. This has actually worked before discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings); } else { List<ResultFlag> flags = new ArrayList<>(); if ("id".equals(resultChild.getName())) { flags.add(ResultFlag.ID); } resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); }}Copy the code
- For the Constructor tag, build a ResultMapping object to add to the ResultMapping collection.
- I haven’t used discriminator tags, but I’ll do that later. build
Discriminator
Object.- For other tags, build a ResultMapping object to add to the ResultMapping collection.
This is a resultMap label below, in after parsing a resultMap, will assemble ResultMapResolver above related objects, invoke ResultMapResolver. Resolve (); Method to build a ResultMap object and place it in the Configuration. Therefore, ResultMap is the entity class corresponding to the ResultMap tag
Parse SQL tags. Put xNode and ID (the ID specified by the SQL tag) into sqlFragments on the XMLMapperBuilder object. SqlFragments are StrictMap extensions and HashMap that override the PUT and GET methods. This is mostly done with put and get. SqlFragments are used to store SQL fragments. Note that parsing these fragments does not deal with dynamic tags in SQL. Remember that dynamic labels are determined by parameters. Here’s just a simple way to store it. And SQL tags are multiple.
Parsing the select | insert | update | delete tags. That’s the point. In order to clear, still, will look at the source select | insert | update | delete tags are multiple. So this is loop parsing, and the following code is just parsing inside the body of the loop.
public void parseStatementNode(a) { String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); // You can specify the dataBase in mybatis, and you can also specify the databaseID in mybatis. // This is a judgment, if not currently applied, will not parse. if(! databaseIdMatchesCurrent(id, databaseId,this.requiredDatabaseId)) { return; } String nodeName = context.getNode().getNodeName(); // Determine the type of SQL by the name of the tag. SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; // If flushCache is not specified and the type is select, the default value is false. boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); // If useCache is not specified and the type is select, the default is true. boolean useCache = context.getBooleanAttribute("useCache", isSelect); // What does this label mean? This is still recommended to see the official documentation of Mybatis. boolean resultOrdered = context.getBooleanAttribute("resultOrdered".false); // Include Fragments before parsing // Want to parse the includ tag before parsing the SQL. You can tell by the name. This is what you do with
tags. // This explains why it is so easy to parse SQL tags. SQL tags will eventually be used in statements. // Dynamic SQL is also written in Statement, so include it before you actually start parsing tags. A piece of on // In the following parsing operation. A resolution XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); String parameterType = context.getStringAttribute("parameterType"); Class<? > parameterTypeClass = resolveClass(parameterType); String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); // Parse selectKey after includes and remove them. / / selectkey processSelectKeyNodes(id, parameterTypeClass, langDriver); // LCnote parse SQL selectKey is removed before parse // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) // useGeneratedKeys is required, and a cache is maintained. So let's see, id is the ID of the selcet tag KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); // eg:org.apache.ibatis.domain.mybatis.mapper.StudentMapper.listAllStudent! selectKey if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } // To be honest, I really don't know what the langDriver of Mybatis is SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); 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"); // Add the assembled MappedStatement to the builderAssistant // The map of the statement is maintained in the configuration file. The key is the ID of the namespace and mapper. 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
Calling the parse method of XMLMapperBuilder parses the mapper.xml file
- The first thing to know is that there must be a corresponding object in Mybatis for each XML element in the data range.
- Aliases and type handlers are definitely used when working with XML. These two are generic. We’ll see that later.
- Add parsed objects to configuration.
- It’s interesting to parse mapper files. For example, you can use ${} to refer to environment variables in SQL tags. Nested references are also supported.
- Indeed, when I looked at the source code, I found that there was this kind of usage, and some things had not been used. Check it out in detail later
- If an error is reported during parsing, it will not be thrown immediately. Instead, put it in a collection and try to parse it once all the XML files have been parsed.
Try binding a Mapper to a nameSpace
private void bindMapperForNamespace(a) {
// As mentioned earlier, builderAssistant corresponds to a utility class during mapper parsing. Get a namespace
String namespace = builderAssistant.getCurrentNamespace();
if(namespace ! =null) { Class<? > boundType =null;
try {
// Try to load by fully qualified class name
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
// ignore, bound type is not required
}
if(boundType ! =null && !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
configuration.addLoadedResource("namespace:"+ namespace); configuration.addMapper(boundType); }}}Copy the code
When parsing the XML file, it generates the corresponding tags and adds them to the Configuration. Then it loads the class class through a nameSpace. If the nameSpace is to correspond to the Mapper, it must be the same. That’s all right. Write whatever you want.
Add the loaded class to configuration. Configuration maintains a map, key is class, value is MapperProxyFactory. Attention should be paid to the configuration. AddMapper (boundType); Methods. And we’re going to look at this method.
The nameSpace in the Mapper tag is cached. The proxy object creation factory is also generated.
This method is in MapperRegistry.
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {Mapper can be registered only once
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.
// It is important to add the type before parsing, so he will automatically try binding to parse mapper. If the type knows, it's okay,
// Parse annotations in mapper.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if(! loadCompleted) { knownMappers.remove(type); }}}}Copy the code
The focus is on MapperProxyFactory
/ * * *@authorLasse Voss * LCnote Mapper proxy object creation factory */
public class MapperProxyFactory<T> {
// The proxy interface is required, i.e., mapper
private final Class<T> mapperInterface;
// Save the cache to avoid repeating objects at new.
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface(a) {
return mapperInterface;
}
public Map<Method, MapperMethodInvoker> getMethodCache(a) {
return methodCache;
}
// The new operation, nothing special, is simply created by calling the method that creates the proxy object.
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
returnnewInstance(mapperProxy); }}Copy the code
2. Create Mapper dynamic proxy
Add theInvocationHandler
What does it look like?
So the key points of MapperProxy, it’s a little bit too long, so I’m just going to pick the important ones, MapperProxy implements InvocationHandler, which is definitely a dynamic proxy. Mapper definitely uses reflection to associate XML files. The following method is not called when the new MapperProxyFactory is called, but when the MapperProxyFactory is called, the MapperProxyFactory is created.
At method invocation time, for the default method, the method is wrapped as MapperMethod, and then PlainMethodInvoker wraps the call.
/** waring, which is also important, is implemented when mapper calls InvocationHandler *@author Clinton Begin
* @author Eduardo Macarron
*/
public class MapperProxy<T> implements InvocationHandler.Serializable {
private static final long serialVersionUID = -4724728412955527868L;
private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
private static final Constructor<Lookup> lookupConstructor;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {// If it is an object method, call it directly
return method.invoke(this, args);
} else {
returncachedInvoker(method).invoke(proxy, method, args, sqlSession); }}catch (Throwable t) {
throwExceptionUtil.unwrapThrowable(t); }}private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return MapUtil.computeIfAbsent(methodCache, method, m -> {
if (m.isDefault()) {// If the method in the interface is default
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return newDefaultMethodInvoker(getMethodHandleJava9(method)); }}catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw newRuntimeException(e); }}else {
//lcnote MapperMethod represents a mapper method. It contains the dataId corresponding to the method, the corresponding SQL type, and the specific signature information of the method, including the method return value, param parameter. Mapkey annotations
return new PlainMethodInvoker(newMapperMethod(mapperInterface, method, sqlSession.getConfiguration())); }}); }catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null? re : cause; }}// mapper method call interface,
interface MapperMethodInvoker {
Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
}
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
// This is just a utility class that implements mapper calls.
public PlainMethodInvoker(MapperMethod mapperMethod) {
super(a);this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
returnmapperMethod.execute(sqlSession, args); }}}Copy the code
When is an object created?
Start with the getMapper method of the SqlSession. This is where the proxy object creation begins.
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// Call MapperProxyFactory to create a mapper instance.
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: "+ e, e); }}Copy the code
NewInstance creates a MapperProxy object. The MapperProxy object is described above.
As you can see, dynamic proxies are used to create a MapperProxy object.
What’s in MapperProxy?
SqlCommand
Represents the type of the associated Mapper and the SQL associated with this method
The MapperStatement is retrieved from the Configuration using the fully qualified class name of the Mapper. Assign id.
Determine what TYPE of SQL this method is.
MethodSignature
Represents the main information about a method.
private final boolean returnsMany;
private final boolean returnsMap; // The return value is not a map, as long as mapKey is not null, this is true
private final boolean returnsVoid; // Flag bit, whether no return value
private final boolean returnsCursor; // Whether a Cursor is returned
private final boolean returnsOptional;
private finalClass<? > returnType;// The actual type returned by this method, such as List
, is List
private final String mapKey; // Mapkey is the value of the mapkey annotation, and we can write an article about the function of this mapkey
private final Integer resultHandlerIndex;
private final Integer rowBoundsIndex;
private final ParamNameResolver paramNameResolver;
Copy the code
MapperMethod
This object contains the above two objects, and when executed, the execute method is called.
Mapper is associated with XML, and the next step is to execute SQL. The following steps must include finding MapperStatement by fully qualified class name, processing input parameters, and processing dynamic SQL. OGNL is called here to parse. Then execute and process the results. The rest of the process comes later