赞
踩
需求: 开发 生成 数据库 同步基础数据 但是又不能直接覆盖表 这时候同步起来 很玛法
解决方案:在开发中 添加 更新 删除的 基础数据 执行的 sql 语句 存到开发数据库 需要同步 到线上时
直接执行 存在 开发数据的 sql 语句 即可
需求: 有的时候 需要动态 拼接 sql 比如 数据权限 根据权限不同 拼接不同的 where 条件
也可以在 Mybatis 拦截器 中实现
下面的代码 主要是 拦截 执行的 sql 语句 存储到 数据库
第一步:
定义 Mybatis 拦截器
package com.erp.init.mybatisplus;
import com.erp.init.sqlLog.SqLogUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.stereotype.Component;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.Statement;
import java.util.Map;
import java.util.Properties;
//在mybatis中可被拦截的类型有四种(按照拦截顺序):
//
// Executor: 拦截执行器的方法。
// ParameterHandler: 拦截参数的处理。
// ResultHandler:拦截结果集的处理。
// StatementHandler: 拦截Sql语法构建的处理。
//@Intercepts({
// @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
// @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
// @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
//})
// 1. @Intercepts:标识该类是一个拦截器;
// @Signature:指明自定义拦截器需要拦截哪一个类型,哪一个方法;
// 2.1 type:对应四种类型中的一种;
// 2.2 method:对应接口中的哪类方法(因为可能存在重载方法);
// 2.3 args:对应哪一个方法;
@Intercepts({
@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
@Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
})
@Slf4j
@Component // 必须要交给 spring boot 管理
public class MybatisLogInterceptor implements Interceptor {
//这个方法里 是重点 主要是拦截 需要执行的sql
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 执行方法
Object result = invocation.proceed();
// 获取MapperStatement对象,获取到sql的详细信息
Object realTarget = realTarget(invocation.getTarget());
// 获取metaObject对象
MetaObject metaObject = SystemMetaObject.forObject(realTarget);
// 获取MappedStatement对象
MappedStatement ms = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
// 获取方法的全类名称
String methodFullName = ms.getId();
// 判断是否是需要日志记录的方法
// 我用的是 Mybatis 提供的方法 methodFullName 这个可以获取到 方法 全类名
// 如果是 写的 原生 sql 执行的 没测试过 需要 测试一下使用
Map<String, Object> map = SqLogUtil.verifyRecordLog(methodFullName);
if (!map.isEmpty() && (boolean) map.get("isRecord")) {
Statement statement;
// 获取方法参数
Object[] args = invocation.getArgs();
Object firstArg = args[0];
if (Proxy.isProxyClass(firstArg.getClass())) {
statement = (Statement) SystemMetaObject.forObject(firstArg).getValue("h.statement");
} else {
statement = (Statement) firstArg;
}
// 这个对象里 有好几个 statement 和 stmt
// 可以打打断点 看啊看 statement 这个对象里的数据结构
MetaObject stmtMetaObj = SystemMetaObject.forObject(statement);
Object stmt = stmtMetaObj.getValue("stmt");
MetaObject metaObject1 = SystemMetaObject.forObject(stmt);
Object statement1 = metaObject1.getValue("statement");
MetaObject metaObject2 = SystemMetaObject.forObject(statement1);
// mybatis 最后执行的sql 就在 这个对象里
Object stmt1 = metaObject2.getOriginalObject();
String finalSql=stmt1.toString();
//去掉不要的字符串
finalSql = finalSql.substring(finalSql.indexOf(":") + 1, finalSql.length());
log.info("最终sql: \n " + finalSql);
String saveLogSql = SqLogUtil.getSaveLogSql(methodFullName, (String) map.get("desc"), finalSql);
if (StringUtils.isNotBlank(saveLogSql)) {
Connection connection = statement.getConnection();
if (connection.isReadOnly()) { // 当前事务是只读事务,则重新用不同的Connection对象
Connection mysqlConnection = SqLogUtil.getMysqlConnection();
if (mysqlConnection != null) {
try {
mysqlConnection.createStatement().execute(saveLogSql);
} catch (Exception e) {
e.printStackTrace();
log.error("拦截器记录日志出错!", e);
} finally {
mysqlConnection.close();//关闭连接
}
}
} else {
connection.createStatement().execute(saveLogSql);
}
}
}
return result;
}
@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties prop) {
}
/**
* <p>
* 获得真正的处理对象,可能多层代理.
* </p>
*/
@SuppressWarnings("unchecked")
public static <T> T realTarget(Object target) {
if (Proxy.isProxyClass(target.getClass())) {
MetaObject metaObject = SystemMetaObject.forObject(target);
return realTarget(metaObject.getValue("h.target"));
}
return (T) target;
}
}
第二步:
创建一个 sql 存储 工具类
package com.erp.init.sqlLog;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.util.JdbcConstants;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* User: Json
* <p>
* Date: 2022/11/22
**/
@Component // 必须要交给 spring boot 管理
@Slf4j
public class SqLogUtil {
/**
* 判断哪个方法 需要被记录
* 这是一种方法 也可以设计成注解的形式 判断注解
* 我用的是 Mybatis 提供的方法
* 如果是 写的 原生 sql 执行的 没测试过 需要 测试一下使用
*/
public static final String[] DEFAULT_RECORD_METHOD_START = {
"com.erp.base.mapper.YsDictMapper.updateById",
"com.erp.base.mapper.YsDictMapper.insert",
"com.erp.base.mapper.YsDictMapper.deleteById",
"com.erp.base.mapper.YsDictSubMapper.insert",
"com.erp.base.mapper.YsDictSubMapper.updateById",
"com.erp.base.mapper.YsDictSubMapper.deleteById",
"com.erp.base.mapper.YsFormMapper.insert",
"com.erp.base.mapper.YsFormMapper.updateById",
"com.erp.base.mapper.YsFormMapper.delete"
};
/**
* 默认不记录的操作方法(记录日志的方法)
* 这个是 执行 Mapper 里的方法
* YsSqlLogMapper 类里的 方法
*/
public static final String[] DEFAULT_NOT_RECORED_METHOD = new String[]{"com.erp.base.mapper.YsSqlLogMapper.saveSqlLog"};
private static SqLogUtil logUtils;
/**
* 注入SqlSessionFactory对象
*/
@Autowired
private SqlSessionFactory sqlSessionFactory;
/**
* 注入DataSource对象
*/
@Autowired
private DataSource mysqlDataSource;
public SqLogUtil() {
}
/**
* 给logUtils对象赋值
*/
@PostConstruct
public void init() {
logUtils = this;
logUtils.sqlSessionFactory = this.sqlSessionFactory;
logUtils.mysqlDataSource = this.mysqlDataSource;
}
/**
* 验证方法是否需要日志记录
*
* @param methodFullName
* @return
*/
public static Map<String, Object> verifyRecordLog(String methodFullName) {
Map<String, Object> resultMap = new HashMap<>();
for (int i = 0; i < DEFAULT_NOT_RECORED_METHOD.length; i++) {
if (methodFullName.equals(DEFAULT_NOT_RECORED_METHOD[i])) {
return resultMap;
}
}
boolean isRecord = false;
String desc = "";
int flag = methodFullName.lastIndexOf(".");
String classPath = methodFullName.substring(0, flag);
String methodName = methodFullName.substring(flag + 1);
Class<?> clazz = null;
try {
clazz = Class.forName(classPath);
} catch (ClassNotFoundException e) {
e.printStackTrace();
log.error("判断是否需要记录日志异常!", e);
}
if (clazz != null) {
if (verifyMethodName(methodFullName)) {
isRecord = true;
}
}
resultMap.put("isRecord", isRecord); // 是否记录
resultMap.put("desc", desc); // 方法描述
return resultMap;
}
/**
* 判断方法名是否满足日志记录格式
*
* @param methodName
* @return
*/
public static boolean verifyMethodName(String methodName) {
boolean methodNameFlag = false;
for (int i = 0; i < DEFAULT_RECORD_METHOD_START.length; i++) {
if (methodName.startsWith(DEFAULT_RECORD_METHOD_START[i])) {
methodNameFlag = true;
break;
}
}
return methodNameFlag;
}
/**
* 填充日记记录SQL参数
*
* @param methodFullName
* @param desc
* @param originalSql
* @return
*/
private static List<Object> getParamList(String methodFullName, String desc, String originalSql) {
List<Object> paramList = new ArrayList<>();
// 完整SQL语句
paramList.add(handlerSql(originalSql));
//时间
paramList.add(LocalDateTime.now().toString());
return paramList;
}
/**
* 处理SQL语句
*
* @param originalSql
* @return
*/
private static String handlerSql(String originalSql) {
// String sql = originalSql.substring(originalSql.indexOf(":") + 1);
// 将原始sql中的空白字符(\s包括换行符,制表符,空格符)替换为" "
return originalSql.replaceAll("[\\s]+", " ");
}
/**
* 获取日志保存SQL
*
* @param methodFullName
* @param desc
* @param originalSql
* @return
*/
public static String getSaveLogSql(String methodFullName, String desc, String originalSql) {
String sql = logUtils.sqlSessionFactory.getConfiguration()
.getMappedStatement(DEFAULT_NOT_RECORED_METHOD[0]).getBoundSql(null).getSql();
List<Object> paramList = getParamList(methodFullName, desc, originalSql);
// paramList 是你需要存到数据库的 数据
sql = paramList != null && !paramList.isEmpty() ? SQLUtils.format(sql, JdbcConstants.MYSQL, paramList) : null;
return sql;
}
/**
* 获取mysql Connection对象
*
* @return
*/
public static Connection getMysqlConnection() {
Connection conn = null;
try {
conn = logUtils.mysqlDataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
log.error("保存日志时获取Connection对象异常!", e);
}
return conn;
}
}
第三步:
创建一个 存储sql 语句的 Mapper 方法
package com.erp.base.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.erp.api.entities.base.base.YsSqlLog;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* sql日志表 Mapper 接口
* </p>
*
* @author Json
* @since 2022-11-22
*/
@Mapper
public interface YsSqlLogMapper extends BaseMapper<YsSqlLog> {
@Insert({"insert into ys_sql_log(sql_info,date) values(#{ysSqlLog.sqlInfo},#{ysSqlLog.createTime})"})
int saveSqlLog(YsSqlLog ysSqlLog);
}
最后再执行 sql 的时候 数据库表里 就会记录 执行的 sql 语句
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。