SpringBoot使用 druid 連接池來優(yōu)化分頁語句
一、前言
一個老系統(tǒng)隨著數(shù)據(jù)量越來越大,我們察覺到部分分頁語句拖慢了我們的速度。
鑒于老系統(tǒng)的使用方式,不打算使用pagehelper和mybatis-plus來處理,加上系統(tǒng)里使用得是druid連接池,考慮直接使用druid來優(yōu)化。
二、老代碼
老代碼是使用得一個mybatis插件進行的分頁,分頁的核心代碼如下:
// 記錄統(tǒng)計的 sql
String countSql = "select count(0) from (" + sql+ ") tmp_count";
PreparedStatement countStmt = connection.prepareStatement(countSql);
BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql, boundSql.getParameterMappings(), parameterObject);
setParameters(countStmt, mappedStatement, countBS,parameterObject);
在原始的 sql 外面包裝了一個 count sql,當然很多插件都是這樣做的。
三、druid 的 PagerUtil
示例 sql(有比較復雜的坐標計算)
SELECT g.* , ROUND(6378.138 * 2 * ASIN(SQRT(POW(SIN((? * PI() / 180 - t.latitude * PI() / 180) / 2), 2) + COS(? * PI() / 180) * COS(t.latitude * PI() / 180) * POW(SIN((? * PI() / 180 - t.longitude * PI() / 180) / 2), 2))), 2) AS distancecd , t.agentname, t.agentlogo, t.compaddress FROM t_bas_integral_goods g LEFT JOIN t_bas_agent t ON g.agentid = t.AGENTID WHERE t.AGENTTYPE = '2' AND t.pass = '0' AND t.dl_type = '4' AND g.type = 0 ORDER BY distancecd ASC
使用 Druid 生成 count sql:
String countSql = PagerUtils.count(sql, DbType.mysql); System.out.println(countSql);
輸出:
SELECT COUNT(*) FROM t_bas_integral_goods g LEFT JOIN t_bas_agent t ON g.agentid = t.AGENTID WHERE t.AGENTTYPE = '2' AND t.pass = '0' AND t.dl_type = '4' AND g.type = 0
我們可以看到優(yōu)化后的 count sql 變得十分簡潔,坐標計算的都已經(jīng)丟棄掉。 注意:PagerUtil還有l(wèi)imit方法用來生成limit語句,感興趣的同學可以自行試驗。
四、改造mybatis分頁插件
4.1 踩坑之路
看到上面 druid PagerUtils count 的優(yōu)化效果,立馬開始改造起來,起初只改掉了countSql,
String countSql = PagerUtils.count(sql, dbType); PreparedStatement countStmt = connection.prepareStatement(countSql); BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql, boundSql.getParameterMappings(), parameterObject); setParameters(countStmt, mappedStatement, countBS,parameterObject);
啟動起來測試一番就發(fā)現(xiàn)報錯了,因為原始 sql 中含有?變量,優(yōu)化后的 sql 已經(jīng)沒有變量了,插件還會繼續(xù)給他設置變量。 我們要怎么解決這個問題呢?
我們再回頭看看pagehelper和mybatis-plus是怎么實現(xiàn)的!它倆都是基于jsqlparser對 sql 進行解析,然后處理。
要多加一個jsqlparser?沒必要沒必要,druid 的 sql 解析功能也是很強大的,我看了看PagerUtils.count方法的源碼,大不了用 druid 的 sql 解析實現(xiàn)一遍。
看了看源碼之后我陷入了沉思,有必要搞這么復雜么?有沒有更好的方法?我反復 debug 發(fā)現(xiàn)了,DynamicSqlSource中有帶#{xxx}這樣的原始 sql,
那么我是否可以使用 druid 先對這種 mybatis 占位符的 sql 進行優(yōu)化呢?我們來試試:
示例 sql:
select * from xxx where type = #{type} order by xx
輸出:
SELECT COUNT(*)
FROM xxx
WHERE type = #{type}
完美?。?! 4.2 繼續(xù)踩坑
然而直接在 Mapper 上注解的 sql 還是有問題,拿不到原始的 sql,debug 發(fā)現(xiàn) RawSqlSource 在構造器里就將 sql 處理成了?號掛參的形式。
@Select("select * from xxx where type = #{type} order by xx")
Object test(@Param("type") String type);
那么我只能看看能不能擴展它,我找到了它是在XMLLanguageDriver里進行初始化,這下好辦了,因為我之前擴展過XMLLanguageDriver,它是可以自定義配置的。 于是我重寫了RawSqlSource, 添加上了包含 mybatis 參數(shù)占位符(#{})的rawSql字段。
/**
* 原始 sql,用于方便 druid 工具進行分頁
*
* @author L.cm
*/
public class MicaRawSqlSource implements SqlSource {
private final String rawSql;
private final SqlSource sqlSource;
public MicaRawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
this(configuration, getSql(configuration, rootSqlNode), parameterType);
}
public MicaRawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
this.rawSql = sql;
this.sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
}
// ... ...
}
自此全部邏輯已經(jīng)走通,我們再來看看我們的PagePlugin核心代碼:
// 進行分頁
Configuration configuration = mappedStatement.getConfiguration();
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject.getClass();
Connection connection = (Connection) invocation.getArgs()[0];
// 1. 對 sql 進行判斷,如果沒有 ? 號,則直接處理
String boundRawSql = boundSql.getSql();
if (boundRawSql.indexOf(CharPool.QUESTION_MARK) == -1) {
// 不包含 ? 號
String countSql = PagerUtils.count(boundRawSql, dbType);
SqlSource newSqlSource = sqlSourceParser.parse(countSql, parameterType, new HashMap<>());
BoundSql newBoundSql = newSqlSource.getBoundSql(parameterObject);
int count = getCount(connection, mappedStatement, parameterObject, newBoundSql);
StringBuilder sqlBuilder = new StringBuilder(boundRawSql);
Page page = getPageParam(parameterObject, sqlBuilder, count);
String pageSql = generatePageSql(sqlBuilder.toString(), dbType, page);
// 將分頁sql語句反射回BoundSql.
setField(boundSql, "sql", pageSql);
return invocation.proceed();
}
// 2. 按 SqlSource 進行解析
SqlSource sqlSource = mappedStatement.getSqlSource();
// xml 中的動態(tài) sql
int count;
if (sqlSource instanceof DynamicSqlSource) {
SqlNode rootSqlNode = PagePlugin.getField(sqlSource, "rootSqlNode");
DynamicContext context = new DynamicContext(configuration, parameterObject);
rootSqlNode.apply(context);
// 生成 count sql,帶 #{xxx} 變量的 sql
String countSql = PagerUtils.count(context.getSql(), dbType);
SqlSource newSqlSource = sqlSourceParser.parse(countSql, parameterType, context.getBindings());
BoundSql newBoundSql = newSqlSource.getBoundSql(parameterObject);
count = getCount(connection, mappedStatement, parameterObject, newBoundSql);
} else if (sqlSource instanceof MicaRawSqlSource) {
String rawSql = ((MicaRawSqlSource) sqlSource).getRawSql();
DynamicContext context = new DynamicContext(configuration, parameterObject);
// 生成 count sql,帶 #{xxx} 變量的 sql
String countSql = PagerUtils.count(rawSql, dbType);
SqlSource newSqlSource = sqlSourceParser.parse(countSql, parameterType, context.getBindings());
BoundSql newBoundSql = newSqlSource.getBoundSql(parameterObject);
count = getCount(connection, mappedStatement, parameterObject, newBoundSql);
} else {
throw new IllegalArgumentException("不支持的 sql 分頁形式,請使用 xml 或者注解");
}
五、結論
整個老服務通過切換到 mica(深度定制)的微服務架構(演示環(huán)境僅僅在單服務低內(nèi)存配置)之后速度提升效果明顯,當然后面我們還會繼續(xù)進行優(yōu)化。
到此這篇關于SpringBoot使用 druid 連接池來優(yōu)化分頁語句的文章就介紹到這了,更多相關SpringBoot druid 連接池分頁內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- SpringBoot中Druid連接池與多數(shù)據(jù)源切換的方法
- SpringBoot整合mybatis使用Druid做連接池的方式
- Springboot中加入druid連接池
- springboot2.0配置連接池(hikari、druid)的方法
- SpringBoot整合Druid實現(xiàn)數(shù)據(jù)庫連接池和監(jiān)控
- springboot項目整合druid數(shù)據(jù)庫連接池的實現(xiàn)
- springboot集成druid連接池配置的方法
- springboot整合druid連接池的步驟
- SpringBoot整合Druid數(shù)據(jù)庫連接池的方法
- 解決Spring Boot中Druid連接池“discard long time none received connection“警告
相關文章
Netty分布式ByteBuf使用subPage級別內(nèi)存分配剖析
這篇文章主要為大家介紹了Netty分布式ByteBuf使用subPage級別內(nèi)存分配剖析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-03-03
IntelliJ IDEA創(chuàng)建普通的Java 項目及創(chuàng)建 Java 文件并運行的教程
這篇文章主要介紹了IntelliJ IDEA創(chuàng)建普通的Java 項目及創(chuàng)建 Java 文件并運行的教程,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-02-02

