马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
使用MyBatis/MyBatis Plus实现SQL日记打印与执行监控
一、配景与代价
在开发过程中,SQL日记的完整输出对于调试和性能优化至关紧张。MyBatis默认的日记输出仅显示带占位符的SQL语句,无法直接看到现实参数值,且缺乏执行时间统计。本文将介绍两种实现方案:
- 原生配置方案:通过日记框架直接输出基础SQL日记
- 增强方案:使用MyBatis拦截器实现完整SQL打印和执行监控
二、原生配置方案(快速上手)
1. 日记框架配置(以Logback为例)
- <!-- logback-spring.xml -->
- <configuration>
- <logger name="com.zaxxer.hikari" level="INFO"/>
- <logger name="java.sql.Connection" level="INFO"/>
- <logger name="java.sql.Statement" level="DEBUG"/>
- <logger name="java.sql.PreparedStatement" level="DEBUG"/>
- </configuration>
复制代码 2. 输出示例
- DEBUG [main] - ==> Preparing: SELECT * FROM user WHERE name = ?
- DEBUG [main] - ==> Parameters: John(String)
复制代码 3. 局限性
- 参数值单独显示,无法直接拼接完整SQL
- 缺乏执行耗时统计
- 动态SQL处理不敷直观
三、增强方案:自界说拦截器实现
1. SQL美化与参数更换
- public class MybatisPlusAllSqlLog implements InnerInterceptor {
- public static final Logger log = LoggerFactory.getLogger("sys-sql");
- @Override
- public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
- logInfo(boundSql, ms, parameter);
- }
- @Override
- public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {
- BoundSql boundSql = ms.getBoundSql(parameter);
- logInfo(boundSql, ms, parameter);
- }
- private static void logInfo(BoundSql boundSql, MappedStatement ms, Object parameter) {
- try {
- log.info("parameter = " + parameter);
- // 获取到节点的id,即sql语句的id
- String sqlId = ms.getId();
- log.info("sqlId = " + sqlId);
- // 获取节点的配置
- Configuration configuration = ms.getConfiguration();
- // 获取到最终的sql语句
- String sql = getSql(configuration, boundSql, sqlId);
- log.info("完整的sql:{}", sql);
- } catch (Exception e) {
- log.error("异常:{}", e.getLocalizedMessage(), e);
- }
- }
- // 封装了一下sql语句,使得结果返回完整xml路径下的sql语句节点id + sql语句
- public static String getSql(Configuration configuration, BoundSql boundSql, String sqlId) {
- return sqlId + ":" + showSql(configuration, boundSql);
- }
- // 进行?的替换
- public static String showSql(Configuration configuration, BoundSql boundSql) {
- // 获取参数
- Object parameterObject = boundSql.getParameterObject();
- List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
- // sql语句中多个空格都用一个空格代替
- String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
- if (!CollectionUtils.isEmpty(parameterMappings) && parameterObject != null) {
- // 获取类型处理器注册器,类型处理器的功能是进行java类型和数据库类型的转换
- TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
- // 如果根据parameterObject.getClass()可以找到对应的类型,则替换
- if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
- sql = sql.replaceFirst("\\?",
- Matcher.quoteReplacement(getParameterValue(parameterObject)));
- } else {
- // MetaObject主要是封装了originalObject对象,提供了get和set的方法用于获取和设置originalObject的属性值,主要支持对JavaBean、Collection、Map三种类型对象的操作
- MetaObject metaObject = configuration.newMetaObject(parameterObject);
- for (ParameterMapping parameterMapping : parameterMappings) {
- String propertyName = parameterMapping.getProperty();
- if (metaObject.hasGetter(propertyName)) {
- Object obj = metaObject.getValue(propertyName);
- sql = sql.replaceFirst("\\?",
- Matcher.quoteReplacement(getParameterValue(obj)));
- } else if (boundSql.hasAdditionalParameter(propertyName)) {
- // 该分支是动态sql
- Object obj = boundSql.getAdditionalParameter(propertyName);
- sql = sql.replaceFirst("\\?",
- Matcher.quoteReplacement(getParameterValue(obj)));
- } else {
- // 打印出缺失,提醒该参数缺失并防止错位
- sql = sql.replaceFirst("\\?", "缺失");
- }
- }
- }
- }
- return sql;
- }
- // 如果参数是String,则添加单引号, 如果是日期,则转换为时间格式器并加单引号; 对参数是null和不是null的情况作了处理
- private static String getParameterValue(Object obj) {
- String value;
- if (obj instanceof String) {
- value = "'" + obj.toString() + "'";
- } else if (obj instanceof Date) {
- DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT,
- DateFormat.DEFAULT, Locale.CHINA);
- value = "'" + formatter.format(new Date()) + "'";
- } else {
- if (obj != null) {
- value = obj.toString();
- } else {
- value = "";
- }
- }
- return value;
- }
- }
复制代码 2. 执行耗时监控
- @Intercepts({
- @Signature(type = Executor.class, method = "update", args = {MappedStatement.class,
- Object.class}),
- @Signature(type = Executor.class, method = "query", args = {MappedStatement.class,
- Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})})
- public class SqlStatementInterceptor implements Interceptor {
- public static final Logger log = LoggerFactory.getLogger("sys-sql");
- @Override
- public Object intercept(Invocation invocation) throws Throwable {
- long startTime = System.currentTimeMillis();
- try {
- return invocation.proceed();
- } finally {
- long timeConsuming = System.currentTimeMillis() - startTime;
- log.info("执行SQL:{}ms", timeConsuming);
- if (timeConsuming > 999 && timeConsuming < 5000) {
- log.info("执行SQL大于1s:{}ms", timeConsuming);
- } else if (timeConsuming >= 5000 && timeConsuming < 10000) {
- log.info("执行SQL大于5s:{}ms", timeConsuming);
- } else if (timeConsuming >= 10000) {
- log.info("执行SQL大于10s:{}ms", timeConsuming);
- }
- }
- }
- @Override
- public Object plugin(Object target) {
- return Plugin.wrap(target, this);
- }
- @Override
- public void setProperties(Properties properties) {
- }
- }
复制代码 自界说拦截器之后,请注意配置该拦截器
3.输出示例
- INFO [http-nio-8080-exec-1] - SQLID: com.example.mapper.UserMapper.selectById
- INFO [http-nio-8080-exec-1] - 完整SQL: SELECT id,name,age FROM user WHERE id=1
- INFO [http-nio-8080-exec-1] - 执行耗时: 48ms
- WARN [http-nio-8080-exec-1] - 慢SQL警告: 执行耗时1204ms
复制代码 四.总结
通过合理配置SQL日记输出,开发者可以:
- 快速定位SQL执行题目
- 直观分析现实执行的SQL语句
- 有效识别性能瓶颈
- 提拔动态SQL调试效率
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |