1、概述
目前工作中,直接使用mybatis原生API開發(fā)的場(chǎng)景很少聂受,基本都是結(jié)合spring一起使用饥悴。但對(duì)于分析mybatis的源碼來說坦喘,使用API的方式能更容易的理清思路。先介紹下原生API的使用方式西设。
public static void main(String[] args) {
String resource = "configuration.xml";
Reader reader;
try {
reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
Role role = roleMapper.getRole(1);
System.out.println(role.getLfPartyId() + "," + role.getPartyName());
} finally {
session.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
其中比較重要的類有SqlSessionFactoryBuilder
瓣铣、SqlSessionFactory
、SqlSession
贷揽、RoleMapper
棠笑。下面先簡(jiǎn)要介紹下他們的使用范圍與作用。
-
SqlSessionFactoryBuilder
SqlSessionFactoryBuilder
是典型的建造者模式的應(yīng)用禽绪,一般他的生命周期就是存在于方法中腐晾,主要作用就是根據(jù)mybatis的全局配置文件(這里不對(duì)mybatis的配置文件做過多介紹,比較簡(jiǎn)單)構(gòu)建出SqlSessionFactory
對(duì)象丐一,同時(shí)會(huì)建立好Configuration
對(duì)象(該對(duì)象及其重要藻糖,貫穿整個(gè)mybatis的生命周期,后面會(huì)重點(diǎn)介紹)库车。 -
SqlSessionFactory
SqlSessionFactory
是典型的工廠模式巨柒,用于生成SqlSession
對(duì)象,一般全局單例柠衍。SqlSessionFactory
是個(gè)接口洋满,有兩個(gè)實(shí)現(xiàn)類DefaultSqlSessionFactory
、SqlSessionManager
珍坊,系統(tǒng)默認(rèn)使用DefaultSqlSessionFactory
牺勾。 -
SqlSession
SqlSession
可以簡(jiǎn)單理解為JDBC的connect對(duì)象泌辫,執(zhí)行SQL方法(具體其實(shí)是Executor對(duì)象執(zhí)行的sql方法纫普,具體后面會(huì)分析),生命周期也是方法級(jí)別悼嫉。SqlSession
也是個(gè)接口履怯,有兩個(gè)實(shí)現(xiàn)類DefaultSqlSession
回还、SqlSessionManager
,系統(tǒng)默認(rèn)使用DefaultSqlSession
叹洲。 -
RoleMapper
RoleMapper
接口是一整個(gè)Mapper接口的代表柠硕,它沒有實(shí)現(xiàn)類,作用是發(fā)送SQL(幫助SqlSession
找到SQL)运提,生命周期與SqlSession
一致蝗柔。
總結(jié):從上面簡(jiǎn)短的介紹就可以看出mybatis框架設(shè)計(jì)的巧妙闻葵,充分體現(xiàn)了依賴倒置原則(面向接口編程)。
2癣丧、從初始化開始說起
從上面概述的原生API的使用代碼片段可以看出笙隙,mybatis的初始化,是從構(gòu)建SqlSessionFactory
對(duì)象開始的坎缭,那我們就進(jìn)入到SqlSessionFactoryBuilder
內(nèi)部,看看SqlSessionFactory
對(duì)象以及重要的Configuration
對(duì)象是如何被構(gòu)建出來的签钩。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
....
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
首先解析XML文件構(gòu)建出Configuration
對(duì)象掏呼,然后將該對(duì)象設(shè)置為DefaultSqlSessionFactory
對(duì)象的屬性后返回。這里可以很明顯的看出铅檩,系統(tǒng)確實(shí)默認(rèn)是使用的DefaultSqlSessionFactory
對(duì)象憎夷。這里先分析下Configuration
對(duì)象是如何構(gòu)建的,然后再分析DefaultSqlSessionFactory
對(duì)象的細(xì)節(jié)昧旨。
2.0 Configuration
概述
在介紹Configuration
對(duì)象是如何構(gòu)建之前拾给,我們先簡(jiǎn)單介紹下Configuration
對(duì)象的作用。簡(jiǎn)單理解兔沃,Configuration
對(duì)象其實(shí)就是mybatis的XML配置文件對(duì)應(yīng)的Java類表示形式(類似的有tomact的XML配置文件和spring的XML配置文件蒋得,他們都是會(huì)解析成對(duì)應(yīng)的Java類)。在SqlSessionFactoryBuilder
類內(nèi)部會(huì)解析XML文件乒疏,相應(yīng)的節(jié)點(diǎn)都會(huì)被解析成Configuration
的相關(guān)屬性额衙。主要有如下的屬性解析過程:
- properties全局參數(shù)
- settings設(shè)置
- typeAliases別名
- typeHandler類型處理器
- ObjectFactory對(duì)象(很少使用)
- plugin插件
- environment環(huán)境(與spring結(jié)合后,由spring來管理環(huán)境的切換)
- DatabaseIdProvider數(shù)據(jù)庫(kù)標(biāo)識(shí)(很少使用)
- Mapper映射器
其中我們分析的重點(diǎn)是第9步怕吴,后面會(huì)詳細(xì)分析窍侧,前面的8步比較簡(jiǎn)單,在后面稍微提及转绷。
2.1 Configuration
構(gòu)建過程
在上面的代碼中伟件,我們已經(jīng)看到,Configuration
對(duì)象是通過XMLConfigBuilder
對(duì)象構(gòu)建而成的议经,分析下該對(duì)象的初始化及其parse
方法的具體實(shí)現(xiàn)斧账,即可明白Configuration
對(duì)象的構(gòu)建過程。
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;
}
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
我們發(fā)現(xiàn)直接調(diào)用了new Configuration()
方法煞肾,實(shí)例化了Configuration
對(duì)象其骄,然后在parse
方法中通過parseConfiguration
方法解析"/configuration"節(jié)點(diǎn)來實(shí)現(xiàn)對(duì)Configuration
對(duì)象的屬性填充。再看看該方法的實(shí)現(xiàn)
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
// Configuration類中有typeAliasRegistry屬性扯旷,是對(duì)HashMap的簡(jiǎn)單封裝拯爽,里面存儲(chǔ)alias->Class的關(guān)聯(lián)關(guān)系
typeAliasesElement(root.evalNode("typeAliases"));
// Configuration類中有InterceptorChain屬性,是對(duì)ArrayList的簡(jiǎn)單封裝钧忽,里面存儲(chǔ)所有的插件實(shí)體對(duì)象毯炮,內(nèi)部有個(gè)pluginAll方法逼肯,通過層層包裝的方式來實(shí)現(xiàn)插件的調(diào)用
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// Configuration類中有typeHandlerRegistry屬性,內(nèi)部有多個(gè)HashMap的屬性桃煎,里面存儲(chǔ)JDBC type到Java type的類型轉(zhuǎn)換規(guī)則
typeHandlerElement(root.evalNode("typeHandlers"));
// Configuration類中有mapperRegistry屬性篮幢,是對(duì)HashMap的簡(jiǎn)單封裝,里面存儲(chǔ)Mapper接口到`MapperProxyFactory`的映射關(guān)系
// 解析的同時(shí)也會(huì)向mappedStatements屬性添加值为迈,該屬性也是對(duì)HashMap的簡(jiǎn)單封裝三椿,里面存儲(chǔ)namespace+id 與MappedStatement對(duì)象的映射關(guān)系,后者就是mapper.xml文件中的select葫辐、insert搜锰、update、delete等SQL語句的節(jié)點(diǎn)耿战。
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
代碼都做了相關(guān)注釋蛋叼,內(nèi)部實(shí)現(xiàn)也都不是很復(fù)雜(需要說明的是在settingsElement(settings);
方法中,會(huì)根據(jù)配置的defaultExecutorType屬性設(shè)置Executor的類型剂陡,默認(rèn)是Simple狈涮,configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
),
其次一個(gè)較為復(fù)雜且最為重要的就是mapperElement(root.evalNode("mappers"));
方法鸭栖,即是我們上面說的到第9步歌馍。我們看看他的實(shí)現(xiàn)。
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
// resource晕鹊、url骆姐、class三個(gè)屬性不可同時(shí)存在
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
目前大部分都是使用package的方式來批量導(dǎo)入mapper的XML文件,所以我們重點(diǎn)分析他捏题,其他的導(dǎo)入方式在底層原理是類似的玻褪。重點(diǎn)是這段代碼configuration.addMappers(mapperPackage);
,它最終會(huì)調(diào)用到MapperRegistry
類的addMapper
方法中公荧。
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
// knownMappers.containsKey判斷是否存在
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 綁定mapper接口與代理類工廠的映射關(guān)系带射,這是mapper接口能直接使用的關(guān)鍵
// 這里mapper接口的代理類還沒實(shí)例化,只是綁定了工廠類循狰,代理類的實(shí)例化在sqlSession.getMapper()方法中實(shí)例化
knownMappers.put(type, new MapperProxyFactory<>(type));
// 下面兩行代碼會(huì)具體實(shí)現(xiàn)上文提到了mappedStatements屬性填充
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
再看MapperAnnotationBuilder
類的parse
方法
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
// 解析mapper的XML文件
loadXmlResource();
// 下面是解析mapper接口中的注解信息窟社,這種方式對(duì)代碼的侵入性比較大,現(xiàn)階段很少使用绪钥,基本可以不用關(guān)注
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
loadXmlResource();
方法會(huì)調(diào)用到XMLMapperBuilder
類的parse()
方法灿里,
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
// 解析resultMap
parsePendingResultMaps();
// 解析cache
parsePendingCacheRefs();
// 解析SQL語句
parsePendingStatements();
}
在該方法里,我們重點(diǎn)關(guān)注parsePendingStatements()
程腹,在其內(nèi)部會(huì)調(diào)用XMLStatementBuilder
類的parseStatementNode()
方法
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
這個(gè)方法就是重頭戲了匣吊,解析mapper的XML文件,將XML中的select、delete色鸳、update社痛、insert等節(jié)點(diǎn)解析成MappedStatement對(duì)象,并緩存到Configure類中命雀。其中不同的節(jié)點(diǎn)會(huì)匹配不同的statementType蒜哀,SQL語句會(huì)封裝到SqlSource對(duì)象中,默認(rèn)是DynamicSqlSource
實(shí)現(xiàn)類吏砂。
至此整個(gè)configuration對(duì)象的初始化過程中的重要階段就介紹完畢了撵儿。configuration對(duì)象在整個(gè)mybatis運(yùn)行過程中的每個(gè)階段都有它的影子,這是一個(gè)非常重的對(duì)象狐血。對(duì)其中的一些比較重要的屬性的理解淀歇,直接關(guān)系到對(duì)整個(gè)mybatis的運(yùn)行機(jī)制的理解。所以這里再簡(jiǎn)單總結(jié)下configuration對(duì)象中比較重要的一些屬性氛雪。
// 記錄Mapper接口與代理工廠類的綁定關(guān)系
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
// 責(zé)任鏈模式,記錄所有的插件
protected final InterceptorChain interceptorChain = new InterceptorChain();
// 記錄所有的類型轉(zhuǎn)換器
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
// 記錄所有的類型簡(jiǎn)稱
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
// 記錄所有的namespace+id與MappedStatement的綁定關(guān)系耸成,其中MappedStatement內(nèi)部關(guān)聯(lián)具體的SQL語句
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
2.2 SqlSessionFactory
簡(jiǎn)介
在之前的分析中报亩,我們已經(jīng)知道,SqlSessionFactoryBuilder
對(duì)象井氢,默認(rèn)是實(shí)例化了DefaultSqlSessionFactory
對(duì)象弦追,我們先看下該對(duì)象的初始化過程
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
很簡(jiǎn)單,DefaultSqlSessionFactory
對(duì)象內(nèi)部持有Configuration
對(duì)象的引用花竞,初始化過程就是完成該屬性的復(fù)制劲件。下面再分析下其獲取SqlSession
對(duì)象的過程
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
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();
}
}
這里我們可以看到,mybatis默認(rèn)確實(shí)是使用的DefaultSqlSession
實(shí)現(xiàn)類约急。其中ExecutorType在上面已經(jīng)介紹過零远,默認(rèn)使用的Simple。SqlSession
對(duì)象持有兩個(gè)比較重要的對(duì)象的引用厌蔽,Configuration
和Executor
牵辣,Configuration
我們?cè)谇懊嬉呀?jīng)介紹過了,Executor
我們?cè)诤竺嬖僭敿?xì)介紹奴饮。
SqlSession
就簡(jiǎn)單介紹到這里纬向,具體的使用原理,在下面介紹Mapper接口的時(shí)候再詳細(xì)說明戴卜。
3逾条、Mapper接口的調(diào)用過程
我們知道在Java中,接口方法是沒法調(diào)用的投剥,在Mybatis中卻很奇怪的可以調(diào)用师脂,且能正常運(yùn)行。這其中是什么原理呢?
這其實(shí)就是代理在其中作祟了危彩。我們從概述介紹的代碼片段中的RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
代碼行開始攒磨,介紹Mapper接口的調(diào)用過程。我們知道汤徽,Mybatis默認(rèn)是使用DefaultSqlSession娩缰,所以我們就從該類的getMapper方法入手。
// DefaultSqlSession.class
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
// Configuration.class
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
// MapperRegistry.class
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);
}
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
如上代碼可以看出谒府,DefaultSqlSession的getMapper方法拼坎,通過層層委托,最終到了MapperRegistry類的getMapper方法完疫,該類中有個(gè)knownMappers屬性泰鸡,在上面介紹初始化過程中已經(jīng)介紹過,里面存儲(chǔ)著type接口與MapperProxyFactory類的映射關(guān)系壳鹤。最終我們得知盛龄,getMapper方法返回了type的JDK動(dòng)態(tài)代理對(duì)象。其中MapperProxy
類實(shí)現(xiàn)了InvocationHandler
接口芳誓,我們?cè)诤竺嬲{(diào)用接口方法時(shí)余舶,其實(shí)最終都會(huì)調(diào)用該類的invoke方法(其實(shí)就是最簡(jiǎn)單的JDK動(dòng)態(tài)代理技術(shù),這里就不詳細(xì)介紹了)锹淌。我們就看下該方法的具體實(shí)現(xiàn)吧
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// Object類中的方法匿值,比如wait、notify等不做代理赂摆,直接調(diào)用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) { // JDK8中接口有默認(rèn)方法實(shí)現(xiàn)挟憔,也不做代理,直接調(diào)用
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 對(duì)method和MapperMethod關(guān)聯(lián)值做了緩存
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
其中烟号,MapperMethod
對(duì)象有兩個(gè)內(nèi)部類绊谭,這里得介紹下:
-
SqlCommand
根據(jù)傳入的configuration
、mapperInterface
汪拥、method
可以獲取該method對(duì)象具體需要執(zhí)行的sql語句龙誊。這里就用到了上面初始化過程中介紹的configuration
類的一個(gè)重要屬性mappedStatements
(內(nèi)部保存了namespace+id-->MappedStatement的映射關(guān)系)
public static class SqlCommand {
private final String name;
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
if(method.getAnnotation(Flush.class) != null){
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
public String getName() {
return name;
}
public SqlCommandType getType() {
return type;
}
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
String statementId = mapperInterface.getName() + "." + methodName;
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
return null;
}
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}
-
MethodSignature
根據(jù)傳入的configuration
、mapperInterface
喷楣、method
可以獲取該方法的返回值類型趟大,參數(shù)解析器等信息,這里就不詳細(xì)分析铣焊。
如此我們知道逊朽,Mapper接口的方法調(diào)用,最終會(huì)執(zhí)行MapperMethod
對(duì)象的execute
方法中曲伊。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
// 1
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
// 2
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;
...
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;
}
方法有點(diǎn)長(zhǎng)叽讳,我們這里就以select類型作為分析對(duì)象追他。首頁根據(jù)方法的返回值類型決定具體的實(shí)現(xiàn)方式,其中標(biāo)注的1和2分別表示返回?cái)?shù)組和返回普通對(duì)象(這里不是很嚴(yán)謹(jǐn))岛蚤,這兩個(gè)分支是調(diào)用頻率最高的邑狸,我們這里以1位代表,分析下執(zhí)行的過程涤妒。其中executeForMany
方法最終會(huì)調(diào)用到DefaultSqlSession
類的selectList
方法中单雾,如下:
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
MappedStatement
對(duì)象包含有具體需要執(zhí)行的SQL語句,executor
對(duì)象是在DefaultSqlSessionFactory
調(diào)用openSession
方法時(shí)構(gòu)建成功的她紫,默認(rèn)是SimpleExecutor
對(duì)象(目前大部分的項(xiàng)目不會(huì)開啟Mybatis的二級(jí)緩存硅堆,否則這里默認(rèn)會(huì)是CachingExecutor
對(duì)象,其實(shí)就是對(duì)SimpleExecutor
對(duì)象的裝飾)贿讹,并注入到DefaultSqlSession
對(duì)象的屬性中渐逃。(這里的executor
對(duì)象已經(jīng)是被插件代理過后的對(duì)象了)。
4民褂、總結(jié)
對(duì)于初始化的過程茄菊,就不做總結(jié)了。比較復(fù)雜的過程就是configuration
對(duì)象的構(gòu)建比較復(fù)雜赊堪,因?yàn)檫@個(gè)對(duì)象太重了面殖,但是在使用的過程中,我們基本是感知不到這個(gè)對(duì)象的存在的雹食。這也是寫一個(gè)框架并能讓大多數(shù)人能接受這個(gè)框架的需要注意的點(diǎn)畜普,將復(fù)雜留給自己期丰,將簡(jiǎn)單給與用戶群叶。
這里主要總結(jié)下Mapper接口具體是如何運(yùn)行并能執(zhí)行SQL拿到數(shù)據(jù)的過程。
- 調(diào)用SqlSession的getMapper方法
- 委托給MapperRegistry對(duì)象的getMapper方法钝荡,通過type拿到MapperProxyFactory對(duì)象街立,然后該對(duì)象會(huì)返回一個(gè)MapperProxy對(duì)象,作為Mapper接口的代理類埠通。
- 調(diào)用Mapper接口的方法赎离,會(huì)知道到MapperProxy對(duì)象的invoke方法中,該方法會(huì)委托給MapperMethod對(duì)象的execute方法
- MapperMethod對(duì)象的execute方法會(huì)根據(jù)MapperMethod對(duì)象的command屬性的type值決定決定SQL的執(zhí)行類型端辱。這里已select為例梁剔,分析executeForMany方法。
- executeForMany方法會(huì)再委托給SqlSession的selectList方法舞蔽,且傳入command屬性的name值即Mapper接口名+方法名荣病。
- SqlSession的selectList方法會(huì)委托給executor類的query方法去執(zhí)行具體的SQL
SQL的具體執(zhí)行又涉及到Executor
、StatementHandler
渗柿、ResultSetHandler
个盆、ParameterHandler
四個(gè)重要的類,下次與mybatis的插件機(jī)制一起分析。