springBoot 之MyBatis 添加拦截器 输出完整SQL语句
不点
阅读:754
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.作者投稿可能会经我们编辑修改或补充。