This article documents the myBatis framework alone, not the Spring integration.
The test method
This method is a test method. The first step is to read the configuration file as a byte stream, focusing on the build method in sqlsessionFactoryBuilder
@Test
public void test() throws IOException {
InputStream resources= Resources.getResourceAsStream("SqlMapperConfig.xml");
final SqlSessionFactory build =new SqlSessionFactoryBuilder().build(resources);
final PaymentChannelMapper mapper = build.openSession().getMapper(PaymentChannelMapper.class);
final List<PaymentChannel> all = mapper.findAll(5,5);
for (int i = 0; i < all.size(); i++) {
System.out.println(all.get(i));
}
}
Copy the code
Start the
The build method here is an overloaded build method that generates an XMLconfigBuilder object
public SqlSessionFactory build(InputStream inputStream, String environment, // Generate an XMLconfigBuilder object XMLconfigBuilder parser = new XMLconfigBuilder (inputStream, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } }Copy the code
XMLConfigBuilder constructor
This initializes the XMLConfigBuilder parameter, focusing on the XPathParser object
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
Copy the code
XPathParser object
Document is the root node of the XML file, Validation is enabled, variables corresponds to a set of key-value pairs under the node, and xpath parses the XML object. Then proceed to the parse method of XMLConfigBuilder.
Public class XPathParser {// Document is the root node of the XML file private final Document document; Private Boolean validation; Private EntityResolver EntityResolver; private Properties variables; //xpath parses XML objects. }Copy the code
XMLConfigBuilder. Parse method
The parser(XPathParser).evalNode method actually parses the tag into a Node object by xpath and returns it wrapped as an XNode object.
public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; }Copy the code
Enter the parseConifguration method
PluginElement pluginElement pluginElement pluginElement pluginElement pluginElement pluginElement pluginElement pluginElement pluginElement pluginElement pluginElement pluginElement pluginElement pluginElement pluginElement
private void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases")); // Interceptor plugins enhance parsing of pluginelements (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")); //mapper. XML parses mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); }}Copy the code
pluginElement
As you can see, traverse the child nodes of the root node, find the configured interceptor information, convert it into an interceptor object, set the parameter properties, and add them to the configuration.
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).newInstance(); interceptorInstance.setProperties(properties); configuration.addInterceptor(interceptorInstance); }}}Copy the code
configuration.addInterceptor
The interceptorChain finally puts the Interceptor object into the interceptor collection of the interceptorChain object. Here the plug-in is parsed
public void addInterceptor(Interceptor interceptor) { interceptorChain.addInterceptor(interceptor); } public class InterceptorChain { private final List<Interceptor> interceptors = new ArrayList<Interceptor>(); . public void addInterceptor(Interceptor interceptor) { interceptors.add(interceptor); }... }Copy the code
EnvitonmentsElement method
The first step is to configure the transactionManager transactionManager, the second step is to configure the datasource, and finally encapsulate it into an environment object.
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()); }}}}Copy the code
MapperElement method
The mapper. XML configuration file is parsed first to determine whether it is a scan type or a URL or a class path (here I’m using reource), then loaded as a byte input stream, and then an XMLMapperBuilder object is created
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."); } } } } }Copy the code
The XMLMapperBuilder object has the same properties as the XMLConfigBuilder described above
public class XMLMapperBuilder extends BaseBuilder { private final XPathParser parser; Private final MapperBuilderAssistant builderAssistant; < SQL > Private final Map<String, XNode> sqlFragments; private final String resource; }Copy the code
Parse method of xmlMapperBuilder Go to the parse method of xmlMapperBuilder to parse the mapper. XML file and check whether resources are loaded
public void parse() { if (! configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }Copy the code
ConfigurationElement method
As you can see here, we get the namespace value, throw an exception if it’s not set or empty, and then we set namesapce to the builderAssistant, which is a MapperBuilderAssistant object, The mapperbuider parsing file puts the information into Assistant for parsing. The next step is to parse and configure the other namespace caches, as well as the local namespace cache, followed by the map map of the request parameters, the map map of the parameters of the result set, and the parsing of the generic SQL statement. Finally, we begin parsing the SQL in each SQL tag, focusing here.
private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { 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")); // parameterMapElement(context.evalNodes("/mapper/parameterMap")); // resultMapElements(context.evalNodes("/mapper/resultMap")); // resultMapElements(context.evalNodes("/mapper/resultMap")); // sqlElement(context.evalNodes("/mapper/ SQL ")); / / all SQL statements buildStatementFromContext (context. EvalNodes (" select | insert | update | delete ")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); }}Copy the code
A method to perform parsing
Private void buildStatementFromContext (List < XNode > List) {/ / multiple source 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 {/ / analytical statementParser parseStatementNode (); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); }}}Copy the code
The logic is easy to understand when you start parsing the SQL configuration, I list the place where I feel hard to understand to explain XMLIncludeTransformer includeParser = new XMLIncludeTransformer (configuration, builderAssistant); And includeParser applyIncludes (context. GetNode ()); The two methods are processSelectKeyNodes(ID, parameterTypeClass, langDriver) that parse include tags; This method parses the SelectKet tag. SqlSource object creation, this is the main logic to generate dynamic SQL, enter a look.
public void parseStatementNode() { String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); if (! databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); String parameterMap = context.getStringAttribute("parameterMap"); String parameterType = context.getStringAttribute("parameterType"); Class<? > parameterTypeClass = resolveClass(parameterType); String resultMap = context.getStringAttribute("resultMap"); String resultType = context.getStringAttribute("resultType"); String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); Class<? > resultTypeClass = resolveClass(resultType); String resultSetType = context.getStringAttribute("resultSetType"); StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); 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); // Include Fragments before parsing XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes and remove them. processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); String resultSets = context.getStringAttribute("resultSets"); String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } 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
CreateSqlSource method
First you create an XMLScriptBuilder object, the XMLLanguageDriver class
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<? > parameterType) { XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType); return builder.parseScriptNode(); }Copy the code
XmlscriptBuilder object
Public class XMLScriptBuilder extends BaseBuilder {private final XNode context; SQL private Boolean isDynamic; Private final Class<? > parameterType; }Copy the code
The parseScripNode method of xmlscriptBuilder
public SqlSource parseScriptNode() {
List<SqlNode> contents = parseDynamicTags(context);
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = null;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
Copy the code
XmlscriptBuilder parseDynamicTags method
If it is static, it will generate a static sqlNode object. If it is dynamic, it will generate different sqlNode objects according to different labels and store them in the list collection.
List<SqlNode> parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<SqlNode>();
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlers(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
isDynamic = true;
}
}
return contents;
}
Copy the code
SQL > create RawSqlSource; SQL > create RawSqlSource; SQL > create RawSqlSource
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
Copy the code
sqlSource.getBoundSql
SqlSource getBoundSql (); sqlSource getBoundSql ();
GetBoundSql method of DynamicSqlSource
@Override public BoundSql getBoundSql(Object parameterObject) { DynamicContext context = new DynamicContext(configuration, parameterObject); rootSqlNode.apply(context); SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); Class<? > parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()); BoundSql boundSql = sqlSource.getBoundSql(parameterObject); for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) { boundSql.setAdditionalParameter(entry.getKey(), entry.getValue()); } return boundSql; }Copy the code
The getBoundSql method of RawSqlSource
public BoundSql getBoundSql(Object parameterObject) {
return sqlSource.getBoundSql(parameterObject);
}
Copy the code
Back to main flow
Select * from insert statement where primary key is required
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
Copy the code
When the addMappedStatement method is called, the parsed SQL is encapsulated as a MapperStatement object and saved
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
AddMappedStatement method
public MappedStatement addMappedStatement(...) {... id = applyCurrentNamespace(id, false); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)... ; ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id); if (statementParameterMap ! = null) { statementBuilder.parameterMap(statementParameterMap); } MappedStatement statement = statementBuilder.build(); configuration.addMappedStatement(statement); return statement; }Copy the code
The configuration. AddMappedStatement method Here you can see the final will be in the form of the key – value stored in the configuration of the map in the collection, The key is namespace.id value is mappedStatement, so the mapper. XML method does not support overloading.
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}
Copy the code
XmlMapperBuilder. Parse method
So once you’ve parsed it back here, there’s an important method called bindMapperForNamespace
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
...
}
Copy the code
BindMapperForNamespace method
This method calls configuration.addMapper and you can see that it stores the object proxy factory for Type into mapperRegister and into Map. Type is the name of the namespace passed in, which is the Mapper interface object.
private void bindMapperForNamespace() { String namespace = builderAssistant.getCurrentNamespace(); if (namespace ! = null) { Class<? > boundType = null; try { 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 configuration.addLoadedResource("namespace:" + namespace); configuration.addMapper(boundType); }}}} // Agent factory corresponding to the interface Private final Map<Class<? >, MapperProxyFactory<? >> knownMappers = new HashMap<Class<? >, MapperProxyFactory<? > > (); 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); }}}}Copy the code
At this point, the mybatis XML configuration file startup process is finished parsing, the next step is to execute the process.
perform
First through the SqlSessionFactoryBuilder is sqlsession openSession approach, then calls the sqlsession getMapper method. The argument passed in is the class for which the proxy object needs to be generated. You can see here what the getMapper method actually calls the MapperRegister method
GetMapper method
The proxy factory object corresponding to the object is identified from the Map collection, and the newInstance method of the factory object is called
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 { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); }}Copy the code
NewInstance method
MapperProxy is an interface that implements the InvocationHandler class. It determines the method. See the specific execution time.
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
Copy the code
Invoke the mapperProxy.invoke method
The invokeDefaultMethod method is invoked. If the method is Object, the invokeDefaultMethod method is invoked. If not, go to the cachedMapperMethod method. This step is mainly to get the MapperMethod method from the cache. If not, generate one and put it in the cache. Finally, the execute method of the MapperMethod method is called.
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
Copy the code
MapperMethod. The execute method
I’m going to tell you what type it is, the select in this case, and then I’m going to tell you whether I need to return a value, a resultHandler, and then I’m going to go to the corresponding execution method, where I’m going to return multiple objects which is the second if judgment.
public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } if (result == null && method.getReturnType().isPrimitive() && ! method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; }Copy the code
MapperMethod executeForMany method
I’m going to get the request parameters, and then I’m going to see if there’s paging, which I don’t have, and I’m going to go to the else logic and I’m going to do selectList
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { List<E> result; Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.<E>selectList(command.getName(), param, rowBounds); } else { result = sqlSession.<E>selectList(command.getName(), param); } // issue #510 Collections & arrays support if (! method.getReturnType().isAssignableFrom(result.getClass())) { if (method.getReturnType().isArray()) { return convertToArray(result); } else { return convertToDeclaredCollection(sqlSession.getConfiguration(), result); } } return result; }Copy the code
DefaultSqlSession selectList method
Get the corresponding MappedStatement here (which is the information for each tag parsed)
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}Copy the code
CachingExecutor. Query method
And then we execute the Query method, and we look at getBoundSql.
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
Copy the code
MappedStatement getBoundSql method
The BoundSql object is retrieved from the sqlSource, which is the DynamicSqlSource.
public BoundSql getBoundSql(Object parameterObject) {
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// check for nested result maps in parameter mappings (issue #30)
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
Copy the code
Boundsql object
SQL: stores SQL; ParameterMappings Records the parameter sequence. ParameterObject Specifies a request parameter. If multiple parameters are passed in, the parameter is an object. If multiple parameters are passed in, the parameter is a ParamMap. AdditionalParameters Parameter map; This metaObject makes it easy to get or set the value of an originalObject (an incoming object parameter)
Public class BoundSql {private final String SQL; // Record parameter sequence private final List<ParameterMapping> parameterMappings; ParameterObject Specifies a single parameter. If multiple parameters are passed in, the parameterObject is a ParamMap collection. // private final map <String, Object> additionalParameters; Private Final MetaObject metaParameters; private final MetaObject metaParameters;Copy the code
DynamicSqlSource getBoundSql method
First create DynamicContext object, store some parameter SQL information, and then execute SqlNode’s Apply method
public BoundSql getBoundSql(Object parameterObject) { DynamicContext context = new DynamicContext(configuration, parameterObject); rootSqlNode.apply(context); SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); Class<? > parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()); BoundSql boundSql = sqlSource.getBoundSql(parameterObject); for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) { boundSql.setAdditionalParameter(entry.getKey(), entry.getValue()); } return boundSql; }Copy the code
SqlNode.apply / rootSqlNode.apply
This iterates through the stored sqlNode objects and executes the apply method
SqlNode collection
You can see that there are three SQLNodes in this SQL
StaticTextSqlNode.apply
The first StaticTextSqlNode is a simple String concatenation, followed by IfsqlNode DynamicContext.appendSQL
public boolean apply(DynamicContext context) {
context.appendSql(text);
return true;
}
Copy the code
IfSqlNode.apply
ExpressionEvaluator evaluateBoolean method Here is through OgnlCache. GetValue method judging conditions and is in line with the request and returns the incoming parameters, if meet the requirements will go MixedSqlNode. Apply method, If not, it will not operate. Mixedsqlnode. apply concatenates the SQL from the qualifying tag.
public boolean apply(DynamicContext context) {
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
Copy the code
MixedSqlNode.apply
public boolean apply(DynamicContext context) {
for (SqlNode sqlNode : contents) {
sqlNode.apply(context);
}
return true;
}
Copy the code
DynamicSqlSource getBoundSql method
Parse #{} from SQL to? And then generate the BoundSql object.
public BoundSql getBoundSql(Object parameterObject) { DynamicContext context = new DynamicContext(configuration, parameterObject); rootSqlNode.apply(context); SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); Class<? > parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()); BoundSql boundSql = sqlSource.getBoundSql(parameterObject); for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) { boundSql.setAdditionalParameter(entry.getKey(), entry.getValue()); } return boundSql; }Copy the code
Executor. Query method
The real query method is then called, which first checks to see if caching is enabled. If not, the query method is executed directly
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); if (cache ! = null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, parameterObject, boundSql); @SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }Copy the code
BaseExecutor. Query method
It first looks from level 1 cache (which is enabled by default), and if it can’t find it, it executes the queryFromDataBase method
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; try { queryStack++; list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list ! = null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list; }Copy the code
BaseExecutor queryFromDatabase method
This side is mainly the level 1 cache placeholder, and then set the level 1 cache after the query results
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
Copy the code
SimpleExecutor doQuery method
The statementHandler is generated, the SQL is preprocessed, and the statementhandler.query method is executed
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); }}Copy the code
PreparedStatementHandler. Query method
After the query result is obtained, it will be processed by ResultSetHandler
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
Copy the code
ResultsetHandler handleResultSets method
So here’s some transformation logic that I won’t go into. So far, the execution process of Mybatis has also been parsed. This process is parsed based on the condition of dynamic SQL.
public List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); final List<Object> multipleResults = new ArrayList<Object>(); int resultSetCount = 0; ResultSetWrapper rsw = getFirstResultSet(stmt); List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); while (rsw ! = null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } String[] resultSets = mappedStatement.getResultSets(); if (resultSets ! = null) { while (rsw ! = null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping ! = null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } } return collapseSingleResultList(multipleResults); }Copy the code
Finally, I will introduce the core components of Mybatis
StatementHandler encapsulates the JDBC Statement operation and is responsible for operations on the JDBC Statement, such as setting parameters
Convert the Statement result set to a List.
ParameterHandler is responsible for converting user-passed parameters into those required for JDBC Statements.
ResultSetHandler is responsible for converting the ResultSet object returned by JDBC into a collection of type List.
The Executor MyBatis Executor, the core of MyBatis scheduling, is responsible for SQL statement generation and query caching
The maintenance of the