@[toc]
## Mybatis查詢語句sql拼裝源碼解析
### 帶著問題學習源碼(從加載mapper到sql拼裝)
#### 問題現(xiàn)象
**后端用Integer接收0傳入param.pushStatus,為什么param.pushStatus !=''判斷為false**
后端使用Integer接收
![\[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-kgpQR0NO-1650516052826)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/d896a8c5d9e669c7b1c36a1e4a1610d7)\]](https://img-blog.csdnimg.cn/b4a279b8e4ce4c61a62bde1c1ebc7ff8.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_12,color_FFFFFF,t_70,g_se,x_16)![\[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-yhqAzZE8-1650516052827)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/9602944814709df16db8d90d17c9a5d3)\]](https://img-blog.csdnimg.cn/8c8537f9ef384c7f898cb99774756a25.png)
#### 原因
mapper接口傳入的參數(shù)類型為Integer值為0時棘钞,mybaits 在進行 **param.pushStatus !=''**的時候會默認<font color='red'>""和0 都轉(zhuǎn)換成double進行比較 都是0.0 </font> 乔夯,結果不是重點端蛆,重點在于下面過程砚哗。
#### 源碼解析(Mybatis-plus)
Mybatis-plus很多類重寫了Mybatis,此處以Mybatis-plus源碼出發(fā)
##### 1脯倒、加載SqlSessionFactory
項目啟動會通過springboot的自動裝配原理加載MybatisPlusAutoConfiguration從而加載SqlSessionFactory(加載mapper到MybatisSqlSessionFactoryBean)
```java
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
? ? ? ? MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
? ? ? ? factory.setDataSource(dataSource);
? ? ? ? ...
? ? ? ? if (StringUtils.hasText(this.properties.getConfigLocation())) {
//下面的ConfigLocation為:classpath:mybatis/mybatis-config.xml
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
? ? ? ? }
? ? ? ? ...
//這里是裝載插件(之后會加入責任鏈中)適用于慢sql查詢攔截器等
? ? ? ? if (!ObjectUtils.isEmpty(this.interceptors)) {
? ? ? ? ? ? factory.setPlugins(this.interceptors);
? ? ? ? }
...
//...注入很多屬性比如:自定義枚舉包办悟、注入主鍵生成器躬厌、注入sql注入器、注入ID生成器等
factory.getObject();
}
//factory.getObject()--會進行后置屬性設置(MybatisSqlSessionFactoryBean)
```
```java
//MybatisSqlSessionFactoryBean
//這里關注mapper的設置
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
? ? ? ? MybatisXMLConfigBuilder xmlConfigBuilder = null;
? ? ? ? if (this.configuration != null) {
? ? ? ? ? ? ...
? ? ? ? } else if (this.configLocation != null) {
? ? ? ? ? ? // this.configLocation 里面包括mybatis/mybatis-config.xml
//如果在配置文件有<mapper>標簽解析
? ? ? ? ? ? xmlConfigBuilder = new MybatisXMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
? ? ? ? ? ? targetConfiguration = xmlConfigBuilder.getConfiguration();
? ? ? ? } else {
? ? ? ? ? ? ...
? ? ? ? }
? ? ......
? ? ? ? if (xmlConfigBuilder != null) {
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? //第一種--解析配置文件mybatis-config.xml信息
? ? ? ? ? ? ? ? xmlConfigBuilder.parse();
? ? ? ? }
? ? ......
//mapperLocations是提前掃描自定義的classpath:mapper/*.xml文件
? ? ? ? //例如:file [E:\code\trade\trade\target\classes\mapper\SellReconciliationMapper.xml]
if (this.mapperLocations != null) {
? ? ? ? ? ? if (this.mapperLocations.length == 0) {
? ? ? ? ? ? ? ...
? ? ? ? ? ? } else {
//循環(huán)遍歷所有的xxxx.xml
? ? ? ? ? ? ? ? for (Resource mapperLocation : this.mapperLocations) {
? ? ? ? ? ? ? ? ? ? if (mapperLocation == null) {
? ? ? ? ? ? ? ? ? ? ? ? continue;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
? ? ? ? ? ? ? ? ? ? ? ? ? ? targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
? ? ? ? ? ? ? ? ? ? ? ? //這里解析mapper
? ? ? ? ? ? ? ? ? ? ? ? xmlMapperBuilder.parse();
? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
}
```
###### xmlConfigBuilder.parse();
```java
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 {
//將mybatis-config.xml通過XPath解析成XNode再進行解析
? ? ? ? ? ? propertiesElement(root.evalNode("properties"));
? ? ? ? ? ? Properties settings = settingsAsProperties(root.evalNode("settings"));
? ? ? ? ? ? loadCustomVfs(settings);
? ? ? ? ? ? loadCustomLogImpl(settings);
? ? ? ? ? ? typeAliasesElement(root.evalNode("typeAliases"));
? ? ? ? ? ? pluginElement(root.evalNode("plugins"));
? ? ? ? ? ? objectFactoryElement(root.evalNode("objectFactory"));
? ? ? ? ? ? objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
? ? ? ? ? ? reflectorFactoryElement(root.evalNode("reflectorFactory"));
? ? ? ? ? ? settingsElement(settings);
? ? ? ? ? ? environmentsElement(root.evalNode("environments"));
? ? ? ? ? ? databaseIdProviderElement(root.evalNode("databaseIdProvider"));
? ? ? ? ? ? typeHandlerElement(root.evalNode("typeHandlers"));
//這里可以配置mapper
? ? ? ? ? ? mapperElement(root.evalNode("mappers"));
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
? ? ? ? }
? ? }
```
###### xmlMapperBuilder.parse();
```java
public void parse() {
? ? if (!configuration.isResourceLoaded(resource)) {
//前面已經(jīng)獲取了SellReconciliationMapper.xml狠裹,通過命名空間解析mapper
? ? ? configurationElement(parser.evalNode("/mapper"));
? ? ? configuration.addLoadedResource(resource);
? ? ? //再通過命名空間綁定mapper
? ? ? bindMapperForNamespace();
? ? }
//解析ResultMap等屬性
? ? parsePendingResultMaps();
? ? parsePendingCacheRefs();
? ? parsePendingStatements();
? }
private void bindMapperForNamespace() {
? ? //com.jdh.trade.mapper.SellReconciliationMapper
? ? String namespace = builderAssistant.getCurrentNamespace();
? ? if (namespace != null) {
? ? ? Class<?> boundType = null;
? ? ? try {
? ? ? ? //獲取命名空間獲取mapper接口
? ? ? ? boundType = Resources.classForName(namespace);
? ? ? }
? ? ? if (boundType != null && !configuration.hasMapper(boundType)) {
? ? ? ? //Spring可能不知道真正的資源名虽界,所以我們設置了一個標志
//防止從映射器接口再次加載這個資源
? ? ? ? configuration.addLoadedResource("namespace:" + namespace);
? ? ? ? //關鍵這里將mapper增加到map中
? ? ? ? configuration.addMapper(boundType);
? ? ? }
? ? }
? }
//key mapper接口 value mapper代理工廠
//private final Map<Class<?>, MybatisMapperProxyFactory<?>> knownMappers = new HashMap<>();
@Override
? ? public <T> void addMapper(Class<T> type) {
? ? ? ? if (type.isInterface()) {
? ? ? ? ? ? ...
? ? ? ? ? ? boolean loadCompleted = false;
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? //!!!!保存到map!!!!
? ? ? ? ? ? ? ? knownMappers.put(type, new MybatisMapperProxyFactory<>(type));
? ? ? ? ? ? ? ? //在運行解析器之前添加類型是很重要的否則,將自動嘗試綁定映射器解析器涛菠。如果類型已經(jīng)知道莉御,則不會嘗試.
? ? ? ? ? ? ? ? MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
? ? ? ? ? ? ? ? parser.parse();
? ? ? ? ? ? ? ? loadCompleted = true;
? ? ? ? ? ? }...
? ? }
```
##### 2、mapper接口生成代理對象
@service 加載bean之后會通過
doGetObjectFromFactoryBean方法中執(zhí)行factory.getObject()獲取到bean實例MybatisMapperProxy俗冻。
```java
//MapperFactoryBean
//對于mybatis相關的mapper
? @Override
? public T getObject() throws Exception {
? ? ? //this.mapperInterface 相當于接口com.jdh.trade.mapper.xxxMapper
? ? return getSqlSession().getMapper(this.mapperInterface);
? }
//SqlSessionTemplate
? @Override
? public <T> T getMapper(Class<T> type) {
? ? return getConfiguration().getMapper(type, this);
? }
//MybatisMapperRegistry
@Override
? ? public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
? ? ? ? //knownMappers 這個map是上面保存的
? ? ? ? final MybatisMapperProxyFactory<T> mapperProxyFactory = (MybatisMapperProxyFactory<T>) knownMappers.get(type);
...
? ? ? ? try {
? ? ? ? ? ? //生成代理對象
? ? ? ? ? ? return mapperProxyFactory.newInstance(sqlSession);
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? throw new BindingException("Error getting mapper instance. Cause: " + e, e);
? ? ? ? }
? ? }
//MybatisMapperProxyFactory
//mapperProxyFactory.newInstance(sqlSession);
protected T newInstance(MybatisMapperProxy<T> mapperProxy) {
? ? //Proxy 生成動態(tài)代理實例
? ? ? ? return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
? ? }
? ? public T newInstance(SqlSession sqlSession) {
? ? ? ? final MybatisMapperProxy<T> mapperProxy = new MybatisMapperProxy<>(sqlSession, mapperInterface, methodCache);
? ? ? ? return newInstance(mapperProxy);
? ? }
```
##### 3礁叔、調(diào)用查詢方法
![\[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-t4H8ej0Q-1650516052829)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/3f07629179b2a67f00a75a1c969ca3ad)\]](https://img-blog.csdnimg.cn/46df55196bb443e1915cd7f1609898fd.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_20,color_FFFFFF,t_70,g_se,x_16)
執(zhí)行mapper的查詢等語句就會進入代理對象的invoke方法
![\[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-z0AArxR6-1650516052829)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/4639d1b1bcbc912c3f7c5a2353ad3da6)\]](https://img-blog.csdnimg.cn/9d4560591b2e4285836cff47aa7a9e2b.png)
InvocationHandler接口是proxy代理實例的調(diào)用處理程序?qū)崿F(xiàn)的一個接口,每一個proxy代理實例都有一個關聯(lián)的調(diào)用處理程序迄薄;在代理實例調(diào)用方法時琅关,方法調(diào)用被編碼分派到調(diào)用處理程序的invoke方法。
MybatisMapperProxy
![\[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-43rqFH0W-1650516052830)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/50c83add18d67470cdd256f3a1fe4546)\]](https://img-blog.csdnimg.cn/ba4cb1f79fab492b88d5ce22574bc813.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_20,color_FFFFFF,t_70,g_se,x_16)
從緩存獲取MybatisMapperMethod-屬性如下
![\[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-YaYw3jeo-1650516052830)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/d4fe14619bcf0e7cfef5d3c509f65069)\]](https://img-blog.csdnimg.cn/2a3241c6c4644e1daf7f0cd53c300c61.png)
![\[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-fDPYeN03-1650516052831)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/3e0d6569d357998f88a74fbe6521245a)\]](https://img-blog.csdnimg.cn/00ac9576c2a24ada9bdff1302839a088.png)
![\[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-yE1T20pP-1650516052831)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/13a45f906187eca9674e49b990f29602)\]](https://img-blog.csdnimg.cn/194138e438d84d5d85e940b99ad33b60.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_20,color_FFFFFF,t_70,g_se,x_16)
最終執(zhí)行sql走的是MybatisMapperMethod.execute(sqlSession, args)
```java
public Object execute(SqlSession sqlSession, Object[] args) {
? ? ? ? Object result;
//根據(jù)是查詢還是更新進入不同分支
? ? ? ? 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()) {
? ? ? ? ? ? ? ? ? ? //比如 selectList 就會走這里
? ? ? ? ? ? ? ? ? ? result = executeForMany(sqlSession, args);
? ? ? ? ? ? ? ? } else if (method.returnsMap()) {
? ? ? ? ? ? ? ? ? ? result = executeForMap(sqlSession, args);
? ? ? ? ? ? ? ? } else if (method.returnsCursor()) {
? ? ? ? ? ? ? ? ? ? result = executeForCursor(sqlSession, args);
? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? Object param = method.convertArgsToSqlCommandParam(args);
? ? ? ? ? ? ? ? ? ? // 分頁查詢
? ? ? ? ? ? ? ? ? ? if (IPage.class.isAssignableFrom(method.getReturnType())) {
? ? ? ? ? ? ? ? ? ? ? ? ...
? ? ? ? ? ? ? ? ? ? ? ? //關注這里 執(zhí)行分頁 默認也是執(zhí)行selectList
? ? ? ? ? ? ? ? ? ? ? ? result = executeForIPage(sqlSession, args);
? ? ? ? ? ? ? ? ? ? ? ? ...
? ? ? ? ? ? ? ? }
}
/**
? ? * TODO IPage 專用
? ? */
? ? private <E> List<E> executeForIPage(SqlSession sqlSession, Object[] args) {
? ? ? ? Object param = method.convertArgsToSqlCommandParam(args);
? ? ? ? //執(zhí)行SqlSessionTemplate的selectList
? ? ? ? return sqlSession.selectList(command.getName(), param);
? ? }
? @Override
? public <E> List<E> selectList(String statement, Object parameter) {
? //DefaultSqlSession
? ? return this.sqlSessionProxy.selectList(statement, parameter);
? }
```
###### DefaultSqlSession代理對象獲取sqlSession
```java
private class SqlSessionInterceptor implements InvocationHandler {
? ? @Override
? ? public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
? ? //加載sqlSession-getSqlSession-通過sessionFactory.openSession(executorType);
? ? //從中會獲取執(zhí)行器和加載插件
? ? ? SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
? ? ? ? ? SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
? ? ? try {
//拿到sqlSession才能去執(zhí)行查詢
? ? ? ? Object result = method.invoke(sqlSession, args);
? ? ? ? ....
? ? }
```
```java
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
? ? ? PersistenceExceptionTranslator exceptionTranslator) {
...
? ? //DefaultSqlSessionFactory.openSession
? ? session = sessionFactory.openSession(executorType);
...
? ? return session;
? }
@Override
? public SqlSession openSession(ExecutorType execType) {
? ? return openSessionFromDataSource(execType, 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);
? ? ? ? //創(chuàng)建執(zhí)行器 MybatisConfiguration.newExecutor
//(默認是simple執(zhí)行器)
? ? ? final Executor executor = configuration.newExecutor(tx, execType);
? ? ? ? //最后返回默認DefaultSqlSession
? ? ? 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();
? ? }
? }
```
###### 裝飾者模式創(chuàng)建executor和責任鏈模式interceptorChain加載插件
```java
@Override
? ? 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 MybatisBatchExecutor(this, transaction);
? ? ? ? } else if (ExecutorType.REUSE == executorType) {
? ? ? ? ? ? executor = new MybatisReuseExecutor(this, transaction);
? ? ? ? } else {
? ? ? ? ? ? executor = new MybatisSimpleExecutor(this, transaction);
? ? ? ? }
? ? ? ? if (cacheEnabled) {
? ? ? ? ? ? //裝飾者模式 裝飾了簡單執(zhí)行器
? ? ? ? ? ? executor = new MybatisCachingExecutor(executor);
? ? ? ? }
? ? ? ? //責任鏈 進行增加所有執(zhí)行器 并執(zhí)行plugin
? ? ? ? //通過判斷插件讥蔽,new Plugin(target, interceptor, signatureMap))生成MybatisCachingExecutor代理對象!!!
? ? ? ? executor = (Executor) interceptorChain.pluginAll(executor);
? ? ? ? return executor;
? ? }
```
##### 繼續(xù)執(zhí)行查詢
DefaultSqlSession.selectList
![\[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Rqw7uuW7-1650516052832)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/7e7eada5ee523b9358e3746bb94237eb)\]](https://img-blog.csdnimg.cn/6711ad8cde8f4c7db48da47dac668b2d.png)
```java
//DefaultSqlSession
@Override
? public <E> List<E> selectList(String statement, Object parameter) {
? ? return this.selectList(statement, parameter, RowBounds.DEFAULT);
? }
? @Override
//rowBounds 是用來邏輯分頁(按照條件將數(shù)據(jù)從數(shù)據(jù)庫查詢到內(nèi)存中涣易,在內(nèi)存中進行分頁)
//wrapCollection(parameter)是用來裝飾集合或者數(shù)組參數(shù) 里面有查詢條件
? public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
? ? try {
? ? //statement:
? ? ? //名字com.jdh.trade.mapper.SellReconciliationMapper.selectSellReconciliationByPage
? ? ? //獲取MappedStatement對象,通過配置信息從StrictMap緩存中獲取
? ? ? MappedStatement ms = configuration.getMappedStatement(statement);
? ? ? ? //執(zhí)行executor對象里面的query方法
? ? ? ? //這里的executor是在DefaultSqlSessionFactory中勤篮,
? ? ? ? //mybatis 通過Configuration對象創(chuàng)建的 對應CachingExecutor?
? ? ? ? //mybatis-plus 通過MybatisConfiguration 創(chuàng)建MybatisCachingExecutor
? ? ? ? //根據(jù)不同的配置都毒,會有不同的Executor 無論那個執(zhí)行器查詢最終都會到下面的查詢
? ? ? return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
? ? }
```
這一次獲得MappedStatement ms 如下
![\[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-cVkJL02K-1650516052833)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/bbd0c079418e440b6bef01551fadbd4f)\]](https://img-blog.csdnimg.cn/19932814ebb54cdcb317dfb280ff29b0.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_20,color_FFFFFF,t_70,g_se,x_16)
###### 關鍵查詢
```java
MybatisCachingExecutor
@Override
? ? public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
? ? ? ? //組裝sql--我們主要研究這里的組裝
? ? ? ? BoundSql boundSql = ms.getBoundSql(parameterObject);
//獲取一級緩存key
? ? ? ? CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
? ? ? ? //從緩存查詢還是直接查詢數(shù)據(jù)庫等
? ? ? ? return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
? ? }
```
```java
MappedStatement
public BoundSql getBoundSql(Object parameterObject) {
? ? //sqlSource 為 DynamicSqlSource(動態(tài)sql拼接)見上圖
//SqlSouce里面已經(jīng)解析mapper對應的sql
//已經(jīng)被解析為MixedSqlNode
? ? BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
? ? ....
? ? return boundSql;
? }
```
```xml
<select id="selectSellReconciliationByPage"
? ? ? ? ? ? resultType="com.jdh.trade.model.bankcontribution.resp.QuerySellReconciliationResp">
? ? ? ? SELECT
? ? ? ? ...
? ? ? ? WHERE sr.root_code = po.root_code
? ? ? ? ...
? ? ? ? <if test="param.pushStatus != null and param.pushStatus 色罚!='' ">
? ? ? ? ? ? and sr.push_status = #{param.pushStatus}
</select>
```
###### 解析sql過程
```java
@Override
? public BoundSql getBoundSql(Object parameterObject) {
? ? //下面有實體屬性
? ? DynamicContext context = new DynamicContext(configuration, parameterObject);
? ? //處理一個個的sqlNode 編譯出一個完整的xml的sql
? ? //rootSqlNode是 MixedSqlNode 合并sql節(jié)點
? ? //${} 已經(jīng)在靜態(tài)節(jié)點賦值了 因為是直接靜態(tài)判斷${ 然后替換 會有sql注入風險
? ? rootSqlNode.apply(context);
? ? //創(chuàng)建sql信息解析器
? ? SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
? ? //獲取入?yún)㈩愋?/p>
? ? Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
? ? //執(zhí)行解析:將帶有#{}和${}的sql語句進行解析碰缔,然后封裝到StaticSqlSource中
? ? ? //比如and sr.push_status = #{param.pushStatus} 解析成? and sr.push_status = ?
? ? SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
? ? //將解析后的sql語句還有入?yún)⒔壎ǖ揭黄穑ǚ庋b到一個對象中,此時還沒有將參數(shù)替換到SQL占位符戳护?)
? ? ? //此時變?yōu)殪o態(tài)綁定 StaticSqlSource
? ? BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
? ? context.getBindings().forEach(boundSql::setAdditionalParameter);
? ? return boundSql;
? }
```
###### sql解析為MixedSqlNode節(jié)點
![\[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-NjkqYKO0-1650516052833)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/6f3a80fa6d0587f8844927a0a4e36141)\]](https://img-blog.csdnimg.cn/90b0406c995743c588dc9c6120647ce7.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_20,color_FFFFFF,t_70,g_se,x_16)
![\[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-WYiKs5H5-1650516052834)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/fd0edc96898cccdbdfdebc55531df78f)\]](https://img-blog.csdnimg.cn/1f3e827aeccd4f549e764c7258637311.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_20,color_FFFFFF,t_70,g_se,x_16)
**將每一個節(jié)點進行各自的解析 然后拼裝xml的sql 到 sqlBuilder (其實是裝入string屬性)**
我們來看一下 rootSqlNode.apply(context);
######? rootSqlNode.apply(context)
每個節(jié)點根據(jù)自己的規(guī)則判斷是否組裝到sqlBuilder
MixedSqlNode.apply
![\[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-wl8g1ejY-1650516052834)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/0fc7f8cea1ab54cc583068836c25b9c5)\]](https://img-blog.csdnimg.cn/6aacecbe823c437396fa0d59f2ecc7b3.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_20,color_FFFFFF,t_70,g_se,x_16)
這里我們注意一下為什么IfSqlNode節(jié)點會失效呢
<font color='red'>注意:為什么param.pushStatus != '' 進入這個方法會返回false</font>
```java
@Override
? public boolean apply(DynamicContext context) {
? ? ? //test = param.pushStatus != null and param.pushStatus != ''
? ? ? //context.getBindings() 有前端傳的參數(shù)條件
? ? ? //判斷符合條件就加入
? ? if (evaluator.evaluateBoolean(test, context.getBindings())) {
? ? ? contents.apply(context);
? ? ? return true;
? ? }
? ? return false;
? }
```
調(diào)用ExpressionEvaluator.evaluateBoolean 進行判斷
```java
public boolean evaluateBoolean(String expression, Object parameterObject) {
? ? //問題出現(xiàn)在這里 value 返回了false
? ? Object value = OgnlCache.getValue(expression, parameterObject);
? ? if (value instanceof Boolean) {
? ? ? //從這里返回boolean值 false
? ? ? return (Boolean) value;
? ? }
? ? if (value instanceof Number) {
? ? ? return new BigDecimal(String.valueOf(value)).compareTo(BigDecimal.ZERO) != 0;
? ? }
? ? return value != null;
? }
```
再調(diào)用OgnlCache.getValue方法 獲取校驗結果
```java
//表達式expression = param.pushStatus != null and param.pushStatus != ''
//root 入?yún)?/p>
public static Object getValue(String expression, Object root) {
? ? try {
? ? ? //Mybatis底層校驗使用Ognl語法
? ? ? Map context = Ognl.createDefaultContext(root, MEMBER_ACCESS, CLASS_RESOLVER, null);
? ? ? ? //這里調(diào)用Ognl.getValue進行比較
? ? ? return Ognl.getValue(parseExpression(expression), context, root);
? ? } catch (OgnlException e) {
? ? ? throw new BuilderException("Error evaluating expression '" + expression + "'. Cause: " + e, e);
? ? }
? }
```
parseExpression(expression) 會從緩存中讀取Node 如果沒有 去解析成特定類型的Node對象
![\[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-jtSyNZzf-1650516052834)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/7f5a45ede1b4c446ca6c010177e944aa)\]](https://img-blog.csdnimg.cn/bd19badffcab45b2922ab70e97345819.png)
```java
//OgnlCache
private static Object parseExpression(String expression) throws OgnlException {
? ? Object node = expressionCache.get(expression);
? ? if (node == null) {
? ? ? node = Ognl.parseExpression(expression);
? ? ? expressionCache.put(expression, node);
? ? }
? ? return node;
? }
```
比如這里轉(zhuǎn)化成 ASTAND
以下節(jié)點都是繼承SimpleNode
![\[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-0pTTkrP2-1650516052835)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/79511546ca850b7612af95c39ef14591)\]](https://img-blog.csdnimg.cn/94023c27a12d4fb4a47f8a5bd263ca8a.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_20,color_FFFFFF,t_70,g_se,x_16)
Ognl.getValue(parseExpression(expression), context, root)獲取最終結果
```java
public static Object getValue(Object tree, Map context, Object root)
? ? ? ? ? ? throws OgnlException
? ? {
? ? ? ? return getValue(tree, context, root, null);
? ? }
/**
? ? * 計算給定的OGNL表達式樹金抡,從給定的根對象中提取值。通過addDefaultContext()為給定的上下文和根設置默認上下文腌且。
? ? *
? ? * @param tree
? ? *? ? ? ? ? ? 要計算的OGNL表達式樹梗肝,由parseExpression()返回
? ? 如:(param.pushStatus != null) && (param.pushStatus != "")
? ? * @param context
? ? *? ? ? ? ? ? 求值的命名上下文
? ? * @param root
? ? *? ? ? ? ? ? OGNL表達式的根對象
? ? * @return ? 返回計算表達式的結果
? ? */
public static Object getValue(Object tree, Map context, Object root, Class resultType) throws OgnlException {
? ? ? ? OgnlContext ognlContext = (OgnlContext)addDefaultContext(root, context);
? ? ? ? //根據(jù)解析表達式獲取的節(jié)點 該例子為 ASTAND
? ? Node node = (Node)tree;
? ? ? ? Object result;
? ? ? ? if (node.getAccessor() != null) {
? ? ? ? ? ? result = node.getAccessor().get(ognlContext, root);
? ? ? ? } else {
? ? ? ? ? ? //調(diào)用父類SimpleNode的getValue方法
//該節(jié)點為 ASTAND
? ? ? ? ? ? result = node.getValue(ognlContext, root);
? ? ? ? }
...
? ? ? ? return result;
? ? }
```
###### ASTAND節(jié)點樹進行解析
![\[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Uv5gijjR-1650516052836)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/4553a1d57700a78d5d412db1f5a5a389)\]](https://img-blog.csdnimg.cn/162fa2950cd84f239eb568b39cc4e968.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_20,color_FFFFFF,t_70,g_se,x_16)
調(diào)用父類 SimpleNode. getValue
以下兩個方法方法為父類方法
```java
public final Object getValue(OgnlContext context, Object source) throws OgnlException {
? ? ? ? Object result = null;
? ? ? ? if (context.getTraceEvaluations()) {
? ? ? ? ? ? ...
? ? ? ? } else {
? ? ? ? ? ? //獲取常量值
? ? ? ? ? ? result = this.evaluateGetValueBody(context, source);
? ? ? ? }
? ? ? ? return result;
? ? }
```
![\[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-gCaqx2zA-1650516052836)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/b89e9fd510e869daf43bccaf08a6ac61)\]](https://img-blog.csdnimg.cn/9b1c7262557a4211a5774818005bc628.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_20,color_FFFFFF,t_70,g_se,x_16)
調(diào)用子類自己的判斷實現(xiàn)getValueBody? 從節(jié)點樹葉子節(jié)點ASTConst一層一層根據(jù)規(guī)則返回結果給上一層
###### ASTAnd
```java
protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {
? ? ? ? Object result = null;
? ? ? ? int last = this._children.length - 1;
? ? ? ? for(int i = 0; i <= last; ++i) {
? ? ? ? ? ? result = this._children[i].getValue(context, source);
? ? ? ? ? ? if (i != last && !OgnlOps.booleanValue(result)) {
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return result;
? ? }
```
![\[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-s4tnU2yD-1650516052837)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/eb2e2e018bc33c8ee5b76a11252e2fb1)\]](https://img-blog.csdnimg.cn/38a998291734448e90c9cbcd2ee4c9aa.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_20,color_FFFFFF,t_70,g_se,x_16)
###### ASTNotEq
```java
protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {
? ? ? ? Object v1 = this._children[0].getValue(context, source);
? ? ? ? Object v2 = this._children[1].getValue(context, source);
? ? ? ? return OgnlOps.equal(v1, v2) ? Boolean.FALSE : Boolean.TRUE;
? ? }
```
![\[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-HyFxeqay-1650516052837)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/6ada714321b6458785e31fabe2887a86)\]](https://img-blog.csdnimg.cn/49b92c25ffa1462f8ce2b5ab4a353aef.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_20,color_FFFFFF,t_70,g_se,x_16)
<font color='red'>這里出現(xiàn)了問題為什么 0 和 '' equal會相等</font>
```java
public static boolean equal(Object v1, Object v2) {
? ? ? ? if (v1 == null) {
? ? ? ? ? ? return v2 == null;
? ? ? ? ? ? //!isEqual 判斷出問題
? ? ? ? } else if (v1 != v2 && !isEqual(v1, v2)) {
? ? ? ? ? ? if (v1 instanceof Number && v2 instanceof Number) {
? ? ? ? ? ? ? ? return ((Number)v1).doubleValue() == ((Number)v2).doubleValue();
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? return false;
? ? ? ? ? ? }
? ? ? ? } else {
? ? ? ? ? ? return true;
? ? ? ? }
? ? }
```
<font color='red'>!isEqual(v1, v2)? 判斷成了相等返回true</font>
```java
public static boolean isEqual(Object object1, Object object2) {
? ? ? ? ...
{
? ? ? ? ? ? ? ? ? ? //前面根據(jù)Ognl的算法
? ? ? ? ? ? ? ? ? ? result = compareWithConversion(object1, object2) == 0;
? ? ? ? ? ? ? ? }
? ? ? ? return result;
? ? }
public static int compareWithConversion(Object v1, Object v2) {
...
? ? ? ? double dv1 = doubleValue(v1);
? ? ? ? double dv2 = doubleValue(v2);
? ? ? ? return dv1 == dv2 ? 0 : (dv1 < dv2 ? -1 : 1);
? ? }
```
![\[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-tP6PMJ3z-1650516052838)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/d51079b0f256d5537fae3039142a2542)\]](https://img-blog.csdnimg.cn/0ccec46131624ee39dd901e7a849baa1.png)
###### 4、問題根源
```java
public static double doubleValue(Object value) throws NumberFormatException {
? ? ? ? if (value == null) {
? ? ? ? ? ? return 0.0D;
? ? ? ? } else {
? ? ? ? ? ? Class c = value.getClass();
? ? ? ? ? ? if (c.getSuperclass() == Number.class) {
? ? ? ? ? ? ? ? return ((Number)value).doubleValue();
? ? ? ? ? ? } else if (c == Boolean.class) {
? ? ? ? ? ? ? ? return (Boolean)value ? 1.0D : 0.0D;
? ? ? ? ? ? } else if (c == Character.class) {
? ? ? ? ? ? ? ? return (double)(Character)value;
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? //這里將''改成了0.0D
? ? ? ? ? ? ? ? String s = stringValue(value, true);
? ? ? ? ? ? ? ? return s.length() == 0 ? 0.0D : Double.parseDouble(s);
? ? ? ? ? ? }
? ? ? ? }
? ? }
```
最后是 "" 轉(zhuǎn)換成了0.0 去比較了
###### ASTChain
```java
protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {
? ? ? ? Object result = source;
? ? ? ? int i = 0;
? ? ? ? for(int ilast = this._children.length - 1; i <= ilast; ++i) {
? ? ? ? ? ? boolean handled = false;
? ? ? ? ? ? if (i < ilast && this._children[i] instanceof ASTProperty) {
? ? ? ? ? ? ? ? ASTProperty propertyNode = (ASTProperty)this._children[i];
? ? ? ? ? ? ? ? int indexType = propertyNode.getIndexedPropertyType(context, result);
...
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? if (!handled) {
//將最后的鏈值返回
? ? ? ? ? ? ? ? result = this._children[i].getValue(context, result);
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return result;
? ? }
```
###### ASTProperty
```java
protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {
? ? ? ? //通過ASTConst 直接獲取返回值 如 param 或 pushStatus 再讀取入?yún)闹?/p>
? Object property = this.getProperty(context, source);
? ? //根據(jù) param 或 pushStatus 返回對應的值
? ? ? ? Object result = OgnlRuntime.getProperty(context, source, property);
? ? ? ? if (result == null) {
? ? ? ? ? ? result = OgnlRuntime.getNullHandler(OgnlRuntime.getTargetClass(source)).nullPropertyValue(context, source, property);
? ? ? ? }
? ? ? ? return result;
? ? }
```
###### ASTConst
```java
protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {
? ? ? ? return this.value;
? ? }
```
#### 解決
(1) 不用Integer接收铺董,使用String類型接收
(2)去掉【參數(shù)巫击!=’‘】 的非空判斷
![\[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Sex5LBqb-1650516052838)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/e872d96804f4039fa5878d896d0f970f)\]](https://img-blog.csdnimg.cn/cf172267c941471a8ea0bf6402ca0f08.png)
> 番外:如果String類型需要判斷!=0精续,則需要寫成 xxx != '0'.toString()