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!