Druid技巧之使用PrepareStatement时输出完整SQL语句

为了防止SQL注入,我们通过采用PrepareStatement代替Statement。使用Mybatis的情况下就是使用 #{} 来代替 ${}

凡事有利必有弊,这样带来了安全性,但随之而来的是调试阶段的检测SQL正确性的繁琐。因为我们需要一个个将?替换为原始的值才能放到诸如plsql里去执行。

本文介绍如何在Druid中粗略解决这个问题。

1. 前言

在现在的开发工作中,我们一般采用数据库连接池的方式来协助我们进行数据库的操作。而Druid作为国内非常知名的数据库连接池。其设计理念决定了解决我们上面提到的需求应该是一件非常轻松的事情。

2. 原理

通常我们想要Druid输出相关执行的SQL语句,我们一般会进行如下配置:

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        .....
        <property name="filters" value="slf4j"/>
        ......
</bean>

跟随其配置,我们可以最终发现Slf4jLogFilter类。而其基类LogFilterlogExecutableSql正承担了我们所关心的任务。 而控制这个方法真正执行的正是 statementExecutableSqlLogEnable 字段的值,其默认为false, 所以我们需要启用它。

    <bean id="log-filter" class="com.alibaba.druid.filter.logging.Slf4jLogFilter">
        <property name="connectionLogEnabled" value="false"/>
        <property name="statementLogEnabled" value="false"/>
        <property name="resultSetLogEnabled" value="true"/>
	    <!-- 启用 -->
        <property name="statementExecutableSqlLogEnable" value="true"/>
    </bean>

 <!-- 数据连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        .....
        <property name="filters" value="stat,wall"/>
        ......
        <property name="proxyFilters">
            <list>
		        <!-- 载入 -->
                <ref bean="log-filter"/>
            </list>
        </property>
    </bean>

3. 疑难问题

发现一个问题就是,当SQL语句出错时Druid是不会进行 ? 替换的。 所以我们需要进行一些魔改。

// 使用如下类代替默认的Slf4jLogFilter 进行Filter注册.
/*
	datasource.setProxyFilters(new ArrayList<Filter>() {
		private static final long serialVersionUID = 1L;

		{
			add(constructFilter());
		}
	});
*/
public class DruidSlf4jLoggerFilterEx extends Slf4jLogFilter {

	private static final Logger LOG = LoggerFactory
			.getLogger(DruidSlf4jLoggerFilterEx.class);

	// 将日志信息导入到我们要求的位置
	@Override
	protected void statementLogError(String message, Throwable error) {
		LOG.error(message, error);
		// super.statementLogError(message, error);
	}

	// 在SQL语句执行出错时, 依然进行 ? 替换
	@Override
	protected void statement_executeErrorAfter(StatementProxy statement,
			String sql, Throwable error) {
		if (!this.isStatementLogErrorEnabled()) {
			return;
		}

		if (!isStatementExecutableSqlLogEnable()) {
			statementLogError("{conn-" + statement.getConnectionProxy().getId()
					+ ", " + stmtId(statement) + "} execute error. " + sql,
					error);
			return;
		}

		int parametersSize = statement.getParametersSize();
		if (parametersSize <= 0) {
			statementLogError("{conn-" + statement.getConnectionProxy().getId()
					+ ", " + stmtId(statement) + "} execute error. " + sql,
					error);
		}

		final List<Object> parameters = new ArrayList<Object>(parametersSize);
		for (int i = 0; i < parametersSize; ++i) {
			JdbcParameter jdbcParam = statement.getParameter(i);
			parameters.add(jdbcParam != null ? jdbcParam.getValue() : null);
		}

		/*	Druid源码	
				String dbType = statement.getConnectionProxy().getDirectDataSource()
						.getDbType();
				String formattedSql = SQLUtils.format(sql, dbType, parameters,
						this.getStatementSqlFormatOption());
				statementLogError(
						"{conn-" + statement.getConnectionProxy().getId() + ", "
								+ stmtId(statement) + "} execute error. "
								+ formattedSql, error);
		*/

		final String formattedSql = transSql(parameters, sql);
		statementLogError(
				"{conn-" + statement.getConnectionProxy().getId() + ", "
						+ stmtId(statement) + "} execute error. "
						+ formattedSql, error);
	}
	
	private String transSql(List<Object> parameters, String sql) {

		if (sql.indexOf("?") < 0) {

			return sql;

		}

		for (int i = 0; i < parameters.size(); i++) {
			sql = sql.replaceFirst("\\?", parameters.get(i) != null ? "\'"
					+ parameters.get(i).toString() + "\'" : "NULL");
		}
		return sql;
	}	

	private String stmtId(StatementProxy statement) {
		StringBuffer buf = new StringBuffer();
		if (statement instanceof CallableStatementProxy) {
			buf.append("cstmt-");
		} else if (statement instanceof PreparedStatementProxy) {
			buf.append("pstmt-");
		} else {
			buf.append("stmt-");
		}
		buf.append(statement.getId());

		return buf.toString();
	}


}

4. 兼容 highgoDb (2018/11/1补充)

随着毛衣战的持续,国产化软件越来越被重视,所在公司也是响应号召要求支持highgoDb ;但在实际的使用过程中发现Druid在输出执行SQL时会报错,经过一番查找发现是因为Druid在格式化SQL语句时无法确定dbType导致的,具体源码如下:

// 被LogFilter.logExecutableSql()所调用
//	SQLUtils.format()
public static String format(String sql, String dbType, List<Object> parameters, FormatOption option) {
	try {
		// 截止Druid1.1.3, 提供了主流数据库db2, oracle, mysql, sqlserver, postgresql等的支持; highgoDb肯定就不在其中了
		SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(sql, dbType, true);
		parser.setKeepComments(true);

		List<SQLStatement> statementList = parser.parseStatementList();

		return toSQLString(statementList, dbType, parameters, option);
	} catch (ParserException ex) {
		LOG.warn("format error", ex);
		return sql;
	}
}

跟踪dbType的来源,最终的解决方案如下:

<bean id="mainDataSource" class="com.alibaba.druid.pool.DruidDataSource">
	...
	<property name="dbType" value="postgresql" />		
</bean>

5. Links

  1. Druid搭配log4j2输出SQL语句和结果
  • 6
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java可以使用Druid来连接数据库,并使用Druid API修改SQL语句Druid是一个高性能、可靠的开源数据库连接池,它支持JDBC和ODBC,可以为Java应用程序提供高效的数据访问。 下面是一个简单的使用Druid修改SQL语句的示例代码: ```java import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidPooledConnection; import java.sql.PreparedStatement; import java.sql.SQLException; public class Main { public static void main(String[] args) throws SQLException { DruidDataSource dataSource = new DruidDataSource(); dataSource.setUrl("jdbc:mysql://localhost:3306/test"); dataSource.setUsername("root"); dataSource.setPassword("password"); DruidPooledConnection connection = dataSource.getConnection(); PreparedStatement statement = connection.prepareStatement("update users set name = ? where id = ?"); statement.setString(1, "newName"); statement.setLong(2, 123); int updateCount = statement.executeUpdate(); System.out.println("Updated " + updateCount + " rows."); statement.close(); connection.close(); } } ``` 这个示例代码连接到本地MySQL数据库,修改了一个名为“users”的表中ID为123的行的“name”列的值。首先,创建了一个Druid数据源,并设置了数据库连接的URL、用户名和密码。然后,从数据源中获取一个连接,并准备执行SQL语句使用`PreparedStatement`类设置要修改的值和条件,并使用`executeUpdate()`方法执行SQL语句。最后,关闭语句和连接。 需要注意的是,这个示例代码没有使用Druid的一些高级功能,如连接池的管理和监控等。在实际开发中,应该根据实际情况配置和使用Druid,以提高数据库访问的性能和可靠性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值