寫在前面
隨著網際網路的發展,越來越多的公司摒棄了Hibernate,而選擇擁抱了MyBatis。而且,很多大廠在面試的時候喜歡問MyBatis底層的原理和原始碼實現。總之,MyBatis幾乎成為了Java開發人員必須深入掌握的框架技術,今天,我們就一起來深入分析MyBatis原始碼。文章有點長,建議先收藏後慢慢研究。整體三萬字左右,全程高能,小夥伴們可慢慢研究。
文章已收錄到:
https://github.com/sunshinelyz/technology-binghe
https://gitee.com/binghe001/technology-binghe
MyBatis原始碼解析
大家應該都知道Mybatis原始碼也是對Jbdc的再一次封裝,不管怎麼進行包裝,還是會有獲取連結、preparedStatement、封裝引數、執行這些步驟的。
配置解析過程
String resource = "mybatis-config.xml";
//1.讀取resources下面的mybatis-config.xml檔案
InputStream inputStream = Resources.getResourceAsStream(resource);
//2.使用SqlSessionFactoryBuilder建立SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//3.通過sqlSessionFactory建立SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
Resources.getResourceAsStream(resource)讀取檔案
public static InputStream getResourceAsStream(String resource) throws IOException {
return getResourceAsStream(null, resource);
}
//loader賦值為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;
}
//classLoader為null
public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
return getResourceAsStream(resource, getClassLoaders(classLoader));
}
//classLoader類載入
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
for (ClassLoader cl : classLoader) {
if (null != cl) {
//載入指定路徑檔案流
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) {
return returnValue;
}
}
}
return null;
}
總結:主要是通過ClassLoader.getResourceAsStream()方法獲取指定的classpath路徑下的Resource 。
通過SqlSessionFactoryBuilder建立SqlSessionFactory
//SqlSessionFactoryBuilder是一個建造者模式
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
//XMLConfigBuilder也是建造者模式
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.
}
}
}
//接下來進入XMLConfigBuilder建構函式
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
//接下來進入this後,初始化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()負責解析xml,build(configuration)建立SqlSessionFactory
return build(parser.parse());
parser.parse()解析xml
public Configuration parse() {
//判斷是否重複解析
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//讀取配置檔案一級節點configuration
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
//properties 標籤,用來配置引數資訊,比如最常見的資料庫連線資訊
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
//實體別名兩種方式:1.指定單個實體;2.指定包
typeAliasesElement(root.evalNode("typeAliases"));
//外掛
pluginElement(root.evalNode("plugins"));
//用來建立物件(資料庫資料對映成java物件時)
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"));
//資料庫型別和Java資料型別的轉換
typeHandlerElement(root.evalNode("typeHandlers"));
//這個是對資料庫增刪改查的解析
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
總結:parseConfiguration完成的是解析configuration下的標籤
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//解析<package name=""/>
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
//包路徑存到mapperRegistry中
configuration.addMappers(mapperPackage);
} else {
//解析<mapper url="" class="" resource=""></mapper>
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);
//讀取Mapper.xml檔案
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.");
}
}
}
}
}
總結: 通過解析configuration.xml檔案,獲取其中的Environment、Setting,重要的是將下的所有解析出來之後新增到
Configuration,Configuration類似於配置中心,所有的配置資訊都在這裡。
mapperParser.parse()對 Mapper 對映器的解析
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//解析所有的子標籤
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
//把namespace(介面型別)和工廠類繫結起來
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
//這裡面解析的是Mapper.xml的標籤
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"));
//是最複雜也是最強大的元素,用來描述如何從資料庫結果集中來載入物件
resultMapElements(context.evalNodes("/mapper/resultMap"));
//可被其他語句引用的可重用語句塊
sqlElement(context.evalNodes("/mapper/sql"));
//獲得MappedStatement物件(增刪改查標籤)
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);
}
}
//獲得MappedStatement物件(增刪改查標籤)
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
//獲得MappedStatement物件(增刪改查標籤)
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
//迴圈增刪改查標籤
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//解析insert/update/select/del中的標籤
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
public void parseStatementNode() {
//在名稱空間中唯一的識別符號,可以被用來引用這條語句
String id = context.getStringAttribute("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和useCache都和二級快取有關
//將其設定為true後,只要語句被呼叫,都會導致本地快取和二級快取被清空,預設值:false
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
//將其設定為 true 後,將會導致本條語句的結果被二級快取快取起來,預設值:對 select 元素為 true
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());
//會傳入這條語句的引數類的完全限定名或別名
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");
//從這條語句中返回的期望型別的類的完全限定名或別名
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
//外部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 = new MappedStatement.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();
//持有在configuration中
configuration.addMappedStatement(statement);
return statement;
}
public void addMappedStatement(MappedStatement ms){
//ms.getId = mapper.UserMapper.getUserById
//ms = MappedStatement等於每一個增刪改查的標籤的裡的資料
mappedStatements.put(ms.getId(), ms);
}
//最終存放到mappedStatements中,mappedStatements存放的是一個個的增刪改查
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection").conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
解析bindMapperForNamespace()方法
把 namespace(介面型別)和工廠類繫結起來
private void bindMapperForNamespace() {
//當前Mapper的名稱空間
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
//interface mapper.UserMapper這種
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 {
//介面型別(key)->工廠類
knownMappers.put(type, new MapperProxyFactory<>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
生成SqlSessionFactory物件
XMLMapperBuilder.parse()方法,是對 Mapper 對映器的解析裡面有兩個方法:
(1)configurationElement()解析所有的子標籤,最終解析Mapper.xml中的insert/update/delete/select標籤的id(全路徑)組成key和整個標籤和資料連線組成MappedStatement存放到Configuration中的 mappedStatements這個map裡面。
(2)bindMapperForNamespace()是把介面型別(interface mapper.UserMapper)和工廠類存到放MapperRegistry中的knownMappers裡面。
SqlSessionFactory的建立
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
直接把Configuration當做引數,直接new一個DefaultSqlSessionFactory。
SqlSession會話的建立過程
mybatis操作的時候跟資料庫的每一次連線,都需要建立一個會話,我們用openSession()方法來建立。這個會話裡面需要包含一個Executor用來執行 SQL。Executor又要指定事務型別和執行器的型別。
建立Transaction(兩種方式)
屬性 | 產生工廠類 | 產生事務 |
---|---|---|
JDBC | JbdcTransactionFactory | JdbcTransaction |
MANAGED | ManagedTransactionFactory | ManagedTransaction |
- 如果配置的是 JDBC,則會使用Connection 物件的 commit()、rollback()、close()管理事務。
- 如果配置成MANAGED,會把事務交給容器來管理,比如 JBOSS,Weblogic。
SqlSession sqlSession = sqlSessionFactory.openSession();
public SqlSession openSession() {
//configuration中有預設賦值protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
<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>
建立Executor
//ExecutorType是SIMPLE,一共有三種SIMPLE(SimpleExecutor)、REUSE(ReuseExecutor)、BATCH(BatchExecutor)
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//xml中的development節點
final Environment environment = configuration.getEnvironment();
//type配置的是Jbdc所以生成的是JbdcTransactionFactory工廠類
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//Jdbc生成JbdcTransactionFactory生成JbdcTransaction
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//建立CachingExecutor執行器
final Executor executor = configuration.newExecutor(tx, execType);
//建立DefaultSqlSession屬性包括 Configuration、Executor物件
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();
}
}
獲得Mapper物件
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
mapperRegistry.getMapper是從MapperRegistry的knownMappers裡面取的,knownMappers裡面存的是介面型別(interface mapper.UserMapper)和工廠類(MapperProxyFactory)。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
從knownMappers的Map里根據介面型別(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);
}
這裡通過JDK動態代理返回代理物件MapperProxy(org.apache.ibatis.binding.MapperProxy@6b2ea799)
protected T newInstance(MapperProxy<T> mapperProxy) {
//mapperInterface是interface mapper.UserMapper
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new
Class[] { mapperInterface }, mapperProxy);
}
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
執行SQL
User user = userMapper.getUserById(1);
呼叫invoke代理方法
由於所有的 Mapper 都是 MapperProxy 代理物件,所以任意的方法都是執行MapperProxy 的invoke()方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//判斷是否需要去執行SQL還是直接執行方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
//這裡判斷的是介面中的預設方法Default等
} 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);
}
呼叫execute方法
這裡使用的例子用的是查詢所以走的是else分支語句。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//根據命令型別走不行的操作command.getType()是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 {
//將引數轉換為SQL的引數
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;
}
呼叫selectOne其實是selectList
selectOne查詢一個和查詢多個其實是一樣的。
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;
}
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//從Configuration裡的mappedStatements里根據key(id的全路徑)獲取MappedStatement 物件
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();
}
}
mappedStatements物件如圖
MappedStatement物件如圖
執行query方法
建立CacheKey
從 BoundSql 中獲取SQL資訊,建立 CacheKey。這個CacheKey就是快取的Key。
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//建立快取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);
}
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
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
清空本地快取
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 用於記錄查詢棧,防止遞迴查詢重複處理快取
//flushCache=true 的時候,會先清理本地快取(一級快取)
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 {
//如果沒有快取,會從資料庫查詢:queryFromDatabase()
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
//如果 LocalCacheScope == STATEMENT,會清理本地快取
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
從資料庫查詢
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 {
//執行Executor 的 doQuery(),預設是SimpleExecutor
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;
}
執行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);
}
}
原始碼總結
總體上來說,MyBatis的原始碼還是比較簡單的,只要大家踏下心來,花個兩三天仔細研究下,基本上都能弄明白原始碼的主體脈絡。
好了,今天就到這兒吧,我是冰河,大家有啥問題可以在下方留言,也可以加我微信:sun_shine_lyz,一起交流技術,一起進階,一起牛逼~~