⽇志系列-MySQL⽇志打印(druid)
使⽤Druid连接池,打印完整可执⾏SQL
前⾔
⼀个基于SpringBoot或者SpringMVC的项⽬,如果要考虑打印完整的可执⾏SQL,有许多⽅式.这⾥介绍基于MySQL数据库,并且使⽤Druid 数据库连接池的完整可执⾏SQL打印⽅式.
1.准备⼀个基于SpringBoot的⼩项⽬
样例项⽬结构如下:
2.为项⽬配置数据库连接(使⽤Druild连接池).
⼀下为数据库连接配置
datasource:
cloud:
username: root
password: 123456
driver-class-name: sql.cj.jdbc.Driver
url:  jdbc:mysql://127.0.0.1:3306/persimmon?useUnicode=true&characterEncoding=UTF-8&useSSL=false
type: com.alibaba.druid.pool.DruidDataSource
maxWait: 60000
initialSize: 5
minIdle: 5
maxActive: 20
以下为数据库连接池配置⽂件(部分)
@Configuration
@EnableSQLFormat
@SuppressWarnings("all")
@EnableTransactionManagement
@MapperScan(basePackages = "batis.mapper")
public class CloudDruidConfig {
@Autowired
private DefaultSlf4jLogFilter defaultSlf4jLogFilter;
@ConfigurationProperties(prefix = "datasource.cloud")
@Bean
public Properties cloudDataSourceProperties() {
return new Properties();
}
@Bean(name = "dataSource")
public DataSource cloudDataSource(@Autowired @Qualifier("cloudDataSourceProperties") Properties properties) throws SQLException {        DruidDataSource dataSource = new DruidDataSource();
dataSource.Property("dbType"));
dataSource.Property("username"));
dataSource.Property("password"));
dataSource.Property("url"));
dataSource.Property("driver-class-name"));
dataSource.setMaxActive(Integer.valueOf((String) ("maxActive")));
dataSource.setMinIdle(Integer.valueOf((String) ("minIdle")));
dataSource.setInitialSize(Integer.valueOf((String) ("initialSize")));
dataSource.setMaxWait(Integer.("maxWait").toString()));
// 为DruidDataSource设置proxyFilters(将Druild的Slf4jLogFilter过滤器设置到此⽅法中)
dataSource.wArrayList(defaultSlf4jLogFilter));
return dataSource;
}
}
以下为⽇志打印属性配置
public class DefaultSlf4jLogFilter extends Slf4jLogFilter {
public DefaultSlf4jLogFilter() {
// 链接的时候
this.setConnectionLogEnabled(false);
this.setDataSourceLogEnabled(false);
this.setResultSetLogEnabled(false);
// SQL格式化
this.setStatementSqlPrettyFormat(true);
// Slf4jLogFilter中statementLogSqlPrettyFormat属性设置为true
this.setStatementExecutableSqlLogEnable(true);
/
/ 执⾏之后
this.setStatementExecuteAfterLogEnabled(false);
// 执⾏参数打印
this.setStatementParameterSetLogEnabled(false);
// 执⾏之前-准备⼯作之后
this.setStatementPrepareAfterLogEnabled(false);
// 执⾏之前-准备⼯作之后
this.setStatementPrepareCallAfterLogEnabled(false);
// 关闭的时候
this.setStatementCloseAfterLogEnabled(false);
this.setConnectionCloseAfterLogEnabled(false);
}
}
3.配置⼩项⽬的⽇志打印级别为debug
这⾥是l⽂件中截取的相关的部分
<logger name="druid.sql.Statement" level="DEBUG" />
4.测试配置-打印⽇志-但不格式化
在可执⾏的Mapper中增加如下⽅法:
@Select(" select \n" +
"menu_name \n" +
"from \n" +
"admin_menu ")
@ReturnExecutableSQL
String fetchAllMenu();
5.测试配置-打印⽇志-但不格式化.
在可执⾏的Mapper中增加如下⽅法:
@Select(" select \n\n" +
"menu_name \n\n\n" +
"from \n" +
"admin_menu \nwhere menu_name \nlike '%${name}%'")
List<AdminMenu> selectMenusLike(@Param("name") String name);
6.测试配置-打印⽇志-同时格式化
在可执⾏的Mapper中增加如下⽅法:
@Select(" select \n\n" +
"menu_name \n\n\n" +
"from \n" +
"admin_menu \nwhere menu_name \nlike concat('%',#{name},'%')")    List<AdminMenu> selectMenusLike(@Param("name") String name);
7.分析-整理
SQL⽇志是否格式化,依赖于如下⽅法(LogFilter.java类):
@Override
protected void statementExecuteAfter(StatementProxy statement, String sql, boolean firstResult) {
logExecutableSql(statement, sql);
if (statementExecuteAfterLogEnable && isStatementLogEnabled()) {
statement.setLastExecuteTimeNano();
double nanos = LastExecuteTimeNano();
double millis = nanos / (1000 * 1000);
statementLog("{conn-" + ConnectionProxy().getId() + ", " + stmtId(statement) + "} executed. "                        + millis + " millis. " + sql);
}
}
相关⽅法:
private void logExecutableSql(StatementProxy statement, String sql) {
if ((!isStatementExecutableSqlLogEnable()) || !isStatementLogEnabled()) {
return;
}
int parametersSize = ParametersSize();
if (parametersSize == 0) {
statementLog("{conn-" + ConnectionProxy().getId() + ", " + stmtId(statement) + "} executed. "                        + sql);
return;
}
List<Object> parameters = new ArrayList<Object>(parametersSize);
for (int i = 0; i < parametersSize; ++i) {
JdbcParameter jdbcParam = Parameter(i);
druid连接池配置详解
parameters.add(jdbcParam != null
: null);
}
String dbType = ConnectionProxy().getDirectDataSource().getDbType();
String formattedSql = SQLUtils.format(sql, dbType, parameters, this.statementSqlFormatOption);
statementLog("{conn-" + ConnectionProxy().getId() + ", " + stmtId(statement) + "} executed. "                    + formattedSql);
}
代码中: 当parametersSize=0的时候,是不会格式化SQL的.
总结
于是:
样例4是⼀定不会有参数的, 所以不会格式化SQL.
样例5和样例6的唯⼀区别是⼀个⽤${}占位符,⼀个⽤#{}占位符
为什么样例5不格式化呢?
这⾥涉及到MyBatis的$和#符号的区别.
$占位符在解析的时候,会直接替换为具体的值,不⾛sql格式化逻辑.
#占位符在解析的时候,会替换为"?",并记录parameter, 会⾛格式化逻辑.
#占位符和$占位符的区别见