springBoot 之MyBatis 添加拦截器 输出完整SQL语句分析

不点 阅读:222 2021-03-31 17:00:16 评论:0

业务要求:

开发过程中,如果使用mybatis做为ORM框架,经常需要打印出完整的sql语句以及执行的结果做为参考。

虽然mybatis结合日志框架可以做到,但打印出来的通常都是sql和参数分开的。

有时我们需要调试这条sql的时候,就需要把参数填进去,这样未免有些浪费时间。

此时我们可以通过实现mybatis拦截器来做到打印带参数的完整的sql,以及结果通过json输入到数据库中。

第一步:实体对象定义:

import com.common.model.BaseModel; 
 
@SuppressWarnings("serial") 
public class SysSqlAnalysis extends BaseModel { 
	// 主键 
    private String sid; 
    // 请求参数 
    private String parameter; 
    // mybatis mapper 命名空间 
    private String mapper; 
    // 完整sql 语句 
    private String sqls; 
    // sql 性能分析 
    private String analysis; 
 
    public String getSid() { 
        return sid; 
    } 
 
    public void setSid(String sid) { 
        this.sid = sid; 
    } 
 
    public String getParameter() { 
        return parameter; 
    } 
 
    public void setParameter(String parameter) { 
        this.parameter = parameter == null ? null : parameter.trim(); 
    } 
 
    public String getMapper() { 
        return mapper; 
    } 
 
    public void setMapper(String mapper) { 
        this.mapper = mapper == null ? null : mapper.trim(); 
    } 
 
   
    public String getSqls() { 
		return sqls; 
	} 
 
	public void setSqls(String sqls) { 
		this.sqls = sqls; 
	} 
 
	public String getAnalysis() { 
        return analysis; 
    } 
 
    public void setAnalysis(String analysis) { 
        this.analysis = analysis == null ? null : analysis.trim(); 
    } 
}

第二:spring jdbc(JdbcTemplate) 工具类封装,将sql 语句记录MySQL 中

package com.ucas.cron.util; 
 
import java.sql.SQLException; 
import org.springframework.jdbc.core.JdbcTemplate; 
import com.digipower.ucas.cron.domain.SysSqlAnalysis; 
 
public class SQLAnalysisUtil { 
	private JdbcTemplate template; 
 
	// set 和 get 方法 
	public JdbcTemplate getTemplate() { 
		return template; 
	} 
 
	public void setTemplate(JdbcTemplate template) { 
		this.template = template; 
	} 
	 
	// 新增sql 
	String insert = "insert into sys_sql_analysis(sid, parameter, mapper, sqls) values (?,?,?,?)"; 
	 
	/** 
	 * sys_cron 插入 
	 *  
	 * @param entity 
	 * @throws SQLException 
	 */ 
	public void insert(SysSqlAnalysis entity) throws SQLException { 
		template.update(insert, entity.getSid(), entity.getParameter(), entity.getMapper(), entity.getSqls()); 
	} 
} 

第三:相关配置:

package com.ucas.cron.config; 
 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration; 
import org.springframework.jdbc.core.JdbcTemplate; 
import org.springframework.jdbc.datasource.DriverManagerDataSource; 
import com.ucas.cron.config.domain.CronTaskConfigEntity; 
import com.ucas.cron.util.CronTaskUtil; 
import com.ucas.cron.util.SQLAnalysisUtil; 
 
@Configuration 
public class CronTaskConfig { 
	 
	@Autowired 
	private CronTaskConfigEntity config; 
	 
	/** 
	 * DataSource 
	 * @return 
	 */ 
	public DriverManagerDataSource getDataSources(){ 
		DriverManagerDataSource dataSource = new DriverManagerDataSource(); 
		dataSource.setDriverClassName(config.getDriverClassName()); 
		dataSource.setUrl(config.getUrl()); 
		dataSource.setUsername(config.getUsername()); 
		dataSource.setPassword(config.getPassword()); 
		return dataSource; 
	} 
	 
	/** 
	 * JdbcTemplate 
	 * @return 
	 */ 
	public JdbcTemplate getJdbcTemplates(){ 
		JdbcTemplate template = new JdbcTemplate(getDataSources()); 
		return template; 
	} 
	/** 
	 * CronTaskUtil 
	 * @return 
	 */ 
	@Bean(name = "cronTaskUtil") 
	public CronTaskUtil getCronTaskUtil(){ 
		CronTaskUtil util = new CronTaskUtil(); 
		util.setTemplate(getJdbcTemplates()); 
		return util; 
	} 
	 
	@Bean(name="sqlAnalysisUtil") 
	public SQLAnalysisUtil getSQLAnalysisUtil(){ 
		SQLAnalysisUtil util = new SQLAnalysisUtil(); 
		util.setTemplate(getJdbcTemplates()); 
		return util; 
	} 
	 
} 

springboot 项目添加SQLAnalysisUtil 工具栏所在的工具包,

开始编辑mysql sql 输出语句拦截器:

package com.ucas.mapper.interceptor; 
 
import java.text.DateFormat; 
import java.util.Date; 
import java.util.List; 
import java.util.Locale; 
import java.util.Properties; 
import java.util.regex.Matcher; 
import org.apache.commons.collections.CollectionUtils; 
import org.apache.ibatis.executor.Executor; 
import org.apache.ibatis.mapping.BoundSql; 
import org.apache.ibatis.mapping.MappedStatement; 
import org.apache.ibatis.mapping.ParameterMapping; 
import org.apache.ibatis.plugin.Interceptor; 
import org.apache.ibatis.plugin.Invocation; 
import org.apache.ibatis.plugin.*; 
import org.apache.ibatis.reflection.MetaObject; 
import org.apache.ibatis.session.Configuration; 
import org.apache.ibatis.session.ResultHandler; 
import org.apache.ibatis.session.RowBounds; 
import org.apache.ibatis.type.TypeHandlerRegistry; 
import org.springframework.beans.factory.annotation.Autowired; 
import com.alibaba.fastjson.JSON; 
import com.ucas.cron.domain.SysSqlAnalysis; 
import com.ucas.cron.util.SQLAnalysisUtil; 
import com.ucas.util.UUIDGenerator; 
 
/** 
 * mybatis 拦截器定义: 输出执行SQL语句 
 * @author zzg 
 * 
 */ 
@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})}) 
@SuppressWarnings({"unchecked", "rawtypes"}) 
public class MyBatisInterceptor implements Interceptor { 
	@Autowired 
	private SQLAnalysisUtil util; 
	 
 
	@Override 
	public Object intercept(Invocation invocation) throws Throwable { 
		// TODO Auto-generated method stub 
		SysSqlAnalysis  analysis = new SysSqlAnalysis(); 
		analysis.setSid(UUIDGenerator.getUUID()); 
		try 
        { 
            // 获取xml中的一个select/update/insert/delete节点,是一条SQL语句 
            MappedStatement mappedStatement = (MappedStatement)invocation.getArgs()[0]; 
            Object parameter = null; 
            // 获取参数,if语句成立,表示sql语句有参数,参数格式是map形式 
            if (invocation.getArgs().length > 1) 
            { 
                parameter = invocation.getArgs()[1]; 
                analysis.setParameter(JSON.toJSONString(parameter)); 
                
            } 
            String sqlId = mappedStatement.getId(); // 获取到节点的id,即sql语句的id 
            System.out.println("sqlId = " + sqlId); 
            analysis.setMapper(sqlId); 
            BoundSql boundSql = mappedStatement.getBoundSql(parameter); // BoundSql就是封装myBatis最终产生的sql类 
            Configuration configuration = mappedStatement.getConfiguration(); // 获取节点的配置 
            String sql = getSql(configuration, boundSql, sqlId); // 获取到最终的sql语句 
            System.out.println("sql = " + sql); 
            analysis.setSqls(sql); 
        } 
        catch (Exception e) 
        { 
            e.printStackTrace(); 
        } 
		util.insert(analysis); 
        // 执行完上面的任务后,不改变原有的sql执行过程 
		return invocation.proceed(); 
	} 
	 
	// 封装了一下sql语句,使得结果返回完整xml路径下的sql语句节点id + sql语句 
    public static String getSql(Configuration configuration, BoundSql boundSql, String sqlId) 
    { 
        String sql = showSql(configuration, boundSql); 
//        StringBuilder str = new StringBuilder(100); 
//        str.append(sqlId); 
//        str.append(":"); 
//        str.append(sql); 
        return sql; 
    } 
  
    // 如果参数是String,则添加单引号, 如果是日期,则转换为时间格式器并加单引号; 对参数是null和不是null的情况作了处理 
    private static String getParameterValue(Object obj) 
    { 
        String value = null; 
        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; 
    } 
  
    // 进行?的替换 
    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.isNotEmpty(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; 
    } 
 
	 
	 
 
	@Override 
	public Object plugin(Object target) { 
		// TODO Auto-generated method stub 
		return Plugin.wrap(target, this); 
	} 
 
	@Override 
	public void setProperties(Properties properties) { 
		// TODO Auto-generated method stub 
	} 
 
} 

springboot 配置mybatis 拦截器

package com.digipower.ucas.config; 
 
import java.util.Properties; 
 
import org.apache.ibatis.plugin.Interceptor; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration; 
 
import com.digipower.ucas.mapper.interceptor.MyBatisInterceptor; 
import com.github.pagehelper.PageHelper; 
 
@Configuration 
public class MyBatisConfig { 
	/** 
	 * 分页对象实列化 
	 * @return 
	 */ 
	@Bean 
	public PageHelper pageHelper() { 
		PageHelper pageHelper = new PageHelper(); 
		Properties p = new Properties(); 
		p.setProperty("offsetAsPageNum", "true"); 
		p.setProperty("rowBoundsWithCount", "true"); 
		p.setProperty("reasonable", "true"); 
		p.setProperty("dialect", "mysql"); 
		pageHelper.setProperties(p); 
		return pageHelper; 
	} 
	 
	/** 
	 * mybatis 自定义拦截器 
	 */ 
	@Bean  
	public Interceptor getInterceptor(){ 
		return new MyBatisInterceptor(); 
	} 
} 

效果展示:

sql 的性能分析实现方式,待编辑。

声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

发表评论
搜索
KIKK导航

KIKK导航

排行榜
关注我们

一个IT知识分享的公众号