本篇文章给大家分享的是有关Druid多数据源下Sql防火墙导致异常的示例分析,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。
由于应用组件同时使用了两个数据源,Oracle和Mysql,而之前一直都是Oracle,近期引入了Mysql;
应用组件生产环境配置开启了Druid的Sql防火墙配置,配置项为:spring.datasource.druid.filter.wall.enabled=true
,由于测试环境未开启,因此将问题带到了生产上面。
开启Sql防火墙后,在数据源初始化的时候会初始化一个 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突然一夜之间报错了,守版本的弟兄遇到这种问题后,难免大惊失色,无奈之下,当晚只好回滚了版本。
下面我们来看下,已经跑了几年的sql当时报了什么错:
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
看到上面的异常堆栈,抛出的异常的意思是:对该SQL进行解析的时候期望接受的USING关键字,但实际上在关键字库里面确没有找到USING关键字,而是把USING当成了标识符IDENTIFIER.
打开SQLStatementParser 及 SQLParser类源码发现有以下信息:
com.alibaba.druid.sql.parser.SQLStatementParser#parseMerge
关键问题在这里,为什么关键字的词里没有找到USING关键字? 通过debug发现在第一次访问数据的时候会调用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();
}
// 重点在这里
for (Filter filter : filters) {
filter.init(this);
}
这句话的意思是初始化配置的Filter,其中我们启用的WallFilter就是在这里初始化的。
接下来我们进入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 取决于第一次调用此方法的数据源的类型
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);
}
我们发现此方法加了 synchronized,并且里面有一个关键的属性 dbType。
由于WallFilter只有一个实例,此处的dbType是成员变量,代码中判断为null时才从当前dataSource中获取;
那么问题来了,我们现在有两个 DruidDataSource
实例,那么在初始化时就会调用WallFilter的初始化,但是尽管调用多次 WallFilter
的 init
方法,但是从上面代码得知dbType仅取决于第一次 DruidDataSource
调用WallFilter 的 init 方法时 dataSource.getDbType()
的值。
分析到这里,你看出什么问题了?
是的,如果当我们第一个访问的数据源是Mysql时,那么不管后续的DruidDataSource多次调用WallFilter 的 init 方法,dbType永远不会改变。
所以导致,我们用的永远都是 MySqlWallProvider
,也就是说我们用的SQL防火墙是Mysql类型的,所以后续对SQL的语法解析也用到了MySqlLexer 词法解析器,该解析器的关键字库里面是没有USING这个关键字的,所以会报错 USING 语法错误。
开始我也说了,本次问题是部分实例报sql 异常,这又是为什么呢?
这是因为,这和你第一次访问的数据有关,如果第一次访问的是Mysql库,那后面在访问Oracle时候就会有这个问题。
如何解决这个问题呢?这里给出两种方案。
可以把
spring.datasource.druid.filter.wall.enabled=true
这个配置设置成false 或去掉该配置,这也是最简单快捷的方式;对WallFilter和StatFilter 两个类进行拓展和相关逻辑优化,需要编程,未实施。
以上就是Druid多数据源下Sql防火墙导致异常的示例分析,小编相信有部分知识点可能是我们日常工作会见到或用到的。希望你能通过这篇文章学到更多知识。更多详情敬请关注亿速云行业资讯频道。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
原文链接:https://my.oschina.net/coderluo/blog/3101038