mybatis查詢語(yǔ)句的背后揭秘
一、前言
在先了解mybatis查詢之前,先大致了解下以下代碼的為查詢做了哪些鋪墊,在這里我們要事先了解,myabtis會(huì)默認(rèn)使用DefaultSqlSessionFactory作為sqlSessionFactory的實(shí)現(xiàn)類,而sqlSession的默認(rèn)實(shí)現(xiàn)類為DefaultSqlSession
public static SqlSessionFactory getSessionFactory() throws IOException {
Reader reader = Resources.getResourceAsReader("mybatis/mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
return builder.build(reader);
}
獲取mybatis的配置文件流,交給sqlSessionFactoryBuilder進(jìn)行解析,在這里只會(huì)涉及到一部分,具體,請(qǐng)大家移步mybatis源碼進(jìn)行分析
解析大致步驟(以下說的配置文件,是mybatis配置數(shù)據(jù)庫(kù)連接信息的那個(gè)配置文件,不是mapper.xml文件)
解析配置文件的核心類在XMLConfigBuilder類中,
代碼如下
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
// 解析properties節(jié)點(diǎn)信息
propertiesElement(root.evalNode("properties"));
// 解析settings節(jié)點(diǎn)配置信息,其中二級(jí)緩存的總開關(guān)就是這里配置,當(dāng)然mybatis默認(rèn)是開啟的,詳細(xì)見Configuration類中的cacheEnabled屬性
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
// 解析別名
typeAliasesElement(root.evalNode("typeAliases"));
// 解析插件
pluginElement(root.evalNode("plugins"));
// 這個(gè)節(jié)點(diǎn)一般不進(jìn)行配置,myabtis也提供了一個(gè)默認(rèn)實(shí)現(xiàn)類DefaultObjectFactory,除非自定義對(duì)象工廠實(shí)現(xiàn),才需配置
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類型和數(shù)據(jù)庫(kù)類型的轉(zhuǎn)換,mybatis提供了許多默認(rèn)實(shí)現(xiàn),詳細(xì)見TypeHandlerRegistry類,如果需自定義,可在此節(jié)點(diǎn)中進(jìn)行配置
typeHandlerElement(root.evalNode("typeHandlers"));
// 這也是一個(gè)核心的配置,mapperElement方法會(huì)對(duì)mapper.xml文件內(nèi)容進(jìn)行一個(gè)解析
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
解析mapper.xml文件 的類XMLMapperBuilder,
public void parse() {
// 也就是檢測(cè)配置文件配置的mapper節(jié)點(diǎn)有沒有加載到configuration類中,防止重復(fù)加載
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
// 這個(gè)是綁定,mapper接口的,當(dāng)處理成功,在configuration類中的mapper注冊(cè)器中,會(huì)添加一個(gè)mapper
bindMapperForNamespace();
}
parsePendingResultMaps();// 解析resultMap節(jié)點(diǎn)
parsePendingCacheRefs(); // 解析緩存節(jié)點(diǎn),如<cache-ref/>
parsePendingStatements();// 解析select|update等節(jié)點(diǎn),并封裝成mappedStatement類
}
其中bindMapperForNamespace()方法的操作會(huì)導(dǎo)致以下結(jié)果
在configuration類中的MapperRegistry屬性中添加一個(gè)mapper,結(jié)果存儲(chǔ)在MapperRegistry類的一個(gè)map中,key為mapper的class value為一個(gè)代理工廠,負(fù)責(zé)產(chǎn)生mapper接口代理類。
二、查詢操作
當(dāng)我們使用要使用mybatis進(jìn)行查詢操作,無(wú)非大致就是兩種方式
/**
* 通過mapper接口形式查詢數(shù)據(jù)
*/
@Test
public void testSelectByMapper() throws IOException {
SqlSession sqlSession = MybatisUtil.getSessionFactory().openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.selectByPrimaryKey(10);
System.out.println(user);
sqlSession.close();
}
/**
* 通過mapper接口的全限定名來(lái)進(jìn)行查詢
* @throws IOException
*/
@Test
public void testSelectByString() throws IOException {
SqlSessionFactory sessionFactory = MybatisUtil.getSessionFactory();
SqlSession sqlSession = sessionFactory.openSession();
User user = sqlSession.selectOne("com.mybatis.demo.mybatisdemo.mapper.UserMapper.selectByPrimaryKey",10);
System.out.println(user);
sqlSession.close();
}
先來(lái)看第一種的分析,當(dāng)我們點(diǎn)擊getMapper進(jìn)去,它會(huì)去調(diào)用configuration類中g(shù)etMapper方法,就如上面介紹的解析出mapper節(jié)點(diǎn)后,會(huì)存儲(chǔ)在configuration類中的mapper注冊(cè)器中,
// defaultSqlSession類
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
//configuration類
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
// 最終獲取mapper對(duì)象的方法,其主要是創(chuàng)建一個(gè)mapper代理工廠,我們都知道m(xù)ybatis的mapper接口是沒有實(shí)現(xiàn)類的,
// 但是我們直接查詢是能獲取數(shù)據(jù),這里起作用的就是代理(采用的是jdk動(dòng)態(tài)代理)
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);
}
}
然后最終會(huì)經(jīng)過代理類MapperProxy的invoke方法,進(jìn)行返回結(jié)果。在這里為了更好的能理解這個(gè)類,舉個(gè)例子,步驟如下
先創(chuàng)建一個(gè)接口,再使用一個(gè)類去實(shí)現(xiàn)java的jdk代理的核心接口InvocationHandler,
public interface TestMapper {
User findByUserId(Integer id);
}
public class MapperProxyTest implements InvocationHandler {
private Class<?> target;
public MapperProxyTest(Class<?> target) {
this.target = target;
}
public Object getProxyInstances(){
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class[]{target},this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
User user = new User();
user.setPassword("123");
user.setUsername("李四");
user.setAddress("123");
user.setRegistertime(new Date());
user.setCellphone("1111111");
user.setAge(25);
return user;
}
}
測(cè)試類
public class MapperTest {
public static void main(String[] args){
MapperProxyTest proxyTest = new MapperProxyTest(TestMapper.class);
TestMapper testMapper = (TestMapper) proxyTest.getProxyInstances();
System.out.println(testMapper.findByUserId(10));
}
}
執(zhí)行結(jié)果
User{id=null, username='李四', password='123', age=25, address='123', cellphone='1111111', registertime=Sat Mar 09 15:02:16 CST 2019}
由上面例子也可以看出最終結(jié)果是在invoke方法內(nèi),同理在mybatis中的MapperProxy的invoke方法也是負(fù)責(zé)返回最終結(jié)果的
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 交給了mpperMethod類去處理
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
mapperMethod類中有兩個(gè)重要屬性,也就是它的內(nèi)部類,

也可以很清楚的了解到SqlCommand是用來(lái)存儲(chǔ)當(dāng)前執(zhí)行方法的信息,如全限定名,還有該方法是屬于select|update|delete|insert|flush的哪一種,
對(duì)于methodSignature,則是紀(jì)錄該方法的一些信息,如返回值類型,參數(shù)等信息,paramNameResolver處理mapper接口中的參數(shù),下面代碼中有一個(gè)大致的介紹,以后會(huì)做一個(gè)詳細(xì)的介紹,這里只貼下代碼,只針對(duì)select做介紹
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
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()) {// 返回值為void類型,但是有ResultHandler參數(shù),并且只能有一個(gè),不然會(huì)報(bào)錯(cuò)
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {// 處理返回值類型為集合類型或者數(shù)組類型
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {//處理返回值類型為Map類型
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {//返回值是否為cursor類型
result = executeForCursor(sqlSession, args);
} else {//其他類型
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;
}
這里只介紹select部分中常用返回多個(gè)實(shí)例對(duì)象的情況,也就是返回值為集合類型。
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
// 將mapper接口的參數(shù)名稱和args整成一個(gè)map結(jié)構(gòu),最后在會(huì)將值賦給sql中對(duì)應(yīng)的變量
// 在3.5版本中,默認(rèn)的mapper結(jié)構(gòu)(假如沒使用@param注解或者處于jdk1.8版本中在代碼編譯時(shí)加上 -parameters 參數(shù)),結(jié)構(gòu)為
// param1 -> args[0] param2 -> args[1]
// arg0 -> args[0] arg1 -> args[1] mybatis之前有些版本不是arg0 而是0 1 。。數(shù)字代替。
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {// 處理參數(shù)中帶有rowBounds參數(shù)
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {// 其它情況
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
// 說明返回類型不是集合List類型,而是數(shù)組類型或其它集合類型。
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
從上面知道,最終還是回到了sqlSession里面,
@Override
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存儲(chǔ)的其實(shí)就是對(duì)每一個(gè)select|update|delete|insert 標(biāo)簽的解析結(jié)果
關(guān)于MappedStatement是怎么解析得來(lái)的,又是怎么存儲(chǔ)在Configuration中,可沿著以下路線進(jìn)行查看
SqlSessionFactoryBuilder ---> build方法
XMLConfigBuilder ----> parse、parseConfiguration、mapperElement方法
XMLMapperBuilder ----> parse、parsePendingStatements、parseStatementNode
MapperBuilderAssistant ----> addMappedStatement
這里不做過多介紹,詳情見源碼
在selectList中executor的默認(rèn)實(shí)現(xiàn)類是,SimpleExecutor,不過它還由Configuration類中的一個(gè)屬性決定最后的類型,
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);
}
// 如果cacheEnabled為true,其實(shí)這個(gè)屬性默認(rèn)為true的,
// 則由CachingExecutor進(jìn)行包裝,也就是常說的裝飾設(shè)計(jì)模式
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
最后回到selectList中來(lái),由此可見,調(diào)用了CachingExecutor類中的query方法來(lái)執(zhí)行。
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 如果不為空,則啟用了二級(jí)緩存
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);
}
關(guān)于二級(jí)緩存,相信熟悉的都清楚,要想使用它,需要?jiǎng)觾蓚€(gè)地方,
一個(gè)是mybatis的配置文件,將cacheEnabled設(shè)置為true,其實(shí)mybatis對(duì)這個(gè)屬性的默認(rèn)值就是true,所以二級(jí)緩存的總開關(guān)是打開的。
第二個(gè)就是在mpper.xml文件中使用 <cache/> 或<cache-ref/>
這里對(duì)緩存不做介紹。
然后調(diào)用了BaseExecutor的query方法,這個(gè)類起的作用就是對(duì)一級(jí)緩存進(jìn)行了操作,最終調(diào)用了SimpleExecutor的doQuery方法進(jìn)行查詢。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
- MyBatis中的模糊查詢語(yǔ)句
- Mybatis查詢語(yǔ)句結(jié)果集的總結(jié)大全
- Mybatis模糊查詢和動(dòng)態(tài)sql語(yǔ)句的用法
- mybatis查詢語(yǔ)句揭秘之封裝數(shù)據(jù)
- mybatis查詢語(yǔ)句揭秘之參數(shù)解析
- Mybatis傳遞多個(gè)參數(shù)進(jìn)行SQL查詢的用法
- 詳解MyBatis直接執(zhí)行SQL查詢及數(shù)據(jù)批量插入
- Mybatis查不到數(shù)據(jù)查詢返回Null問題
- Mybatis實(shí)現(xiàn)增刪改查及分頁(yè)查詢的方法
- Mybatis多表關(guān)聯(lián)查詢的實(shí)現(xiàn)(DEMO)
相關(guān)文章
Java實(shí)現(xiàn)調(diào)用第三方相關(guān)接口
最近在做一個(gè)項(xiàng)目,需要調(diào)用第三方接口,本文主要介紹了Java實(shí)現(xiàn)調(diào)用第三方相關(guān)接口,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09
Java的深拷貝與淺拷貝的幾種實(shí)現(xiàn)方式
這篇文章主要介紹了Java的深拷貝與淺拷貝的幾種實(shí)現(xiàn)方式,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
SpringBoot集成Swagger2構(gòu)建在線API文檔的代碼詳解
這篇文章主要介紹了SpringBoot集成Swagger2構(gòu)建在線API文檔,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12
Echarts+SpringMvc顯示后臺(tái)實(shí)時(shí)數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了Echarts+SpringMvc顯示后臺(tái)實(shí)時(shí)數(shù)據(jù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12
SpringCloud Gateway 路由配置定位原理分析
本節(jié)主要了解系統(tǒng)中的謂詞與配置的路由信息是如何進(jìn)行初始化關(guān)聯(lián)生成路由對(duì)象的。每個(gè)謂詞工廠中的Config對(duì)象又是如何被解析配置的2021-07-07
Java實(shí)現(xiàn)在線聊天室(層層遞進(jìn))
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)在線聊天室,層層遞進(jìn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-09-09

