您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關(guān)Druid多數(shù)據(jù)源下Sql防火墻導(dǎo)致異常的示例分析,小編覺得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
由于應(yīng)用組件同時(shí)使用了兩個(gè)數(shù)據(jù)源,Oracle和Mysql,而之前一直都是Oracle,近期引入了Mysql;
應(yīng)用組件生產(chǎn)環(huán)境配置開啟了Druid的Sql防火墻配置,配置項(xiàng)為:spring.datasource.druid.filter.wall.enabled=true
,由于測(cè)試環(huán)境未開啟,因此將問題帶到了生產(chǎn)上面。
開啟Sql防火墻后,在數(shù)據(jù)源初始化的時(shí)候會(huì)初始化一個(gè) WallFilter .
@Bean @ConfigurationProperties("spring.datasource.druid.filter.wall") @ConditionalOnProperty( prefix = "spring.datasource.druid.filter.wall", name = {"enabled"} ) @ConditionalOnMissingBean public WallFilter wallFilter(WallConfig wallConfig) { WallFilter filter = new WallFilter(); filter.setConfig(wallConfig); return filter; }
在近期版本上線后,一些跑了好幾年的sql突然一夜之間報(bào)錯(cuò)了,守版本的弟兄遇到這種問題后,難免大驚失色,無奈之下,當(dāng)晚只好回滾了版本。
下面我們來看下,已經(jīng)跑了幾年的sql當(dāng)時(shí)報(bào)了什么錯(cuò):
at com.alibaba.druid.wall.WallFilter.checkInternal(WallFilter.java:798) at com.alibaba.druid.wall.WallFilter.connection_prepareStatement(WallFilter.java:251) at com.alibaba.druid.filter.FilterChainImpl.connection_prepareStatement(FilterChainImpl.java:473) at com.alibaba.druid.filter.FilterAdapter.connection_prepareStatement(FilterAdapter.java:929) at com.alibaba.druid.filter.FilterEventAdapter.connection_prepareStatement(FilterEventAdapter.java:122) at com.alibaba.druid.filter.FilterChainImpl.connection_prepareStatement(FilterChainImpl.java:473) at com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl.prepareStatement(ConnectionProxyImpl.java:342) at com.alibaba.druid.pool.DruidPooledConnection.prepareStatement(DruidPooledConnection.java:350) at org.apache.ibatis.executor.statement.PreparedStatementHandler.instantiateStatement(PreparedStatementHandler.java:87) at org.apache.ibatis.executor.statement.BaseStatementHandler.prepare(BaseStatementHandler.java:88) at org.apache.ibatis.executor.statement.RoutingStatementHandler.prepare(RoutingStatementHandler.java:59) at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:85) at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:49) at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117) at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:76) at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:198) at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:185) at sun.reflect.GeneratedMethodAccessor408.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:433) ... 121 common frames omitted Caused by: com.alibaba.druid.sql.parser.ParserException: syntax error, error in :' USING (select ? as o', expect USING, actual IDENTIFIER pos 61, line 2, column 39, token IDENTIFIER USING at com.alibaba.druid.sql.parser.SQLParser.printError(SQLParser.java:284) at com.alibaba.druid.sql.parser.SQLParser.accept(SQLParser.java:292) at com.alibaba.druid.sql.parser.SQLStatementParser.parseMerge(SQLStatementParser.java:2879) at com.alibaba.druid.sql.parser.SQLStatementParser.parseStatementList(SQLStatementParser.java:225) at com.alibaba.druid.sql.parser.SQLStatementParser.parseStatementList(SQLStatementParser.java:81) at com.alibaba.druid.wall.WallProvider.checkInternal(WallProvider.java:622) at com.alibaba.druid.wall.WallProvider.check(WallProvider.java:576) at com.alibaba.druid.wall.WallFilter.checkInternal(WallFilter.java:785) ... 141 common frames omitted
看到上面的異常堆棧,拋出的異常的意思是:對(duì)該SQL進(jìn)行解析的時(shí)候期望接受的USING關(guān)鍵字,但實(shí)際上在關(guān)鍵字庫里面確沒有找到USING關(guān)鍵字,而是把USING當(dāng)成了標(biāo)識(shí)符IDENTIFIER.
打開SQLStatementParser 及 SQLParser類源碼發(fā)現(xiàn)有以下信息:
com.alibaba.druid.sql.parser.SQLStatementParser#parseMerge
關(guān)鍵問題在這里,為什么關(guān)鍵字的詞里沒有找到USING關(guān)鍵字? 通過debug發(fā)現(xiàn)在第一次訪問數(shù)據(jù)的時(shí)候會(huì)調(diào)用com.alibaba.druid.pool.DruidDataSource#init 方法, 該方法里有這一句:
public void init() throws SQLException { if (inited) { return; } final ReentrantLock lock = this.lock; try { lock.lockInterruptibly(); } catch (InterruptedException e) { throw new SQLException("interrupt", e); } boolean init = false; try { if (inited) { return; } initStackTrace = Utils.toString(Thread.currentThread().getStackTrace()); this.id = DruidDriver.createDataSourceId(); if (this.id > 1) { long delta = (this.id - 1) * 100000; this.connectionIdSeed.addAndGet(delta); this.statementIdSeed.addAndGet(delta); this.resultSetIdSeed.addAndGet(delta); this.transactionIdSeed.addAndGet(delta); } if (this.jdbcUrl != null) { this.jdbcUrl = this.jdbcUrl.trim(); initFromWrapDriverUrl(); } // 重點(diǎn)在這里 for (Filter filter : filters) { filter.init(this); }
這句話的意思是初始化配置的Filter,其中我們啟用的WallFilter就是在這里初始化的。
接下來我們進(jìn)入WallFilter 的 init方法:
@Override public synchronized void init(DataSourceProxy dataSource) { if (null == dataSource) { LOG.error("dataSource should not be null"); return; } if (this.dbType == null || this.dbType.trim().length() == 0) { // dbType 取決于第一次調(diào)用此方法的數(shù)據(jù)源的類型 if (dataSource.getDbType() != null) { this.dbType = dataSource.getDbType(); } else { this.dbType = JdbcUtils.getDbType(dataSource.getRawJdbcUrl(), ""); } } if (dbType == null) { dbType = JdbcUtils.getDbType(dataSource.getUrl(), null); } if (JdbcUtils.MYSQL.equals(dbType) || // JdbcUtils.MARIADB.equals(dbType) || // JdbcUtils.H2.equals(dbType)) { if (config == null) { config = new WallConfig(MySqlWallProvider.DEFAULT_CONFIG_DIR); } // 初始化Mysql類型的防火墻 provider = new MySqlWallProvider(config); } else if (JdbcUtils.ORACLE.equals(dbType) || JdbcUtils.ALI_ORACLE.equals(dbType)) { if (config == null) { config = new WallConfig(OracleWallProvider.DEFAULT_CONFIG_DIR); }
我們發(fā)現(xiàn)此方法加了 synchronized,并且里面有一個(gè)關(guān)鍵的屬性 dbType。
由于WallFilter只有一個(gè)實(shí)例,此處的dbType是成員變量,代碼中判斷為null時(shí)才從當(dāng)前dataSource中獲??;
那么問題來了,我們現(xiàn)在有兩個(gè) DruidDataSource
實(shí)例,那么在初始化時(shí)就會(huì)調(diào)用WallFilter的初始化,但是盡管調(diào)用多次 WallFilter
的 init
方法,但是從上面代碼得知dbType僅取決于第一次 DruidDataSource
調(diào)用WallFilter 的 init 方法時(shí) dataSource.getDbType()
的值。
分析到這里,你看出什么問題了?
是的,如果當(dāng)我們第一個(gè)訪問的數(shù)據(jù)源是Mysql時(shí),那么不管后續(xù)的DruidDataSource多次調(diào)用WallFilter 的 init 方法,dbType永遠(yuǎn)不會(huì)改變。
所以導(dǎo)致,我們用的永遠(yuǎn)都是 MySqlWallProvider
,也就是說我們用的SQL防火墻是Mysql類型的,所以后續(xù)對(duì)SQL的語法解析也用到了MySqlLexer 詞法解析器,該解析器的關(guān)鍵字庫里面是沒有USING這個(gè)關(guān)鍵字的,所以會(huì)報(bào)錯(cuò) USING 語法錯(cuò)誤。
開始我也說了,本次問題是部分實(shí)例報(bào)sql 異常,這又是為什么呢?
這是因?yàn)椋@和你第一次訪問的數(shù)據(jù)有關(guān),如果第一次訪問的是Mysql庫,那后面在訪問Oracle時(shí)候就會(huì)有這個(gè)問題。
如何解決這個(gè)問題呢?這里給出兩種方案。
可以把
spring.datasource.druid.filter.wall.enabled=true
這個(gè)配置設(shè)置成false 或去掉該配置,這也是最簡(jiǎn)單快捷的方式;對(duì)WallFilter和StatFilter 兩個(gè)類進(jìn)行拓展和相關(guān)邏輯優(yōu)化,需要編程,未實(shí)施。
以上就是Druid多數(shù)據(jù)源下Sql防火墻導(dǎo)致異常的示例分析,小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見到或用到的。希望你能通過這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。