首页 > 其他分享 >【Mybatis】Mybatis拦截器+注解,实现敏感数据自动加解密

【Mybatis】Mybatis拦截器+注解,实现敏感数据自动加解密

时间:2024-09-29 12:50:48浏览次数:10  
标签:拦截器 return String param 敏感数据 key Mybatis 加密 class

一:背景

         今天,公司新要求对数据库的敏感数据进行加密,对手机号、身份证号、姓名等一些敏感数据进行加密处理,要求:

        1. 通过程序实现数据加密解密 (快速便捷,尽量减少对原先代码的修改)

        2. 可以通过sql查询对数据进行解密(通过密钥直接对数据库加密参数进行解密)

        3. 数据库:mysql

二:思考

        1. 需要用到sql对数据库进行直接解密,同时又比较安全,第一个想到的是使用mysql自带的AES对数据进行加密

        2. 对代码减少修改,想到使用mybatis拦截器实现,对 sql语句 或者 参数 进行修改

        3. 数据只需要在入库层进行加解密,上层业务调用不需要再考虑敏感数据的加解密问题

三:mysql AES加解密

-- 加密 key: 123213  val: qwertyuiop123

select to_base64(AES_ENCRYPT('qwertyuiop123','123213'))

-- 返回:ZDuEEEeORjQFUlr7rkTsQg==



-- 解密:key: ZDuEEEeORjQFUlr7rkTsQg==  val: qwertyuiop123

SELECT CONVERT(AES_DECRYPT(from_base64('ZDuEEEeORjQFUlr7rkTsQg=='), '123213') USING UTF8MB4)

-- 返回:qwertyuiop123

四:实现一: 修改sql语句实现入库加密

        实现方式:

@Component
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class InsuranceMybatisPlugin implements Interceptor {

    private String myBatisAESKey = "123123";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
            // 获取要执行的 sql
            BoundSql boundSql = statementHandler.getBoundSql();
            String sqlBefore = boundSql.getSql();

            // 判断当前sql 是否需要加密
            boolean containsChannel = sqlBefore.contains("{{myBatisAESKey}}");

            // 替换加密需要的key
            if(StringUtils.isNotBlank(sqlBefore) && containsChannel){
                
                // 替换key值    
                String sqlAfter = sqlBefore.replaceAll("\\{\\{myBatisAESKey\\}\\}", myBatisAESKey );
                Field sqlField = boundSql.getClass().getDeclaredField("sql");
                
                // 重新设置对应sql
                sqlField.setAccessible(true);
                ReflectionUtils.setField(sqlField, boundSql, sqlAfter);
                sqlField.setAccessible(false);
            }
        }catch (Exception e){
            // 打印错误日志
        }
        return invocation.proceed();
    }

}
-- xml sql加密修改 
select 加密字段

-- 改为:
select SELECT to_base64(AES_ENCRYPT(加密字段,'{{myBatisAESKey}}'))


-- xml sql 解密修改
select 解密字段

--改为:
select convert(AES_DECRYPT(from_base64(解密字段), '{{myBatisAESKey}}') using UTF8MB4)

优点:

        1. 实现简单,只需要不到20行代码就能实现

        2. 可以使用mysql自带的解密

        3. 效率比较高,一般sql语句都不会太长,替换比较快

缺点:

        1. 需要修改原先写好的xml sql, 如果项目比较大的话工作量也不怎么轻松

        2. 后续如果换数据库的话,又需要重新更改sql

        3. 系统需要重新测试,防止人为因素导致sql修改错误(粗心导致的sql异常)

五:实现二:mybatis拦截器 + 注解

        1. 需要解决的问题:

                1. 程序加密后 可以使用 mysql 自带的函数进行解密

                2. 不修改原先sql, 就能实现加密

                3. 降低对程序的影响,减少代码侵入

        2. 解决加解密问题

              mysql 使用AES加密,程序我也可以用同样的加解密方式,这不就没问题了,代码实现:

public class EncryptUtil {

    private static final Logger log = LoggerFactory.getLogger(EncryptUtil.class);

    /**
     * 加密算法
     */
    private static final String KEY_ALGORITHM = "AES";


    /**
     * 算法/模式/补码方式
     */
    private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";


    /**
     * 编码格式
     */
    private static final String CODE = "utf-8";


    /**
     * base64验证规则
     */
    private static final String BASE64_RULE = "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)=?$";


    /**
     * 正则验证对象
     */
    private static final Pattern PATTERN = Pattern.compile(BASE64_RULE);



    /**
     * 加密
     * @param content 加密参数
     * @param key     加密key
     * @return 结果字符串
     */
    public static String encrypt(String content, String key) {
        // 判断如果已经是base64加密字符串则返回原字符串
        if (Objects.isNull(content) || isBase64(content)) {
            return content;
        }


        byte[] encrypted = encrypt2bytes(content, key);
        if (null == encrypted || encrypted.length < 1) {
            return null;
        }

        // 转成 base64
        return Base64Utils.encodeToString(encrypted);

    }


    /**
     * @param content 加密字符串
     * @param key     加密key
     * @return 返回加密字节
     */
    public static byte[] encrypt2bytes(String content, String key) {
        try {
            byte[] raw = key.getBytes(CODE);
            SecretKeySpec secretKeySpec = new SecretKeySpec(raw, KEY_ALGORITHM);
            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
            return cipher.doFinal(content.getBytes(CODE));
        } catch (Exception e) {
            log.error("encrypt2bytes to content: {}, key {}", content, key, e);
            return null;
        }
    }



    /**
     * 解密
     *
     * @param content 解密字符串
     * @param key     解密key
     * @return 解密结果
     */
    public static String decrypt(String content, String key) {
        // 不是base64格式字符串则不进行解密
        if (!isBase64(content)) {
            return content;
        }

        // 先转成 Base64 再进行解密
        return decrypt(Base64Utils.decodeFromString(content), key);
    }



    /**
     * @param content 解密字节
     * @param key     解密key
     * @return 返回解密内容
     */
    public static String decrypt(byte[] content, String key) {
        if (key == null) {
            return null;
        }

        try {
            byte[] raw = key.getBytes(CODE);
            SecretKeySpec keySpec = new SecretKeySpec(raw, KEY_ALGORITHM);
            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, keySpec);
            byte[] original = cipher.doFinal(content);
            return new String(original, CODE);
        } catch (Exception e) {
            log.error("failed to decrypt content: {} key: {}", content, key, e);
            return null;
        }
    }



    /**
     * 判断是否为 base64加密
     *
     * @param str 参数
     * @return 结果
     */
    public static boolean isBase64(String str) {
        Matcher matcher = PATTERN.matcher(str);
        return matcher.matches();
    }



    /**
     * 加密 解密实验
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        System.out.println(EncryptUtil.encrypt("138094asdas49025", "qwertyuieqweqw12"));
        System.out.println(EncryptUtil.decrypt("BQOBvTgoRzEhHuNwgn1llTfVcvpwHahOzd5otNKlsRY=", "qwertyuieqweqw12"));

       // 执行结果为:
       // 加密: BQOBvTgoRzEhHuNwgn1llTfVcvpwHahOzd5otNKlsRY=
       // 解密: 138094asdas49025
    }

mysql 加解密:

select to_base64(AES_ENCRYPT('138094asdas49025','qwertyuieqweqw12'))
select convert(AES_DECRYPT(from_base64('BQOBvTgoRzEhHuNwgn1llTfVcvpwHahOzd5otNKlsRY='), 'qwertyuieqweqw12') using UTF8MB4)

-- 执行结果
-- 加密:BQOBvTgoRzEhHuNwgn1llTfVcvpwHahOzd5otNKlsRY=
-- 解密:138094asdas49025

        3. 实现参数加密,入参加密:

                方法标识SensitiveMethodParam 注解
/**
 * 方法标识,入参加密
 */
@Inherited
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveMethodParam {

}
        参数标识SensitiveParam注解,具体加密的字段
@Documented
@Inherited
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveParam {
}
        拦截器实现:
@Component
@Intercepts(
        {
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
                @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 BarExecuteInterceptor implements Interceptor {

    private static final Logger log = LoggerFactory.getLogger(BarExecuteInterceptor.class);

    private final String AES_KEY = "qwertyuiopasdfgh";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        // 获取当前方法
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];

        // 方法为空, 正常执行
        Method interfaceMethod = InterceptorUtil.getInterfaceMethod(mappedStatement.getId());
        Object parameterObject = invocation.getArgs()[1];

        // 一个参数的情况下 直接放入当前参数,多个参数的情况下 ParamMap 将结果进行包装
        // 存在 param 注解 key 为 param
        // 验证是否存在加密参数方法
        if (Objects.isNull(interfaceMethod) || Objects.isNull(parameterObject) || isNotSensitiveMethodParam(interfaceMethod))
            return invocation.proceed();

        // 获取加密的参数名称,多个参数
        Set<String> sensitiveNameSet = getSensitiveNameSet(interfaceMethod);

        // 多个参数 mybatis 使用 MapperMethod.ParamMap进行包装
        sensitiveParam(invocation, sensitiveNameSet, true);

        // 重新生成动态参数, list<Sting>集合的动态参数会被解析出来,所以需要重新生成一下参数
        setBoundSql(mappedStatement, invocation.getArgs());

        // 执行完成后将参数进行解密,防止影响后续流程,
        Object result;
        try {
            result =  invocation.proceed();
        } finally {
            sensitiveParam(invocation, sensitiveNameSet, false);
        }

        // 返回执行结果
        return result;
    }



    /**
     * 验证当前方法是否为加密参数方法
     *
     * @param interfaceMethod MappedStatement 对应方法
     * @return
     */
    private boolean isNotSensitiveMethodParam(Method interfaceMethod) {
        SensitiveMethodParam annotation = interfaceMethod.getAnnotation(SensitiveMethodParam.class);
        return Objects.isNull(annotation);
    }



    /**
     * 获取方法中添加了注解的方法
     *
     * @param method
     * @return
     */
    private Set<String> getSensitiveNameSet(Method method) {
        Set<String> sensitiveNames = new HashSet<>();
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            Annotation[] annotations = method.getParameterAnnotations()[i];

            // 验证参数 是否存在 SensitiveMethodParam 注解
            boolean noneMatch = Arrays.stream(annotations)
                    .noneMatch(annotation -> annotation instanceof SensitiveParam);
            if (noneMatch) continue;

            // 获取 paramMap 的 key, Param注解 > 形参名称
            String name = Arrays.stream(annotations)
                    .filter(annotation -> annotation instanceof Param)
                    .map(annotation -> ((Param) annotation).value())
                    .findFirst()
                    .orElse(parameter.getName());

            sensitiveNames.add(name);
        }

        return sensitiveNames;
    }


    /**
     * 加解密参数
     * @param invocation 对象
     * @param sensitiveNameSet 加密参数
     * @param isEncrypt 加解密标识
     */
    private void sensitiveParam(Invocation invocation,Set<String> sensitiveNameSet, boolean isEncrypt){
        if (invocation.getArgs()[1] instanceof MapperMethod.ParamMap) {
            @SuppressWarnings("unchecked")
            Map<String, Object> stringObjectMap = (Map<String, Object>) invocation.getArgs()[1];
            sensitiveParamMap(stringObjectMap, sensitiveNameSet, isEncrypt);
            return;
        }

        invocation.getArgs()[1] = sensitiveParamOther(invocation.getArgs()[1], sensitiveNameSet, isEncrypt);
    }


    /**
     * 处理 类型为 paramMap
     * @param parameterObject
     * @param sensitiveNameSet
     */
    private void sensitiveParamMap(Map<String, Object> parameterObject, Set<String> sensitiveNameSet, boolean isEncrypt) {
        for (String key : sensitiveNameSet) {
            // 获取需要加密的 value
            Object value = parameterObject.get(key);

            // 值为字符串
            if (value instanceof String) {
                parameterObject.put(key, sensitiveString((String) value, isEncrypt));
            }

            // 值为集合, 没有处理集合为不可变类型 如: Collections.singletonList()
            else if(value instanceof List) {
                @SuppressWarnings("unchecked")
                List<Object> list = (List<Object>) value;
                sensitiveList(list, isEncrypt);
            }

            // 其他类型处理
            else {
                sensitiveObject(value, isEncrypt);
            }
        }
    }



    /**
     * 处理其他类型,直接只有一个参数类型,没有进行 paramMap包装,所以需要单独解析
     * @param parameterObject
     * @param sensitiveNameSet
     * @return
     */
    private Object sensitiveParamOther(Object parameterObject, Set<String> sensitiveNameSet, boolean isEncrypt) {
        // 只有一个形参的情况下 没有加密注解 直接跳过
        if (sensitiveNameSet.isEmpty())
            return parameterObject;

        if (parameterObject instanceof String) {
            return sensitiveString((String) parameterObject, isEncrypt);
        }

        // 值为集合, 没有处理集合为不可变类型 如: Collections.singletonList()
        else if (parameterObject instanceof List) {
            @SuppressWarnings("unchecked")
            List<Object> list = (List<Object>) parameterObject;
            sensitiveList(list, isEncrypt);
        }

        // 处理其他类型,
        else {
            sensitiveObject(parameterObject, isEncrypt);
        }

        return parameterObject;
    }



    /**
     * 加密 list
     * @param list
     */
    private void sensitiveList(List<Object> list, boolean isEncrypt) {
        for (int i = 0; i < list.size(); i++) {
            Object val = list.get(i);
            if (val instanceof String) {
                list.set(i, sensitiveString((String) val, isEncrypt));
            } else {
                sensitiveObject(val, isEncrypt);
            }
        }
    }



    /**
     * 加密普通对象
     * @param parameterObject
     */
    private void sensitiveObject(Object parameterObject, boolean isEncrypt) {
        // 过滤调一些不需要加密的类型,
        if (parameterObject instanceof Map || parameterObject instanceof Collection
                || parameterObject instanceof Number || parameterObject instanceof Boolean
                || parameterObject instanceof String || parameterObject instanceof Date)
            return;

        // 如果是普通对象,检查是否有@SensitiveField注解的字段
        // 只处理普通对象
        Class<?> clazz = parameterObject.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            if (Objects.isNull(field.getAnnotation(SensitiveParam.class))) // 验证数据是否需要加密
                continue;

            try {// map 和 其他类型 不进行处理
                field.setAccessible(true);
                Object value = field.get(parameterObject);
                if (value  instanceof String) {
                    field.set(parameterObject, sensitiveString((String) value, isEncrypt));
                } else if (value  instanceof List) {
                    @SuppressWarnings("unchecked")
                    List<Object> list = (List<Object>) value;
                    sensitiveList(list, isEncrypt);
                } else {
                    sensitiveObject(value, isEncrypt);
                }
            } catch (Exception e){
                log.error("数据加密异常!", e);
            } finally {
                field.setAccessible(false);
            }

        }
    }



    /**
     *
     * @param value 加解密字符串
     * @param isEncrypt true(加密) false(解密)
     * @return
     */
    private String sensitiveString(String value, boolean isEncrypt) {
        return isEncrypt ? EncryptUtil.encrypt(value, AES_KEY)
                : EncryptUtil.decrypt(value, AES_KEY);
    }


    /**
     * 处理动态参数
     * @param mappedStatement
     * @param args
     */
    private void setBoundSql(MappedStatement mappedStatement, Object[] args) {
        if(args.length == 6 && Objects.nonNull(args[5])){
            args[5]  = mappedStatement .getBoundSql(args[1]);
        }
    }

        4. 实现返回参数解密:

                方法标识 SensitiveMethodResult注解
/**
 *  方法标识  返参解密
 *  当方法添加此注解 返参参数为 string 或者 list<String> 是会对内部参数进行解密
 *  当返参参数为普通对象时,只有内部属性添加 SensitiveResult 注解的 才会进行解密
 */
@Inherited
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveMethodResult {
}
                返回参数标识SensitiveResult注解
@Documented
@Inherited
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveResult {
}
                 拦截器实现:
@Component
@Intercepts(
        {
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
                @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 RetExecuteInterceptor implements Interceptor {

    private static final Logger log = LoggerFactory.getLogger(RetExecuteInterceptor.class);

    private final String AES_KEY = "qwertyuiopasdfgh";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        // 执行方法, 获取当前方法
        Object proceed = invocation.proceed();
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        Method interfaceMethod = InterceptorUtil.getInterfaceMethod(mappedStatement.getId());

        if(Objects.isNull(proceed) || Objects.isNull(interfaceMethod) || isNotSensitiveMethodResult(interfaceMethod))
            return proceed;


        // 处理返回类型, 返回类型只有 集合 和 数字, 数字不处理 只处理集合
        if(proceed instanceof ArrayList) {
            @SuppressWarnings("unchecked")
            ArrayList<Object> resultList = (ArrayList<Object>) proceed;

            for (int i = 0; i < resultList.size(); i++) {
                Object obj = resultList.get(i);

                // 为空数据直接跳过
                if(Objects.isNull(obj))
                    continue;

                // 字符串直接解密
                if(obj instanceof String) {
                    resultList.set(i, EncryptUtil.decrypt((String) obj, AES_KEY));
                    continue;
                }

                decryptObject(obj);
            }
        }

        return proceed;
    }



    /**
     * 验证当前方法是否为加密参数方法
     *
     * @param interfaceMethod
     * @return
     */
    private boolean isNotSensitiveMethodResult(Method interfaceMethod) {
        SensitiveMethodResult annotation = interfaceMethod.getAnnotation(SensitiveMethodResult.class);
        return Objects.isNull(annotation);
    }


    /**
     * 处理对象类型
     * @param obj 解密的对象
     */
    private void decryptObject(Object obj){
        // 过滤调一些不需要解密的类型,防止类型错误导致死循环
        if (obj instanceof Map || obj instanceof Collection || obj instanceof Number ||
                obj instanceof Boolean || obj instanceof String || obj instanceof Date)
            return;

        // 如果是普通对象,检查是否有 @SensitiveResult 注解的字段
        Class<?> clazz = obj.getClass();
        for (Field field : clazz.getDeclaredFields()) {

            // 验证数据是否需要加密
            if (Objects.isNull(field.getAnnotation(SensitiveResult.class)))
                continue;

            try {// 只处理字符串类型
                field.setAccessible(true);
                Object value = field.get(obj);
                if (value instanceof String) {
                    field.set(obj, EncryptUtil.decrypt((String) value, AES_KEY));
                }
            } catch (Exception e){
                log.error("数据加密异常!", e);
            } finally {
                field.setAccessible(false);
            }
        }
    }
                InterceptorUtil实现:
public class InterceptorUtil {

    private static final Logger log = LoggerFactory.getLogger(InterceptorUtil.class);


    /**
     * 获取 接口方法
     *
     * @param mappedStatement mybatis 拦截器 Interceptor 参数
     * @return 接口方法
     */
    public static Method getInterfaceMethod(String statementId) {
        // 获取MappedStatement的 id,格式为namespace.id
        try {
            // 获取 namespace 和 id
            int lastDot = statementId.lastIndexOf(".");
            String namespace = statementId.substring(0, lastDot);
            String methodId = statementId.substring(lastDot + 1);
            Class<?> mapperInterface = Class.forName(namespace);// 获取接口类型
            for (Method method : mapperInterface.getDeclaredMethods()) {// 获取接口方法
                if (method.getName().equals(methodId))
                    return method;
            }
        } catch (Exception e) {
            log.error("方法获取失败,获取属性: {}", statementId);
        }

        return null;
    }

         5. 案例:

    @SensitiveMethodParam
    int insertEdcMonitorLog(@SensitiveParam EdcMonitorLog edcMonitorLog);

    @SensitiveMethodParam
    int insertEdcMonitorLog2(@SensitiveParam List<String> edcMonitorLog);

    @SensitiveMethodParam
    int insertEdcMonitorLog3(@SensitiveParam List<EdcMonitorLog> edcMonitorLog);

    @SensitiveMethodParam
    @SensitiveMethodResult
    List<EdcMonitorLog> selectEdcMonitorLog(@SensitiveParam String msgId);

    @SensitiveMethodParam
    @SensitiveMethodResult
    List<EdcMonitorLog> selectEdcMonitorLog2(@SensitiveParam List<String> msgId);

    @SensitiveMethodParam
    @SensitiveMethodResult
    List<EdcMonitorLog> selectEdcMonitorLog3(@SensitiveParam List<EdcMonitorLog> list);

    @SensitiveMethodParam
    @SensitiveMethodResult
    List<String> selectMsgId(@SensitiveParam @Param("list1") List<String> list1, @Param("list2") List<String> list2);
@Data
public class EdcMonitorLog {

    @SensitiveParam
    @SensitiveResult
    private String msgId;

}

         优点:

                    1. 对代码没有侵入,对sql也没有修改

                    2. 可以通过数据库对参数进行直接解密

          缺点:

                    1. 如果参数比较复杂的情况下,多级嵌套,可能影响效率

                    2. 获取方法没有进行优化(getInterfaceMethod) 可以缓存解析结果,本人偷个懒,期待大佬帮忙优化一下

                    3. 普通对象需要加密的字段也可以缓存(期待大佬优化)

                    4. 当前只加密了字符串类型,其他类型没有处理(没有需求)

                    5. 程序加密的AES算法要求的密钥长度必须是128, 192或256位,但是使用mysql是没有影响的

六:mybatis插件原理

Mybatis的插件,是采用责任链机制,通过JDK动态代理来实现的。默认情况下,Mybatis允许使用插件来拦截四个对象:

Executor:执行CURD操作;
StatementHandler:处理sql语句预编译,设置参数等相关工作;
ParameterHandler:设置预编译参数用的;
ResultSetHandler:处理结果集。

        方案一:拦截预编译sql

        方案二:拦截执行器 query、update方法

标签:拦截器,return,String,param,敏感数据,key,Mybatis,加密,class
From: https://blog.csdn.net/weixin_43262384/article/details/142627898

相关文章

  • Mybatis学习笔记
    目录Mybatis简介MyBatis历史MyBatis特性MyBatis下载和其它持久化层技术对比搭建MyBatis开发环境创建maven工程创建MyBatis的核心配置文件创建mapper接口创建MyBatis的映射文件通过junit测试功能加入log4j日志功能核心配置文件详解默认的类型别名MyBatis的增删改查MyBatis获取参数......
  • SpringBoot-MybatisPlus项目中,在控制台查看sql执行日志的方法
    SpringBoot-MybatisPlus项目中,在控制台查看sql执行日志的方法springboot、maven、mybatisplus、sql、日志、控制台、console、log背景在baomidou.com学习mybatisPlus入门的过程中,接触到表名和关键词冲突,加注解加表名引号后问题解决。不过我还想,在控制台打印一下执行......
  • 使用 MyBatis 原生功能
    我们使用MyBatis-Flex作为MyBatis的增强框架进行代码开发,并不会影响原有的MyBatis的任何功能。一.使用 @Select 等MyBatis原生注解1.创建数据库表并插入数据CREATETABLEIFNOTEXISTStb_userinfo(idINTAUTO_INCREMENTCOMMENT'用户编号'PRIMARYKE......
  • IDea中使用MyBatis 实现原生功能和xml分页功能
    在开始之前,我们假定您已经:熟悉Java环境配置及其开发熟悉关系型数据库,比如MySQL熟悉SpringBoot及相关框架熟悉Java构建工具,比如Maven        今天我们要了解和学习的是MyBatis原生功能,在MyBatis中提供了 @Insert 、@Delete 、@Update 、@Select 4......
  • Mybatis(三)
    4.9.4延迟加载        延迟加载,就是在使用数据时,进行查询操作,不使用时,不提前加载。可以节省内存,提高查询效率。第一种方式:局部配置(映射文件)在<association>标记里配置如下属性:fetchType="lazy"     lazy:延迟加载 eager:不延迟加载如下: <......
  • SpringBoot与MyBatis-Plus的整合与综合实例
    MyBatis是一款优秀的持久层框架,它支持定制化SQL、存储过程、以及高级映射。MyBatis3提供的注解可以取代XML。例如,使用注解@Select直接编写SQL完成数据查询。MyBatis-Plus是一个对MyBatis进行增强的工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生......
  • MyBatis-Plus分页查询
    在实际开发中,对于大量数据的查询,可以通过分页查询的方式来减少查询量和提高查询效率。在MyBatis-Plus中,分页查询可以通过使用Page对象和IService接口提供的分页方法来实现。MyBatis-Plus的分页插件PaginationInnerInterceptor提供了强大的分页功能,支持多种数据库,使得......
  • 轻松上手MyBatis反向工程:从零到一的自动化代码生成
    前言反向工程概念:反向工程是从已有的具体实现(如数据库表结构)中推导出高层次的信息(如Java代码)的过程。在MyBatis中,它特指根据数据库表结构自动生成Java实体类、Mapper接口和XML映射文件。原理:MyBatis反向工程通过读取数据库表结构的元数据(如表名、字段名、字段类型等),然后利......
  • 深入剖析 MyBatis-Plus:操作总结、对比与实践案例(CRUD 操作、分页、条件构造器、自动填
    MyBatis-Plus是MyBatis的增强工具,它极大简化了MyBatis的配置和操作,提高了开发效率。本文从基本操作到高阶用法,详细介绍了MyBatis-Plus的常见功能及与MyBatis的区别,并通过实际案例展示其强大的扩展能力。MyBatis-Plus基于MyBatis,但旨在减少开发者的代码量,增强可......
  • Mybatis详细教程 (万字详解)
    Mybatis3.5.14来自于B站‘天气预报’,一名宝藏up,跟着他可以培养起独立解决编程问题的能力!!!01.简介1.1官网官方中文网:MyBatis中文网中文网参考手册1.2概念MyBatis是一款优秀的持久层框架,支持自定义SQL,存储过程,高级映射官方解释必要前置技术Mys......