前提条件
连接池使用的是阿里的druid,其他连接池后续有空再考虑
继承FilterEventAdapter
并重写statement_close
和resultSet_next
statement_close 用于格式化delete
,insert
(没有主键id返回),update
resultSet_next
用户格式化select
,insert
(有主键id返回)
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
| package cn.allbs.mybatis.filter;
import cn.allbs.common.constant.DateConstant; import cn.allbs.mybatis.properties.MybatisProperties; import cn.allbs.mybatis.utils.TableConsoleUtil; import com.alibaba.druid.DbType; import com.alibaba.druid.filter.FilterChain; import com.alibaba.druid.filter.FilterEventAdapter; import com.alibaba.druid.proxy.jdbc.JdbcParameter; import com.alibaba.druid.proxy.jdbc.ResultSetProxy; import com.alibaba.druid.proxy.jdbc.StatementProxy; import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.util.StringUtils; import lombok.extern.slf4j.Slf4j;
import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Types; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAccessor; import java.util.*;
@Slf4j public class DruidSqlLogFilter extends FilterEventAdapter {
private static final SQLUtils.FormatOption FORMAT_OPTION = new SQLUtils.FormatOption(false, false);
private final MybatisProperties mybatisProperties;
private boolean first = true;
private static final Set<Integer> BLOB_TYPES = new HashSet<>();
private ResultSet rs; private final Set<Integer> blobColumns = new HashSet<>();
private int rows;
private List<String> rowList = new LinkedList<>();
private boolean STATEMENT_CLOSE_RUN = true;
static { BLOB_TYPES.add(Types.BINARY); BLOB_TYPES.add(Types.BLOB); BLOB_TYPES.add(Types.CLOB); BLOB_TYPES.add(Types.LONGNVARCHAR); BLOB_TYPES.add(Types.LONGVARBINARY); BLOB_TYPES.add(Types.LONGVARCHAR); BLOB_TYPES.add(Types.NCLOB); BLOB_TYPES.add(Types.VARBINARY); }
public DruidSqlLogFilter(MybatisProperties mybatisProperties) { this.mybatisProperties = mybatisProperties; }
@Override protected void statementExecuteBefore(StatementProxy statement, String sql) { statement.setLastExecuteStartNano(); }
@Override protected void statementExecuteBatchBefore(StatementProxy statement) { statement.setLastExecuteStartNano(); }
@Override protected void statementExecuteUpdateBefore(StatementProxy statement, String sql) { statement.setLastExecuteStartNano(); }
@Override protected void statementExecuteQueryBefore(StatementProxy statement, String sql) { statement.setLastExecuteStartNano(); }
@Override protected void statementExecuteAfter(StatementProxy statement, String sql, boolean firstResult) { statement.setLastExecuteTimeNano(); }
@Override protected void statementExecuteBatchAfter(StatementProxy statement, int[] result) { statement.setLastExecuteTimeNano(); }
@Override protected void statementExecuteQueryAfter(StatementProxy statement, String sql, ResultSetProxy resultSet) { statement.setLastExecuteTimeNano(); }
@Override protected void statementExecuteUpdateAfter(StatementProxy statement, String sql, int updateCount) { statement.setLastExecuteTimeNano(); }
@Override public void statement_close(FilterChain chain, StatementProxy statement) throws SQLException { super.statement_close(chain, statement); if (!mybatisProperties.isShowSql()) { return; }
if (!log.isInfoEnabled()) { return; }
if (!STATEMENT_CLOSE_RUN) { STATEMENT_CLOSE_RUN = true; return; } String sql = statement.getBatchSql(); if (StringUtils.isEmpty(sql)) { return; } String executeSql = statement(statement); log.info(executeSql); }
public String statement(StatementProxy statement) { String sql = statement.getBatchSql(); if (StringUtils.isEmpty(sql)) { return ""; } int parametersSize = statement.getParametersSize(); List<Object> parameters = new ArrayList<>(parametersSize); for (int i = 0; i < parametersSize; ++i) { parameters.add(getJdbcParameter(statement.getParameter(i))); } String dbType = statement.getConnectionProxy().getDirectDataSource().getDbType(); String formattedSql = SQLUtils.format(sql, DbType.of(dbType), parameters, FORMAT_OPTION); return printSql(formattedSql, statement); }
private static Object getJdbcParameter(JdbcParameter jdbcParam) { if (jdbcParam == null) { return null; } Object value = jdbcParam.getValue(); if (value instanceof TemporalAccessor) { return value.toString(); } return value; }
private static String printSql(String sql, StatementProxy statement) { String sqlLogger = "\n--------------------------------[ %s Sql Log ]---------------------------------" + "\n%s" + "\n--------------------------------[ Sql Execute Time: %s ]---------------------------------\n"; return String.format(sqlLogger, LocalDateTime.now().format(DateTimeFormatter.ofPattern(DateConstant.NORM_DATETIME_PATTERN)), sql.trim(), format(statement.getLastExecuteTimeNano())); }
private static String format(long nanos) { if (nanos < 1) { return "0ms"; } double millis = (double) nanos / (1000 * 1000); if (millis > 1000) { return String.format("%.3fs", millis / 1000); } else { return String.format("%.3fms", millis); } }
@Override public boolean resultSet_next(FilterChain chain, ResultSetProxy resultSet) throws SQLException { boolean next = super.resultSet_next(chain, resultSet); if (!mybatisProperties.isShowSql()) { return next; }
if (!log.isInfoEnabled()) { return next; } if (!next) { String querySql = statement(resultSet.getStatementProxy()); String sqlLogger = querySql + "{}" + "--------------------------------[ Results Total {} ]---------------------------------\n"; String results = ""; if (rowList.size() > 0) { results = TableConsoleUtil.printResult(rowList); } log.info(sqlLogger, results, rows); rows = 0; rowList = new LinkedList<>(); first = true; STATEMENT_CLOSE_RUN = false; return false; } STATEMENT_CLOSE_RUN = true; rows++; rs = resultSet.getResultSetRaw(); ResultSetMetaData rsmd = resultSet.getMetaData(); final int columnCount = rsmd.getColumnCount(); if (first) { first = false; printColumnHeaders(rsmd, columnCount); } printColumnValues(columnCount); return true; }
private void printColumnHeaders(ResultSetMetaData rsmd, int columnCount) throws SQLException { StringJoiner row = new StringJoiner(","); for (int i = 1; i <= columnCount; i++) { if (BLOB_TYPES.contains(rsmd.getColumnType(i))) { blobColumns.add(i); } row.add(rsmd.getColumnLabel(i)); } rowList.add(row.toString()); }
private void printColumnValues(int columnCount) { StringJoiner row = new StringJoiner(","); for (int i = 1; i <= columnCount; i++) { try { if (blobColumns.contains(i)) { row.add("<<BLOB>>"); } else { row.add(rs.getString(i)); } } catch (SQLException e) { row.add("<<Cannot Display>>"); } } rowList.add(row.toString()); } }
|
将结果格式化为表格形式的工具代码
目前因为查询出的结果包含中文汉字,为了尽可能整齐,只是默认取了一个汉字为1.5倍的其他字符。有兴趣的自己修改成根据字体、字号获取实际的宽度并生成表格。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| package cn.allbs.mybatis.utils;
import java.math.BigDecimal; import java.math.RoundingMode; import java.util.List;
public class TableConsoleUtil {
public static String printResult(List<String> rows) { StringBuilder sb = new StringBuilder(); String[] tempA = rows.get(0).split(","); int maxLen = tempA.length; for (int i = 1; i < rows.size(); i++) { tempA = rows.get(i).split(","); if (maxLen < tempA.length) maxLen = tempA.length; } String[][] row = new String[rows.size()][maxLen]; for (int i = 0; i < row.length; i++) for (int j = 0; j < row[0].length; j++) row[i][j] = ""; for (int i = 0; i < rows.size(); i++) { tempA = rows.get(i).split(","); System.arraycopy(tempA, 0, row[i], 0, tempA.length); } int[] maxJ = new int[maxLen]; for (int j = 0; j < maxLen; j++) { for (int i = 0; i < rows.size(); i++) { int vLen = (getWordCount(row[i][j]) - 1 >> 3) * 8 + 8; if (vLen > maxJ[j]) { maxJ[j] = vLen; } } } StringBuilder opera = new StringBuilder("+"); for (int value : maxJ) { for (int k = 0; k < value; k++) { opera.append('-');
} opera.append('+'); } boolean first = true; for (String[] strings : row) { if (first) { sb.append(opera); sb.append("\n"); } sb.append("|"); for (int j = 0; j < row[0].length; j++) { int len = maxJ[j] - getWordCount(strings[j]); String format; if (len == 0) { format = "" + "%s"; } else { format = "%" + len + "s"; } sb.append(strings[j]); sb.append(String.format(format, "")); sb.append("|"); } sb.append("\n"); if (first) { sb.append(opera); sb.append("\n"); first = false; } } sb.append(opera); sb.append("\n"); return sb.toString(); }
public static int getWordCount(String s) { int length = 0; int chineseNum = 0; for (int i = 0; i < s.length(); i++) { int ascii = Character.codePointAt(s, i); if (ascii >= 0 && ascii <= 255) length++; else chineseNum++;
} return BigDecimal.valueOf(length + chineseNum * 1.5).setScale(0, RoundingMode.HALF_UP).intValue(); } }
|
注册过滤器
1 2 3 4
| @Bean public DruidSqlLogFilter sqlLogFilter(MybatisProperties properties) { return new DruidSqlLogFilter(properties); }
|
添加了配置开关当前日志打印效果
1 2 3 4 5 6 7 8 9 10
| @Data @Component @ConfigurationProperties("mybatis-plus") public class MybatisProperties {
private boolean showSql = true; }
|
使用说明
需要将mybatis实现的日志打印注释,防止和本文打印的日志重复。同时将show-sql设置为true。如果生产环境不需要日志打印将其设置为false即可。
实际使用示例
查询
新增
返回值为插入数据的主键
更新
删除
因为使用逻辑删除所以是更新语句
移出逻辑删除后
xml中的自定义sql