当前位置:   article > 正文

spring boot 2.x 使用mybatis拦截器实现系统日志记录(将完整参数的SQL语句记录到数据库中)_mybatis将完整的sql保存到数据库中

mybatis将完整的sql保存到数据库中

一、前言

最近要求做系统的全局日志记录功能,要求把执行的完整SQL语句保存到数据库中,在实际开发中最终放弃了使用AOP的方法,改由使用mybatis的拦截器实现,这里简单记录一下实现过程。

系统数据库主要环境:

  • 数据库:MySQL
  • 数据源:DruidDataSource

二、创建数据库用表

这里只是简单演示一下实现过程,只用两张表模拟一下:

  • 用户表(user)
CREATE TABLE `user`  (
  `id` bigint(20) NOT NULL,
  `age` int(11) NULL DEFAULT NULL,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 日志表(sys_log)
CREATE TABLE `sys_log`  (
  `id` bigint(20) NOT NULL COMMENT '主键',
  `uri` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '调用的接口',
   `daoMethodName` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'DAO层执行的方法名称',
  `ip` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'ip地址',
  `wholeSql` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '完整SQL语句',
  `desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '描述',
  `createDate` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

注意:不要使用“SQL”关键字作为字段名称,否则后续系统执行SQL语句会报错。

三、创建演示项目

  1. 创建一个spring boot项目,项目结构如下:

在这里插入图片描述
2. 导入maven依赖:

        <!--spring boot web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
        <!--druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!--apache工具类-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.11</version>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--spring boot test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
  • 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
  1. 修改application.yml配置文件:
server:
  port: 8888
spring:
  datasource:
     url: jdbc:mysql://localhost:3306/bg-learnsp?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false # 连接数据库的url
     username: root # 连接数据库的用户名
     password: root # 连接数据库的密码
     driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
  mapper-locations: classpath:/mapper/*.xml
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  1. 创建用户和日志的实体类:
  • User类:
package com.learn.entity.user;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.io.Serializable;

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {

    private long id;

    private Integer age;

    private String name;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • SysLog类:
package com.learn.entity.log;

import lombok.Data;

import java.util.Date;

@Data
public class SysLog {

    private long id;

    private String uri;

	private String daoMethodName;
	
    private String ip;

    private String wholeSql;

    private String desc;

    private Date createDate;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  1. 创建用户和日志的mapper接口和xml文件:
  • UserMapper:
package com.learn.mapper.user;

import com.learn.entity.user.User;

import java.util.List;

public interface UserMapper {

    List<User> selectAllUsers();

    int insert(User user);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • UserMapper.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.learn.mapper.user.UserMapper">
    <select id="selectAllUsers"  resultType="com.learn.entity.user.User">
        select id,age,name from user
    </select>

    <insert id="insert" parameterType="com.learn.entity.user.User">
        INSERT INTO USER(ID,NAME, AGE) VALUES(#{id},#{name}, #{age})
    </insert>
</mapper>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • LogMapper:
package com.learn.mapper.log;

import com.learn.entity.log.SysLog;

public interface LogMapper {

    int insertLog(SysLog sysLog);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • SysLogMapper.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
        
<mapper namespace="com.learn.mapper.log.LogMapper">

   <insert id="insertLog" parameterType="com.learn.entity.log.SysLog">
       INSERT INTO `sys_log`(`id`, `uri`,`daoMethodName`, `ip`, `wholeSql`,`desc`, `createDate`) VALUES (#{id}, #{uri}, #{daoMethodName}, #{ip}, #{wholeSql}, #{desc}, #{createDate})
    </insert>
    
</mapper>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  1. 修改启动类,添加相关配置:
@SpringBootApplication
@MapperScan("com.learn.mapper.user") //mapper包扫描
@EnableTransactionManagement
public class SpringbootLogrecordApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootLogrecordApplication.class, args);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

四、自定义日志注解和增加工具类

为了增加日记记录的灵活性,增加两个自定义日志注解:

  • RecordLog注解:
package com.learn.annotation.log;

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented

/**
 * 使用此注解标识DAO层需要日记记录的方法
 *
 */

public @interface RecordLog {
    /**
     * 日志记录描述
     *
     * @return
     */
    String desc() default "";
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • UnRecordLog注解:
package com.learn.annotation.log;

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented

/**
 * 使用此注解标识DAO层不需要日记记录的方法
 *
 */
public @interface UnRecordLog {
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

为了方便开发,增加几个工具类:

  • GlobalWebVarUtil工具类主要用于获取HttpServletRequest对象:
public class GlobalWebVarUtil {
    /**
     * 得到HttpServletRequest对象
     *
     * @return
     */
    public static HttpServletRequest getRequest() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        return requestAttributes != null ? ((ServletRequestAttributes) requestAttributes).getRequest() : null;
    }

    /**
     * 设置父线程requestAttributes共享 当异步执行的DAO方法需要记录日志时,需要先调用此方法设置
     */
    public static void setParentRequestShare() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes != null) {
            RequestContextHolder.setRequestAttributes(requestAttributes, true);
        }
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • IPUtils工具类主要用于获取ip地址:
@Slf4j
public class IPUtils {
    /**
     * 获取请求主机IP地址,如果通过代理进来,则透过防火墙获取真实IP地址;
     *
     * @param request
     * @return
     */
    public static String getIpAddress(HttpServletRequest request) {
        // 获取请求主机IP地址,如果通过代理进来,则透过防火墙获取真实IP地址
        String ip = request.getHeader("X-Forwarded-For");

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_CLIENT_IP");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
            }
        } else if (ip.length() > 15) {
            String[] ips = ip.split(",");
            for (int index = 0; index < ips.length; index++) {
                String strIp = (String) ips[index];
                if (!("unknown".equalsIgnoreCase(strIp))) {
                    ip = strIp;
                    break;
                }
            }
        }
        if ("127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip)) {
            // 根据网卡取本机配置的IP
            try {
                ip = getLocalIp();
            } catch (UnknownHostException e) {
                log.error("获取请求主机IP地址异常!", e);
                ip = "UNIP";
                e.printStackTrace();
            }
        }
        return ip;
    }


    /**
     * 获取本机IP
     */
    public static String getLocalIp() throws UnknownHostException {
        InetAddress inetAddress = InetAddress.getLocalHost();
        String ip = inetAddress.getHostAddress().toString();// 获得本机Ip
        return ip;
    }
}
  • 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

接下来重点介绍一下LogUtils工具类:

@Component
@Slf4j
public class LogUtils {
    /**
     * 默认记录操作日志的DAO层方法名开头
     */
    public static final String[] DEFAULT_RECORD_METHOD_START = {"insert", "update", "delete", "remove"};

    /**
     * 默认不记录的操作方法(记录日志的方法)
     */
    public static final String[] DEFAULT_NOT_RECORED_METHOD = new String[]{"com.learn.mapper.log.LogMapper.insertLog"};

    private static LogUtils logUtils;

    /**
     * 注入SqlSessionFactory对象
     */
    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    /**
     * 注入DataSource对象
     */
    @Autowired
    private DataSource mysqlDataSource;

    private LogUtils() {
    }

    /**
     * 给logUtils对象赋值
     */
    @PostConstruct
    public void init() {
        logUtils = this;
        logUtils.sqlSessionFactory = this.sqlSessionFactory;
        logUtils.mysqlDataSource = this.mysqlDataSource;
    }

    /**
     * 判断方法名是否满足日志记录格式
     *
     * @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;
    }

    /**
     * 验证方法是否需要日志记录
     *
     * @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 = StringUtils.EMPTY;
        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) {
            Method[] methods = clazz.getDeclaredMethods();
            for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];
                if (methodName.equals(method.getName())) { // 找到当前方法
                    RecordLog rl = method.getAnnotation(RecordLog.class); // 判断是否有RecordLog注解
                    if (rl != null) { // 有RecordLog注解,直接进行记录
                        isRecord = true;
                        desc = rl.desc();
                    } else {
                        // 没有UnRecordLog注解,并且方法满足记录格式则进行记录
                        if (method.getAnnotation(UnRecordLog.class) == null && verifyMethodName(methodName)) {
                            isRecord = true;
                        }
                    }
                    break;
                }
            }
        }
        resultMap.put("isRecord", isRecord); // 是否记录
        resultMap.put("desc", desc); // 方法描述
        return resultMap;
    }

    /**
     * 填充日记记录SQL参数
     *
     * @param methodFullName
     * @param desc
     * @param originalSql
     * @return
     */
    private static List<Object> getParamList(String methodFullName, String desc, String originalSql) {
        List<Object> paramList = new ArrayList<>();

        String unknownFlag = "UNKNOWN";
        // 获取Request对象
        HttpServletRequest request = GlobalWebVarUtil.getRequest();

        String uri;
        String ip;

        if (request == null) {
            uri = unknownFlag;
            ip = unknownFlag;
        } else {
            uri = request.getRequestURI();
            ip = IPUtils.getIpAddress(request);
        }
        // id
        paramList.add(System.currentTimeMillis());
        // 调用的接口
        paramList.add(uri);
         //DAO层执行的方法名称
        paramList.add(methodFullName);
        // ip地址
        paramList.add(ip);
        // 完整SQL语句
        paramList.add(handlerSql(originalSql));
        // 描述
        paramList.add(desc);
        // 创建时间
        paramList.add(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));

        return paramList;
    }

    /**
     * 处理SQL语句
     *
     * @param originalSql
     * @return
     */
    private static String handlerSql(String originalSql) {
        String sql = originalSql.substring(originalSql.indexOf(":") + 1);
        // 将原始sql中的空白字符(\s包括换行符,制表符,空格符)替换为" "
        return sql.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);
        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;
    }

}
  • 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

工具类的代码注释写的很清晰了,主要就是判断一下DAO层的方法是否需要记录(默认记录DAO层以"insert", “update”, “delete”, "remove"开头的方法),以及填充日志记录SQL进行记录保存到数据库,代码中使用的SQLUtils工具类,是druid自带的工具类。

五、日志记录拦截器实现

自己实现一个mybatis的拦截器,然后进行判断是否需要日志记录,核心代码:

package com.learn.interceptor;

import com.learn.util.LogUtils;
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.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.ResultHandler;
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;

@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})
})
@Slf4j
@Component
public class MybatisLogInterceptor implements Interceptor {

    @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();

        // 判断是否是需要日志记录的方法
        Map<String, Object> map = LogUtils.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;
            }

            MetaObject stmtMetaObj = SystemMetaObject.forObject(statement);
            // 获取Statement对象(sql语法已经构建完毕)
            statement = (Statement) stmtMetaObj.getOriginalObject();
            // 获取sql语句
            String originalSql = statement.toString();

            String saveLogSql = LogUtils.getSaveLogSql(methodFullName, (String) map.get("desc"), originalSql);

            if (StringUtils.isNotBlank(saveLogSql)) {
                Connection connection = statement.getConnection();

                if (connection.isReadOnly()) { // 当前事务是只读事务,则重新用不同的Connection对象
                    Connection mysqlConnection = LogUtils.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;
    }
}
  • 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

拦截器的核心代码已经全都注释的很清晰了,思路如下:

  1. 判断该DAO层的方法是否需要记录日志;
  2. 如果需要记录,则获取日志记录的完整SQL;
  3. 获取当前Connection对象,执行日志记录SQL(如果当前Connection对象是只读的,则重新从数据源获取一个新的Connection对象)。

六、测试

为了简单,创建一个UserController类进行测试:

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 查询所有user
     *
     * @return
     */
    @GetMapping("listAll")
    public List<User> listAllUsers() {
        return userService.listAllUsers();
    }

    @GetMapping("saveDefaultUser")
    public int saveDefaultUser() {
        log.info("保存了默认用户");
        return userService.saveDefaultUser(new User(System.currentTimeMillis(), 23, "admin"));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

运行程序,分别访问:

查询数据库sys_log表:

在这里插入图片描述

可以看到成功记录了com.learn.mapper.user.UserMapper#insert方法执行的完整SQL。

下面,使用一下 @RecordLog注解和@UnRecordLog注解:

public interface UserMapper {

    @RecordLog(desc="查询所有用户")
    List<User> selectAllUsers();

    @UnRecordLog
    int insert(User user);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

先删除sys_log中的数据,再次访问上面两个链接地址,查询数据库sys_log表:

在这里插入图片描述

可以看到,这次只记录了selectAllUsers方法执行的SQL,没有记录insert方法执行的SQL。

到此,spring boot 2.x 使用mybatis拦截器实现系统日志记录(将完整参数的SQL语句记录到数据库中)就简单实现了。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/194532?site
推荐阅读
相关标签
  

闽ICP备14008679号