XMLStatementBuilder類中的parseStatementNode方法是真正開始決議指定的SQL節點,
從上文中可知context就是SQL標簽對應的XNode物件,該方法前面大部分內容都是從XNode物件中獲取各個資料,其實該方法的大致意思就是決議這個SQL標簽里的所有資料(SQL陳述句以及標簽屬性),并把所有資料通過addMappedStatement這個方法封裝在MappedStatement這個物件中,這個物件中封裝了一條SQL所在標簽的所有內容,比如這個SQL標簽的id、SQL陳述句、輸入值、輸出值等,我們要清楚一個SQL的標簽就對應一個MappedStatement物件,
public void parseStatementNode() {
// 通過XNode物件獲取標簽的各個資料
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
String nodeName = context.getNode().getNodeName();
// 省略其他內容...
// 獲取SQL陳述句并封裝成一個SqlSource物件
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
// 將SQL標簽的所有資料添加至MappedStatement物件中
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
講解一下關于SQL陳述句的獲取,我們著重關注這一行代碼:
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
這里LanguageDriver介面實作類是XMLLanguageDriver,再進入createSqlSource方法,又是熟悉的XxxBuilder物件,很明顯是用來決議SQL內容的,parseScriptNode方法最侄訓創建一個RawSqlSource物件,里面存盤一個BoundSql物件,而BoundSql物件才是真正存盤SQL陳述句的類,
在創建RawSqlSource物件的時候,會呼叫GenericTokenParser類中的parse方法來決議SQL陳述句中的通用標記(GenericToken),比如“#{ }”,并且將所有的通用標記都變為“?”占位符,
// XMLLanguageDriver類
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
// XMLScriptBuilder類
public SqlSource parseScriptNode() {
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
// 創建的是RawSqlSource物件,里面存有一個BoundSql物件
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
// 真正存盤SQL陳述句以及引數的物件
public class BoundSql {
private final String sql;
private final List<ParameterMapping> parameterMappings;
private final Object parameterObject;
private final Map<String, Object> additionalParameters;
private final MetaObject metaParameters;
// 省略其他內容...
}
進入到addMappedStatement方法,又是一個很長的方法,很明顯這里又使用了建造者模式,依靠MappedStatement類中的一個內部類Builder來構造MappedStatement物件,Configuration類中使用了一個Map集合來存盤所有的MappedStatement物件,Key值就是這個SQL標簽的id值,我們這里應該就是“getPaymentById”,Value值就是我們創建的對應的MapperStatement物件,
有個地方需要注意一下,在創建MapperStatement物件前會對id(即介面方法名)進行處理,在id前加上命名空間,也就成了該介面方法的全限定名,因此我們在呼叫selectOne等方法時應該填寫介面方法的全限定名,
其實我們決議XML檔案的目的就是把每個XML檔案中的所有增、刪、改、查SQL標簽決議成一個個MapperStatement,并把這些物件裝到Configuration的Map中備用,
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");
}
// 修改存入Map集合的Key值為全限定名
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 創建MappedStatement的構造器
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物件,并添加至Configuration物件中
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
繼續回到XMLMapperBuilder類中的parse方法,當決議完一個XML檔案后就會把該檔案的路徑存入loadedResources集合中,
接下來我們看看bindMapperForNamespace方法,看名字就知道它的作用是通過命名空間系結mapper,一開始獲取名稱空間,名稱空間一般都是我們mapper的全限定名,它通過反射獲取這個mapper的Class物件,Configuration中維護了一個名為knownMappers的Map集合,Key值是我們剛才通過反射創建的Class物件,Value值則是通過動態代理創建的Class物件的代理物件,
因為knownMappers集合中還沒有存入我們創建的Class物件,所以會進入判斷陳述句,它先把名稱空間存到我們剛才存XML檔案路徑名的Set集合中,表示該命名空間已經加載過,然后再把mapper的Class物件存到knownMappers集合中,
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
// 將決議完的XML檔案路徑存入Configuration的loadedResources集合中
configuration.addLoadedResource(resource);
// 通過名稱空間系結mapper
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
private void bindMapperForNamespace() {
// 獲取到Mapper介面的全限定名
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
// 通過全限定名創建該Mapper介面的Class物件
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
// ignore, bound type is not required
}
if (boundType != null && !configuration.hasMapper(boundType)) {
// 將命名空間的名稱存入已加載的Set集合中
configuration.addLoadedResource("namespace:" + namespace);
// 將Class物件存入Map集合中
configuration.addMapper(boundType);
}
}
}
public class Configuration {
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
}
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
// 添加已注冊的Mapper介面的Class物件
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<>(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);
}
}
}
}
// 省略其他內容...
}
在存入Class物件的代理物件后,后面還有一步MapperAnnotationBuilder類的決議作業,我們進入到parse方法,可以看到它會使用Class物件的字串進行判斷該是否已經決議過該Class物件,通常這里是不會進入判斷的(下面的mapperClass方式才會進來),
public void parse() {
String resource = type.toString();
// 判斷該Mapper介面的Class物件是否已經決議過
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
// 省略其他內容...
}
parsePendingMethods();
}
基于resource的方式講解完畢了,接下來就是url和mapperClass,url的方式和resource一樣,這里就不再贅述了,關于mapperClass,這種方式的決議步驟其實和resource是相反的,即先通過反射創建Mapper介面的Class物件,再通過Class物件的全限定名來尋找對應的XML映射檔案,
else if (resource == null && url == null && mapperClass != null) {
// 反射創建Mapper介面的Class物件
Class<?> mapperInterface = Resources.classForName(mapperClass);
// 將該Class物件添加至knownMappers集合中
configuration.addMapper(mapperInterface);
}
// Configuration類
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
mapperClass方式決議XML映射檔案和resource方式略有不同,mapperClass首先使用的是MapperAnnotationBuilder類進行決議,雖然看名字貌似該類只是負責注解的映射,但是其實暗藏玄機,
// MapperRegistry類
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<>(type));
// 使用的是MapperAnnotationBuilder決議器
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// mapperClass方式進行XML映射檔案的決議
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
我們進入到該類的parse方法,與resource方式不同的是這里會進入該判斷,并且執行了loadXmlResource方法,這個方法就是通過Class物件的名稱來加載XML映射檔案,loadXmlResource方法中首先也是進行一個判斷,這里肯定是沒有加載該XML映射檔案的命名空間的,后面有一行代碼非常關鍵,它將Class物件的名稱中所有的“.”替換為了“/”,然后拼接上了XML檔案的后綴,這表示MyBatis會在Mapper介面的同層級目錄來尋找對應的XML映射檔案,后面的步驟就和之前resource方式一樣了,通過XMLMapperBuilder類來決議,
這也解釋了為啥使用mapperClass方式時,XML映射檔案要與Mapper介面放在同一層級目錄下,
// MapperAnnotationBuilder
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
// 加載XML映射檔案
loadXmlResource();
// 加載完成后同樣存入Map集合中
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
for (Method method : type.getMethods()) {
if (!canHaveStatement(method)) {
continue;
}
if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
&& method.getAnnotation(ResultMap.class) == null) {
parseResultMap(method);
}
try {
parseStatement(method);
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
// 通過Class物件的名稱來加載XML映射檔案
private void loadXmlResource() {
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
// 構建XML映射檔案路徑
String xmlResource = type.getName().replace('.', '/') + ".xml";
// 后續步驟和resource方式一致
InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
if (inputStream == null) {
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e2) {
// ignore, resource is not required
}
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
至此,單檔案映射的加載已經講解完畢,接下里進入多檔案映射的加載!
最后再講講多檔案映射的加載,它首先或得XML所在的包名,然后呼叫configuration的addMappers物件,是不是有點眼熟,單檔案映射是addMapper,多檔案映射就是addMappers,
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
}
進入addMappers方法,就是通過ResolverUtil這個決議工具類找出該包下的所有mapper的名稱并通過反射創建mapper的Class物件裝進集合中,然后回圈呼叫addMapper(mapperClass)這個方法,這就和單檔案映射的Class型別一樣了,把mapper介面的Class物件作為引數傳進去,然后生成代理物件裝進集合然后再決議XML,
// Configuration類
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
// MapperRegistry類
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
// 后續步驟和單映射檔案加載一致
addMapper(mapperClass);
}
}
// ResolveUtil類
public ResolverUtil<T> find(Test test, String packageName) {
String path = getPackagePath(packageName);
try {
List<String> children = VFS.getInstance().list(path);
for (String child : children) {
if (child.endsWith(".class")) {
addIfMatching(test, child);
}
}
} catch (IOException ioe) {
log.error("Could not read package: " + packageName, ioe);
}
return this;
}
protected void addIfMatching(Test test, String fqn) {
try {
String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
ClassLoader loader = getClassLoader();
if (log.isDebugEnabled()) {
log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
}
// 反射創建Class物件并存入Set集合中
Class<?> type = loader.loadClass(externalName);
if (test.matches(type)) {
matches.add((Class<T>) type);
}
} catch (Throwable t) {
log.warn("Could not examine class '" + fqn + "'" + " due to a "
+ t.getClass().getName() + " with message: " + t.getMessage());
}
}
終于把MyBatis的初始化步驟講完了,這里只是重點講解了<mappers>節點的決議,還有許多節點比如<environments>、<settings>、<typeAliases>等都大同小異,
第三步
這一步的主要目的就是通過之前初始化的SqlSessionFactory實作類來開啟一個SQL會話,
SqlSession sqlSession = sqlSessionFactory.openSession();
可以看出這里SqlSessionFactory實作類為DefaultSqlSessionFactory類,
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
我們知道SqlSession是我們與資料庫互動的頂級介面,所有的增刪改查都要通過SqlSession,所以進入DefaultSqlSessionFactory類的openSession方法,而openSession方法又呼叫了openSessionFromDataSource方法,
因為我們決議的XML主組態檔把所有的節點資訊都保存在了Configuration物件中,它開始直接獲得Environment節點的資訊,這個節點配置了資料庫的連接資訊和事務資訊,
之后通過Environment創建了一個事務工廠TransactionFactory,這里其實是實作類JdbcTransactionFactory,然后通過事務工廠實體化了一個事務物件Transaction,這里其實是實作類JdbcTransaction,在JdbcTransaction類中存有我們配置的資料庫環境相關資訊,例如資料源、資料庫隔離界別、資料庫連接以及是否自動提交事務,
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 通過Configuration物件獲取資料庫環境物件
final Environment environment = configuration.getEnvironment();
// 從資料庫環境物件中獲取事務工廠物件,呼叫方法在下面
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 根據資料庫環境資訊創建事務物件
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
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();
}
}
// 從資料庫環境物件中獲取事務工廠物件,如果沒有就新建一個
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
if (environment == null || environment.getTransactionFactory() == null) {
return new ManagedTransactionFactory();
}
return environment.getTransactionFactory();
}
// 省略其他內容...
}
默認情況下,不會傳入Connection引數,而是等到使用時才通過DataSource創建Connection物件,
public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
dataSource = ds;
level = desiredLevel;
autoCommit = desiredAutoCommit;
}
public Connection getConnection() throws SQLException {
// 使用時才創建Connection
if (connection == null) {
openConnection();
}
return connection;
}
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
setDesiredAutoCommit(autoCommit);
}
重點來了,最后他創建了一個執行器Executor ,我們知道SqlSession是與資料庫互動的頂層介面,SqlSession中會維護一個Executor來負責SQL生產和執行和查詢快取等,
由原始碼可知最終它是創建一個SqlSession實作類DefaultSqlSession,并且維護了一個Executor實體,
// DefaultSqlSessionFactory類中關于創建Excutor和SqlSession的代碼片段
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
// 維護了一個Executor實體
private final Executor executor;
private final boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
// 省略其他內容...
}
我們再來看看這個執行器的創建程序,其實就是判斷生成哪種執行器,defaultExecutorType默認指定使用SimpleExecutor,
MyBatis有三種的執行器:
-
SimpleExecutor(默認),
-
ReuseExecutor,
-
BatchExecutor,
SimpleExecutor:簡單執行器,
是MyBatis中默認使用的執行器,每執行一Update或Select,就開啟一個Statement物件(或PreparedStatement物件),用完就直接關閉Statement物件(或PreparedStatment物件),
ReuseExecutor:可重用執行器,
這里的重用指的是重復使用Statement(或PreparedStatement),它會在內部使用一個Map把創建的Statement(或PreparedStatement)都快取起來,每次執行SQL命令的時候,都會去判斷是否存在基于該SQL的Statement物件,如果存在Statement物件(或PreparedStatement物件)并且對應的Connection還沒有關閉的情況下就繼續使用之前的Statement物件(或PreparedStatement物件),并將其快取起來,
因為每一個SqlSession都有一個新的Executor物件,所以我們快取在ReuseExecutor上的Statement作用域是同一個SqlSession,
BatchExecutor:批處理執行器,
用于將多個SQL一次性輸出到資料庫,
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
// 選擇執行器
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/234987.html
標籤:Java
下一篇:spring事務相關
