Writing in the front

With the development of the Internet, more and more companies have abandoned Hibernate and embraced MyBatis. Moreover, many large factories in the interview like to ask MyBatis underlying principle and source code implementation. In short, MyBatis has almost become a Java developers must master the framework technology, today, we will go into the in-depth analysis of MyBatis source code. The article is a little long, it is suggested to collect slowly after the study. The whole thirty thousand words or so, the whole high-energy, small friends can slowly study.

The article has been included:

Github.com/sunshinelyz…

Gitee.com/binghe001/t…

MyBatis source code analysis

We should all know that Mybatis source code is also the Jbdc encapsulation again, no matter how to package, there will be a link, preparedStatement, encapsulation parameters, execute these steps.

Configuration Parsing Procedure

String resource = "mybatis-config.xml";
//1. Read the mybatis-config. XML file under resources
InputStream inputStream = Resources.getResourceAsStream(resource);
//2. Create SqlSessionFactory using SqlSessionFactoryBuilder
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//3. Create a SqlSession using sqlSessionFactory
SqlSession sqlSession = sqlSessionFactory.openSession();
Copy the code

Read the file Resources. GetResourceAsStream (resource)

public static InputStream getResourceAsStream(String resource) throws IOException {
	return getResourceAsStream(null, resource);
} 
// Loader assigns the value to null
public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
	InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
	if (in == null) {
		throw new IOException("Could not find resource " + resource);
	} 
	return in;
}
/ / this is null
public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
	return getResourceAsStream(resource, getClassLoaders(classLoader));
} 
/ / this class loading
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
	for (ClassLoader cl : classLoader) {
		if (null! = cl) {// Load the specified path file stream
			InputStream returnValue = cl.getResourceAsStream(resource);
			// now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource
			if (null == returnValue) {
				returnValue = cl.getResourceAsStream("/" + resource);
			} 
			if (null! = returnValue) {returnreturnValue; }}}return null;
}
Copy the code

Conclusion: mainly through this. GetResourceAsStream () method to obtain the classpath specified path of the Resource.

Create SqlSessionFactory from SqlSessionFactoryBuilder

/ / the SqlSessionFactoryBuilder is a builder pattern
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
public SqlSessionFactory build(InputStream inputStream) {
	return build(inputStream, null.null);
}
//XMLConfigBuilder is also the builder mode
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
	try {
		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.}}}// Next go to the XMLConfigBuilder constructor
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
	this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
// After entering this, initialize Configuration
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
	super(new Configuration());
	ErrorContext.instance().resource("SQL Mapper Configuration");
	this.configuration.setVariables(props);
	this.parsed = false;
	this.environment = environment;
	this.parser = parser;
}
Parser.parse () parses the XML and build(configuration) creates the SqlSessionFactory
return build(parser.parse());
Copy the code

To parse the XML parser. The parse ()

public Configuration parse(a) {
	// Determine whether to repeat parsing
	if (parsed) {
		throw new BuilderException("Each XMLConfigBuilder can only be used once.");
	} 
	parsed = true;
	// Read the level-1 node configuration file
	parseConfiguration(parser.evalNode("/configuration"));
	return configuration;
}
Copy the code
private void parseConfiguration(XNode root) {
	try {
		//properties tag, which is used to configure parameter information such as the most common database connection information
		propertiesElement(root.evalNode("properties"));
		Properties settings = settingsAsProperties(root.evalNode("settings"));
		loadCustomVfs(settings);
		loadCustomLogImpl(settings);
		1. Specify a single entity; 2. 2. Specify the package
		typeAliasesElement(root.evalNode("typeAliases"));
		/ / the plugin
		pluginElement(root.evalNode("plugins"));
		// Used to create objects (when database data is mapped to Java objects)
		objectFactoryElement(root.evalNode("objectFactory"));
		objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
		reflectorFactoryElement(root.evalNode("reflectorFactory"));
		settingsElement(settings);
		// read it after objectFactory and objectWrapperFactory issue #631
		// Database environment
		environmentsElement(root.evalNode("environments"));
		databaseIdProviderElement(root.evalNode("databaseIdProvider"));
		// Convert database type to Java data type
		typeHandlerElement(root.evalNode("typeHandlers"));
		// Add, delete, change, and query the database
		mapperElement(root.evalNode("mappers"));
	} catch (Exception e) {
		throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: "+ e, e); }}Copy the code

Conclusion :parseConfiguration does what it takes to parse the tabs under configuration

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");
				// The package path is stored in mapperRegistry
				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);
					// Read the mapper. XML file
					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

Conclusion: by parsing the configuration. The XML file, for the Environment, the Setting, it is important to add under all after parsing out to the configuration, the configuration is similar to the allocation center, all configuration information here.

Mapperparser.parse () Parses the Mapper

public void parse(a) {
	if(! configuration.isResourceLoaded(resource)) {// Parse all child tags
		configurationElement(parser.evalNode("/mapper"));
		configuration.addLoadedResource(resource);
		// Bind the namespace (interface type) to the factory class
		bindMapperForNamespace();
	}
	parsePendingResultMaps();
	parsePendingCacheRefs();
	parsePendingStatements();
} 
// The mapper. XML tag is parsed
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);
		// References to other namespace cache configurations
		cacheRefElement(context.evalNode("cache-ref"));
		// Cache configuration for the given namespace
		cacheElement(context.evalNode("cache"));
		parameterMapElement(context.evalNodes("/mapper/parameterMap"));
		// is the most complex and powerful element describing how to load an object from a database result set
		resultMapElements(context.evalNodes("/mapper/resultMap"));
		// A reusable block of statements that can be referenced by other statements
		sqlElement(context.evalNodes("/mapper/sql"));
		// Get the MappedStatement object
		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); }}// Get the MappedStatement object
private void buildStatementFromContext(List<XNode> list) {
	if(configuration.getDatabaseId() ! =null) {
		buildStatementFromContext(list, configuration.getDatabaseId());
	} 
	buildStatementFromContext(list, null);
}
// Get the MappedStatement object
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
	// Add, delete, change labels
	for (XNode context : list) {
		final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
		try {
			// Parse labels in insert/update/select/del
			statementParser.parseStatementNode();
		} catch(IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); }}}public void parseStatementNode(a) {
	// A unique identifier in the namespace that can be used to reference this statement
	String id = context.getStringAttribute("id");
	// Database vendor 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;
	FlushCache and useCache are both related to level 2 caching
	// When set to true, local and secondary caches will be cleared whenever a statement is called. Default :false
	boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
	// Setting it to true will cause the results of this statement to be cached by the second level cache. Default: true for select elements
	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());
	// The fully qualified name or alias of the argument class passed in for this statement
	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.
	processSelectKeyNodes(id, parameterTypeClass, langDriver);
	// 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);
	if (configuration.hasKeyGenerator(keyStatementId)) {
		keyGenerator = configuration.getKeyGenerator(keyStatementId);
	} else {
		keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
	} 
	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");
	// The fully qualified name or alias of the class of the desired type returned from this statement
	String resultType = context.getStringAttribute("resultType"); Class<? > resultTypeClass = resolveClass(resultType);// A named reference to an external resultMap
	String resultMap = context.getStringAttribute("resultMap");
	String resultSetType = context.getStringAttribute("resultSetType");
	ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
	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);
}
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) {
	if (unresolvedCacheRef) {
		throw new IncompleteElementException("Cache-ref not yet resolved");
	} 
		id = applyCurrentNamespace(id, false);
		boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
		MappedStatement.Builder statementBuilder = newMappedStatement.Builder(configuration, id, sqlSource, sqlCommandType) .resource(resource) .fetchSize(fetchSize) .timeout(timeout) .statementType(statementType) .keyGenerator(keyGenerator) .keyProperty(keyProperty) .keyColumn(keyColumn) .databaseId(databaseId) .lang(lang) .resultOrdered(resultOrdered) .resultSets(resultSets) .resultMaps(getStatementResultMaps(resultMap, resultType, id)) .resultSetType(resultSetType) .flushCacheRequired(valueOrDefault(flushCache, ! isSelect)) .useCache(valueOrDefault(useCache, isSelect)) .cache(currentCache); ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);if(statementParameterMap ! =null) {
			statementBuilder.parameterMap(statementParameterMap);
		} 
		MappedStatement statement = statementBuilder.build();
		// It is held in configuration
		configuration.addMappedStatement(statement);
		return statement;
}
public void addMappedStatement(MappedStatement ms){
//ms.getId = mapper.UserMapper.getUserById
//ms = MappedStatement = data in each tag
	mappedStatements.put(ms.getId(), ms);
}
// mappedStatements are stored one by one
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection").conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
Copy the code

Parse the bindMapperForNamespace() method

Bind the namespace (interface type) to the factory class

private void bindMapperForNamespace(a) {
	// The namespace of the current Mapper
	String namespace = builderAssistant.getCurrentNamespace();
	if(namespace ! =null) { Class<? > boundType =null;
		try {
			/ / interface mapper. UserMapper this
			boundType = Resources.classForName(namespace);
		} catch (ClassNotFoundException e) {
		} 
		if(boundType ! =null) {
			if(! configuration.hasMapper(boundType)) { configuration.addLoadedResource("namespace:"+ namespace); configuration.addMapper(boundType); }}}}public <T> void addMapper(Class<T> type) {
	mapperRegistry.addMapper(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 {
			// Interface type (key)-> Factory
			knownMappers.put(type, new MapperProxyFactory<>(type));
			MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
			parser.parse();
			loadCompleted = true;
		} finally {
			if(! loadCompleted) { knownMappers.remove(type); }}}}Copy the code

Generate the SqlSessionFactory object

The xmlMapperBuilder.parse () method, which parses the Mapper, has two methods:

(1) the configurationElement () parsing all child tags, final parse Mapper. In XML insert/update/delete/select tag id (full path) of the key and the label and the data connection MappedStatement deposit Put it in the mappedStatements map in Configuration.

(2) bindMapperForNamespace() stores the interface type (Interface mapper.UserMapper) and factory classes into knownMappers in MapperRegistry.

The creation of a SqlSessionFactory

public SqlSessionFactory build(Configuration config) {
	return new DefaultSqlSessionFactory(config);
}
Copy the code

Take Configuration as an argument and simply new a DefaultSqlSessionFactory.

SqlSession Session creation process

OpenSession () method is used to create a session for each connection between mybatis and the database. This session needs to contain an Executor to execute SQL. Executor in turn specifies the transaction type and the type of the Executor.

Create Transaction(two ways)

attribute Generation factory class Produce a transaction
JDBC JbdcTransactionFactory JdbcTransaction
MANAGED ManagedTransactionFactory ManagedTransaction
  • If JDBC is configured, transactions are managed using commit(), rollback(), and close() of the Connection objects.
  • MANAGED assigns transactions to containers such as JBOSS and Weblogic.
SqlSession sqlSession = sqlSessionFactory.openSession();
Copy the code
public SqlSession openSession(a) {
	ExecutorType defaultExecutorType = executorType.simple
	return openSessionFromDataSource(configuration.getDefaultExecutorType(), null.false);
}
Copy the code
<environments default="development">
	<environment id="development">
		<transactionManager type="JDBC"/>
		<dataSource type="POOLED">
			<property name="driver" value="${driver}"/>
			<property name="url" value="${url}"/>
			<property name="username" value="${username}"/>
			<property name="password" value="${password}"/>
		</dataSource>
	</environment>
</environments>
Copy the code

Create Executor

ExecutorType: SIMPLE(SimpleExecutor), REUSE(ReuseExecutor), and BATCH(BatchExecutor)
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
	Transaction tx = null;
	try {
		// The development node in XML
		final Environment environment = configuration.getEnvironment();
		// Type is set to Jbdc, so generate the JbdcTransactionFactory factory class
		final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
		//Jdbc generates JbdcTransactionFactory generates JbdcTransaction
		tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
		// Create the CachingExecutor actuator
		final Executor executor = configuration.newExecutor(tx, execType);
		// Create DefaultSqlSession properties including Configuration and Executor objects
		return new DefaultSqlSession(configuration, executor, autoCommit);
	} catch (Exception e) {
		closeTransaction(tx); // may have fetched a connection so lets call
		close()
		throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
	} finally{ ErrorContext.instance().reset(); }}Copy the code

Get the Mapper object

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Copy the code
public <T> T getMapper(Class<T> type) {
	return configuration.getMapper(type, this);
}
Copy the code

MapperRegistry. GetMapper is taken from mapperRegistry’s knownMappers, where interface types are stored Mapper. UserMapper) and the factory class (MapperProxyFactory).

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
	return mapperRegistry.getMapper(type, sqlSession);
}
Copy the code

Take the factory class from knownMappers’ Map according to the interface type (Interface mapper.UserMapper).

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); }}public T newInstance(SqlSession sqlSession) {
	final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
	return newInstance(mapperProxy);
}
Copy the code

Here by the JDK dynamic proxy agent MapperProxy object returned (org. Apache. Ibatis. Binding. MapperProxy @ 6 b2ea799)

protected T newInstance(MapperProxy<T> mapperProxy) {
	/ / mapperInterface interface mapper. UserMapper
	return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new
	Class[] { mapperInterface }, mapperProxy);
}
Copy the code
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Copy the code

Execute SQL

User user = userMapper.getUserById(1);
Copy the code

Invoke the Invoke proxy method

Since all Mapper are MapperProxy proxy objects, any method is the invoke() method that executes MapperProxy

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	try {
		// Determine whether you need to execute SQL or execute methods directly
		if (Object.class.equals(method.getDeclaringClass())) {
			return method.invoke(this, args);
			// The Default method in the interface is determined
		} else if (isDefaultMethod(method)) {
			returninvokeDefaultMethod(proxy, method, args); }}catch (Throwable t) {
		throw ExceptionUtil.unwrapThrowable(t);
	} 
    // Get the cache that holds the relationship between method signatures and interface methods
	final MapperMethod mapperMethod = cachedMapperMethod(method);
	return mapperMethod.execute(sqlSession, args);
}
Copy the code

Calling the execute method

The example we’re using here is a query so we’re doing an else branch.

public Object execute(SqlSession sqlSession, Object[] args) {
	Object result;
	Command.gettype () is select
	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 {
				// Convert parameters to SQL parameters
				Object param = method.convertArgsToSqlCommandParam(args);
				result = sqlSession.selectOne(command.getName(), param);
				if (method.returnsOptional()
				&& (result == null ||
				!method.getReturnType().equals(result.getClass()))) {
					result = Optional.ofNullable(result);
				}
			}
			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

Calling a selectOne is really a selectList

SelectOne querying one is the same as querying multiple.

public <T> T selectOne(String statement, Object parameter) {
	// Popular vote was to return null on 0 results and throw exception on too many.
	List<T> list = this.selectList(statement, parameter);
	if (list.size() == 1) {
		return list.get(0);
	} else if (list.size() > 1) {
		throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
	} else {
		return null; }}Copy the code
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
	try {
		// Get the MappedStatement object from the mappedStatements in the Configuration according to the key(full path of id)
		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

The mappedStatements object is shown

The MappedStatement object is shown

Execute the query method

Create CacheKey

Get the SQL information from BoundSql to create a CacheKey. The CacheKey is the Key of the cache.

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
	// Create a cache Key
	BoundSql boundSql = ms.getBoundSql(parameterObject);
	//key = -575461213:-771016147:mapper.UserMapper.getUserById:0:2147483647:select * from test_user where id = ? :1:development
	CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
	return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
Copy the code
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, boundSql);
			@SuppressWarnings("unchecked")
			List<E> list = (List<E>) tcm.getObject(cache, key);
			if (list == null) {
				list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
				tcm.putObject(cache, key, list); // issue #578 and #116
			} 
			returnlist; }}return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
Copy the code

Clearing the local cache

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.");
	} 
	//queryStack is used to record the queryStack and prevent recursive queries from being processed in the cache repeatedly
	//flushCache=true, the local cache is flushed first.
	if (queryStack == 0 && ms.isFlushCacheRequired()) {
		// Clear the local cache
		clearLocalCache();
	} 
	List<E> list;
	try {
		queryStack++;
		list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
		if(list ! =null) {
			handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
		} else {
			// If there is no cache, query from the database: queryFromDatabase()list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); }}finally {
		queryStack--;
	} 
	if (queryStack == 0) {
		for (DeferredLoad deferredLoad : deferredLoads) {
		deferredLoad.load();
		} 
		// issue #601
		deferredLoads.clear();
		// If LocalCacheScope == STATEMENT, the local cache is cleared
		if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
			// issue #482clearLocalCache(); }}return list;
}
Copy the code

Query from database

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
	List<E> list;
	// Use placeholders in the cache first
	localCache.putObject(key, EXECUTION_PLACEHOLDER);
	try {
		// Execute Executor's doQuery(), default SimpleExecutor
		list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
	} finally {
		// After executing the query, remove the placeholder
		localCache.removeObject(key);
	} 
	// Re-insert data
	localCache.putObject(key, list);
	if (ms.getStatementType() == StatementType.CALLABLE) {
		localOutputParameterCache.putObject(key, parameter);
	} 
	return list;
}
Copy the code

Perform doQuery

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.query(stmt, resultHandler);
	} finally{ closeStatement(stmt); }}Copy the code

Source summary

In general, the source code of MyBatis is relatively simple, as long as we step down, spend two or three days to study carefully, basically can figure out the main context of the source code.

Ok, that’s all for today, I’m Glacier, if you have any questions, you can leave a comment below, you can also add my wechat: sun_shine_LYz, exchange technology together, advance together, together awesome ~~

Glacier Original PDF

Follow Glacier Technology wechat official account:

Reply to “Concurrent Programming” to receive the PDF of In-depth Understanding of High Concurrent Programming (1st edition).

Reply “concurrent source code” to get the “Concurrent programming core Knowledge (source code Analysis first edition)” PDF document.

Reply to “Limit Traffic” to get the PDF document “Distributed Solution under 100 million Traffic”.

Reply to “design patterns” to get the PDF of “simple Java23 design patterns”.

Reply “new Java8 features” obtain the Java8 new features tutorial PDF document.

Reply to “Distributed Storage” to receive the PDF of “Learn Distributed Storage Techniques from Glacier”.

Reply to “Nginx” to receive the PDF of Learn Nginx Technology from Glacier.

Reply to “Internet Engineering” to get the PDF of “Learn Internet Engineering techniques from Glacier”.

Write in the last

If you think glacier wrote good, please search and pay attention to “glacier Technology” wechat public number, learn with glacier high concurrency, distributed, micro services, big data, Internet and cloud native technology, “glacier technology” wechat public number updated a large number of technical topics, each technical article is full of dry goods! Many readers have successfully moved to Dachang by reading articles on the “Glacier Technology” wechat official account; There are also many readers to achieve a technological leap, become the company’s technical backbone! If you also want to like them to improve their ability to achieve a leap in technical ability, into the big factory, promotion and salary, then pay attention to the “Glacier Technology” wechat public account, update the super core technology every day dry goods, so that you no longer confused about how to improve technical ability!