MyBatis 插件介绍及应用

MyBatis 插件介绍及应用

MyBatis 是一个持久层框架,它允许开发者自定义 SQL 语句并将其映射到 Java 对象中。MyBatis 提供了一种灵活的数据库操作方式,但随着项目的复杂度增加,一些通用功能如分页、缓存、事务管理等可能需要重复编写。为了解决这个问题,MyBatis 提供了插件机制,允许开发者扩展 MyBatis 的功能,实现自定义逻辑。

一、MyBatis 插件概述

MyBatis 插件是 MyBatis 框架的扩展点,它们可以拦截 MyBatis 的核心处理过程,包括执行器、参数处理器、结果处理器等。通过编写插件,开发者可以在不修改 MyBatis 核心代码的情况下,增加新的功能或改变 MyBatis 的行为。

1.1 插件的作用

  • 拦截器:在 MyBatis 执行 SQL 之前或之后执行自定义逻辑。
  • 扩展功能:实现 MyBatis 未提供的功能,如分页、性能监控等。
  • 自定义 SQL:通过插件机制,可以自定义 SQL 片段,提高代码的复用性。

1.2 插件的工作原理

MyBatis 插件通过使用 Java 的代理机制实现。开发者需要实现 MyBatis 提供的 Interceptor 接口,并重写 intercept 方法。在 intercept 方法中,可以定义拦截逻辑。

1.3 拦截四种核心组件

MyBatis所允许拦截的⽅法如下:

Executor:执⾏器 (update、query、commit、rollback等⽅法),负责SQL语句的执行和事务管理;
StatementHandler:SQL语法构建器(prepare、parameterize、batch、updates query等⽅ 法),处理具体的SQL语句,包括预编译和参数设置等;
ParameterHandler:参数处理器 (getParameterObject、setParameters⽅法),负责将用户传递的参数转换成JDBC可识别的参数;
ResultSetHandler:结果集处理器 (handleResultSets、handleOutputParameters等⽅法),负责将JDBC返回的结果集转换成用户所需的对象或集合;

二、MyBatis 插件开发

开发 MyBatis 插件需要对 MyBatis 的工作流程有深入的理解。下面是一个简单的插件开发示例。

会针对四种核心组件分别实现一个插件。

2.1Executor 拦截器实现

2.1.1 Query 拓展点
  • 用途:可以在查询操作执行前后添加逻辑,如记录查询时间、进行查询缓存等。
  • 拦截方法query

功能:打印查询sql耗时,以及结果集行数。

package com.company.oneday.plugin;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;

import java.util.*;

@Component
@Intercepts({@Signature(type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class ExecutorQueryPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = invocation.proceed(); // 执行原方法
        long end = System.currentTimeMillis();
        System.out.println("查询时间: " + (end - start) + " ms");
        // 如果查询结果是一个 List,可以打印查询到的行数
        if (result instanceof List<?>) {
            List<?> list = (List<?>) result;
            System.out.println("查询行数: " + list.size());
        }

        return result; // 返回原方法结果
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 可以为插件配置属性
    }
}

image-20240422185834914

2.1.2 Update 拓展点
  • 用途:可以在更新(插入、修改、删除)操作执行前后添加逻辑,如统计影响行数、记录变更日志等。
  • 拦截方法update
package com.company.oneday.plugin;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.springframework.stereotype.Component;

import java.util.Properties;

@Component
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class ExecutorUpdatePlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 记录操作开始时间
        long start = System.currentTimeMillis();

        // 执行 update 操作
        Object result = invocation.proceed(); // 执行目标方法

        // 记录操作结束时间
        long end = System.currentTimeMillis();

        // 计算操作耗时
        long timeElapsed = end - start;

        // 获取影响的行数
        int affectedRows = (Integer) result;

        // 获取 MappedStatement 来获取相关的信息,如 SQL 语句和参数
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        Object parameter = invocation.getArgs()[1];

        // 记录变更日志
        recordChangeLog(mappedStatement, parameter, affectedRows);

        // 打印消耗时间和影响行数
        System.out.println(String.format("更新操作耗时: %d ms, 受影响的行数: %d", timeElapsed, affectedRows));

        return result; // 返回操作影响的行数
    }

    private void recordChangeLog(MappedStatement mappedStatement, Object parameter, int affectedRows) {
        // 模拟记录变更日志的逻辑
        // 这里可以根据实际需要,将变更信息写入日志系统或者存储起来
        String sql = mappedStatement.getBoundSql(parameter).getSql();
        sql = sql.replaceAll("\n", " ").replaceAll(" +", " ").toLowerCase();
        System.out.println("sql 日志打印: " + sql);

        // 可以在这里添加更多的日志记录逻辑,如记录操作的用户、时间戳等
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 可以为插件配置属性
    }
}

image-20240423182936627

2.1.3 Commit 拓展点
  • 用途:可以在事务提交时执行额外的操作,如记录事务提交日志、执行某些后置操作等。
  • 拦截方法commit
package com.company.oneday.plugin;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.springframework.stereotype.Component;

import java.util.Properties;

@Component
@Intercepts({@Signature(type = Executor.class, method = "commit", args = {boolean.class})})
public class ExecutorCommitPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 记录事务提交前的时间
        long start = System.currentTimeMillis();

        // 执行 commit 操作
        Object result = invocation.proceed(); // 执行目标方法

        // 记录事务提交后的时间
        long end = System.currentTimeMillis();

        // 计算事务提交耗时
        long timeElapsed = end - start;

        // 打印事务提交耗时
        System.out.println("事务提交时间: " + timeElapsed + " ms");

        // 记录事务提交日志
        recordTransactionCommitLog();

        // 执行后置操作
        performPostActions();

        return result; // 返回事务提交操作的结果
    }

    private void recordTransactionCommitLog() {
        // 模拟记录事务提交日志的逻辑
        // 这里可以根据实际需要,将事务提交信息写入日志系统或者存储起来
        System.out.println("事务已经提交成功.");
    }

    private void performPostActions() {
        // 模拟执行后置操作的逻辑
        // 后置操作可以是清理缓存、发送通知、统计信息等
        System.out.println("事务提交成功后置通知.");
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 可以为插件配置属性
    }
}

image-20240422193830476

2.1.4 Rollback 拓展点
  • 用途:在事务回滚时执行逻辑,如记录回滚原因、清理资源等。
  • 拦截方法rollback
package com.company.oneday.plugin;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.plugin.*;
import org.springframework.stereotype.Component;

import java.util.Properties;
@Component
@Intercepts({@Signature(type = Executor.class, method = "rollback", args = {boolean.class})})
public class ExecutorRollbackPlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            System.out.println(1111);
            // 执行回滚操作
            return invocation.proceed();
        } catch (Exception t) {
            // 记录回滚原因
            recordRollbackReason(t);
            throw t;
        }
    }

    private void recordRollbackReason(Throwable t) {
        // 实际应用中,这里应将异常信息记录到日志文件或数据库中
        System.err.println("事务回滚原因: " + t.getMessage());
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 可以为插件配置属性
    }
}

2.2 StatementHandler 拦截器实现

  1. prepare 方法
    • 作用:用于准备 Statement 对象。在这个方法中,会创建 PreparedStatement 对象,并且可能涉及动态 SQL 的解析和参数的预处理。
    • 拓展:可以修改 SQL 语句以添加日志、性能监控、防止 SQL 注入、实现分页逻辑等。
  2. parameterize 方法
    • 作用:用于处理参数对象,将参数与 SQL 语句中的占位符进行绑定。
    • 拓展:可以修改参数绑定逻辑,例如使用自定义的类型处理器(Type Handler)或者在参数绑定前后添加额外的处理。
  3. batch 方法
    • 作用:用于执行批量更新操作。在这个方法中,会将多个 SQL 语句打包在一起执行,以提高性能。
    • 拓展:可以监控批量操作的执行情况,或者对批量参数进行预处理。
  4. update 方法
    • 作用:用于执行插入、更新或删除操作。在这个方法中,会执行 Statement 对象以修改数据库中的数据。
    • 拓展:可以统计影响行数、记录操作日志、在执行前后添加事务控制逻辑等。
  5. query 方法
    • 作用:用于执行查询操作。在这个方法中,会执行 Statement 对象以获取查询结果,并将其映射到 Java 对象中。
    • 拓展:可以实现分页查询、缓存查询结果、修改结果集处理逻辑、添加查询性能监控等。
  6. getBoundSql 方法
    • 作用:用于获取 BoundSql 对象,该对象包含了 SQL 语句、参数信息和额外的上下文信息。
    • 拓展:可以修改 BoundSql 中的 SQL 语句或参数,或者添加额外的上下文信息。
  7. 结果集处理器(ResultSetHandler
    • 作用:用于处理 ResultSet 对象,将 SQL 查询的结果集映射到 Java 对象。
    • 拓展:可以自定义结果集的映射逻辑,实现复杂的对象映射关系,或者在结果集处理前后添加额外的处理。
2.2.1 prepare 拓展点
  • 用途:在事务回滚时执行逻辑,如记录回滚原因、清理资源等。
  • 拦截方法rollback
package com.company.oneday.plugin.statement;

import com.alibaba.fastjson.annotation.JSONField;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMap;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;

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

    // 默认的方言类型,可以根据需要进行扩展
    private static final String DIALECT_MYSQL = "mysql";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);

        // 分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过下面的两次操作可以分离出最原始的的目标类)
        while (metaStatementHandler.hasGetter("h")) {
            Object object = metaStatementHandler.getValue("h");
            metaStatementHandler = SystemMetaObject.forObject(object);
        }

        // 获取到当前的映射语句对象(MappedStatement)
        MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");

        // 只对需要分页的查询进行拦截
        if (mappedStatement.getId().endsWith("ByPage")) {
            BoundSql boundSql = statementHandler.getBoundSql();
            String sql = boundSql.getSql();
            PaginationParam paginationParam = null;
            // 获取分页参数
            //兼容下面两种情况,其他的遇到了再补充
            if (boundSql.getParameterObject() instanceof PaginationParam) {
                paginationParam = (PaginationParam) boundSql.getParameterObject();
            } else if (boundSql.getParameterObject() instanceof MapperMethod.ParamMap) {
                MapperMethod.ParamMap map = (MapperMethod.ParamMap) boundSql.getParameterObject();
                List<PaginationParam> collect = (List<PaginationParam>) map.values().stream()
                        .filter(e ->  e instanceof PaginationParam)
                        .collect(Collectors.toList());
                if(!CollectionUtils.isEmpty(collect)) {
                    paginationParam = collect.get(0);
                }
            } else {
            }

            String pageSql = buildPageSql(sql, paginationParam);
            pageSql = pageSql.replaceAll("\n", " ").replaceAll(" +", " ").toLowerCase();
            System.out.println("sql:" + pageSql);
            // 通过反射设置当前boundSql对应的sql为分页sql
            Field sqlField = boundSql.getClass().getDeclaredField("sql");
            sqlField.setAccessible(true);
            sqlField.set(boundSql, pageSql);

            // 采用物理分页后,就不需要mybatis的内存分页了,所以这里将这两个参数都置为null即可
            metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.DEFAULT.getOffset());
            metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.DEFAULT.getLimit());
        }

        // 继续执行原始方法
        return invocation.proceed();
    }

    private String buildPageSql(String sql, PaginationParam paginationParam) {
        if(paginationParam != null && paginationParam.getOffset() != null && paginationParam.getLimit() != null) {
            // 这里只提供了一个简单的MySQL分页示例,实际情况可能需要根据数据库类型动态构建SQL
            sql = sql + " LIMIT " + paginationParam.getOffset() + "," + paginationParam.getLimit();
        }
        return sql;
    }

    @Override
    public Object plugin(Object target) {
        // 创建代理对象
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 处理插件属性(如果有的话)
    }

    // 分页参数类
    @Data
    public static class PaginationParam {
        @TableField(exist = false)
        private Integer offset; // 起始行数

        @TableField(exist = false)
        private Integer limit;  // 每页显示的数量
    }
}

2.3 ParameterHandler 拦截器实现

  1. getParameterObject 方法
    • 作用:获取传递给 StatementHandler 的参数对象。
    • 拓展:可以在这个方法中修改参数对象,例如添加额外的参数、修改参数值或替换参数对象。
  2. setParameters 方法
    • 作用:将参数对象的值设置到 Statement 对象的 SQL 占位符中。
    • 拓展:可以修改参数的设置逻辑,例如使用自定义的类型处理器(Type Handler)或在参数设置前后添加额外的处理。
2.3.1 setParameters 拓展点
package com.company.oneday.plugin.parameterHandler;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.MybatisDefaultParameterHandler;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.*;
import org.springframework.stereotype.Component;

import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.Properties;

@Component
@Intercepts({@Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class})})
public class ParameterHandlerSetParametersPlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        PreparedStatement statement = (PreparedStatement) invocation.getArgs()[0];
        MybatisDefaultParameterHandler parameterHandler = (MybatisDefaultParameterHandler) invocation.getTarget();

        // 打印参数对象
        Object parameter = parameterHandler.getParameterObject();
        System.out.println("Parameter object before setting: " + JSON.toJSONString(parameter));

        // 修改参数对象
        // ........

        // 继续执行参数设置
        Object result = invocation.proceed();

        // 再次打印参数对象,可能已被修改
        System.out.println("Parameter object after setting: " + JSON.toJSONString(parameter));

        return result;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 可以为插件配置属性
    }
}

image-20240425141353042

2.4 ResultSetHandler 拦截器实现

  1. handleResultSets 方法
    • 作用:处理 Statement 对象执行后返回的结果集。
    • 拓展:可以修改结果集的处理逻辑,例如实现自定义的结果集映射、过滤特定列的数据、实现懒加载等。
  2. handleOutputParameters 方法
    • 作用:处理存储过程调用后的输出参数。
    • 拓展:可以对输出参数进行特殊处理,比如转换为特定的 Java 类型。
2.4.1 handleResultSets拓展点

实现对查询结果中的密码password进行MD5加密。

处理器实现:

package com.company.oneday.plugin.resultSetHandler;

import com.company.oneday.entity.User;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.resultset.ResultSetHandler;

import java.sql.CallableStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

public class EncryptingResultSetHandler implements ResultSetHandler {

    private final ResultSetHandler resultSetHandler;

    public EncryptingResultSetHandler(ResultSetHandler resultSetHandler) {
        this.resultSetHandler = resultSetHandler;
    }

    @Override
    public List<Object> handleResultSets(Statement stmt) throws SQLException {
        // 使用委托对象处理结果集
        List<Object> result = this.resultSetHandler.handleResultSets(stmt);

        // 假设我们有一个User对象,并且知道密码字段名为"password"
        // 对密码进行“加密”操作(这里只是示例,实际应该是解密)
        if (result instanceof List) {
            List<?> resultList = (List<?>) result;
            for (Object item : resultList) {
                if (item instanceof User) {
                    User user = (User) item;
                    String encryptedPassword = encryptPassword(user.getPassword());
                    user.setPassword(encryptedPassword);
                }
            }
        }

        return result;
    }

    private String encryptPassword(String password) {
        // 这里应该是你的加密逻辑,为了演示,我们使用一个简单的替换逻辑
        return DigestUtils.md5Hex(password);
    }

    @Override
    public <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException {
        return null;
    }

    @Override
    public void handleOutputParameters(CallableStatement cs) throws SQLException {

    }

    // 其他方法...
}

拦截器实现:

package com.company.oneday.plugin.resultSetHandler;

import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import org.springframework.stereotype.Component;

import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;

@Component
@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
public class ResultSetHandlerHandleResultSetsPlugin implements Interceptor {

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

        Statement stmt = (Statement) invocation.getArgs()[0];

        // 创建自定义的 EncryptingResultSetHandler
        EncryptingResultSetHandler customResultSetHandler = new EncryptingResultSetHandler((ResultSetHandler) invocation.getTarget());
//        Object result = invocation.proceed();
        // 使用自定义的 EncryptingResultSetHandler 重新处理结果集
        return customResultSetHandler.handleResultSets(stmt);
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 可以为插件配置属性
    }
}

image-20240429155148345

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/582950.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【网络原理】IP协议的地址管理和路由选择

系列文章目录 【网络通信基础】网络中的常见基本概念 【网络编程】网络编程中的基本概念及Java实现UDP、TCP客户端服务器程序&#xff08;万字博文&#xff09; 【网络原理】UDP协议的报文结构 及 校验和字段的错误检测机制&#xff08;CRC算法、MD5算法&#xff09; 【网络…

uniapp-vue3-wechat:基于uniapp+vue3仿微信app聊天实例(H5+小程序+App端)

uni-vue3-wchat&#xff1a;基于uni-appvue3pinia2高仿微信app聊天模板。 原创基于最新跨端技术uni-appvue3.xpinia2vite4uv-ui构建三端仿微信app界面聊天实例。实现编辑框多行消息/emoj混合、长按触摸式仿微信语音面板、图片/视频预览、红包/朋友圈等功能。支持编译到H5小程序…

unity3d使用3D WebView播放网页中的视频

Unity2021.3.35f1&#xff0c;硬件ESP32-Cam&#xff0c;3D WebView插件 1.新建工程&#xff0c;导入3D WebView for Winfows和3D WebView for Android 2.打开场景Assets\Vuplex\WebView\Demos\Scenes\2_CanvasWebViewDemo 3.修改Canvas的Render Mode为Screen Space-Camera&am…

Spark持久化、broadcast广播变量和accumulator累加器

持久化操作 什么是持久化&#xff0c;为什么要持久化 Spark中最重要的功能之一是跨操作在内存中持久化&#xff08;或缓存&#xff09;数据集。当您持久化RDD时&#xff0c;每个节点将其计算的任何分区存储在内存中&#xff0c;并在该数据集&#xff08;或从该数据集派生的数…

AI大模型日报#0429:人大多模态Awaker1.0、清华「AI+材料」落地、微软小模型Orca-Math、GenAI黄金法则

导读&#xff1a; 欢迎阅读《AI大模型日报》&#xff0c;内容基于Python爬虫和LLM自动生成。目前采用“文心一言”生成了今日要点以及每条资讯的摘要。AI大模型日报今日要点&#xff1a; 中山大学与重庆大学合作开发的基于Transformer的单细胞注释方法SANGO在跨样本、平台和组织…

平安城市 校园 景区 停车场网络语音对讲立柱SV-11TS

平安城市 校园 景区 停车场网络语音对讲立柱SV-11TS 长方形立柱式设计&#xff0c;外观简约&#xff0c;线条优美&#xff0c;工艺考究&#xff0c;坚固耐用可一键实现在紧急情况下求助报警、事件咨询&#xff0c;与SIP网络对讲主机进行全双工对讲。内置警示灯&#xff0c;呼叫…

接口自动化框架篇:使用python连接数据库 - PySQL介绍!

PySQL介绍&#xff1a;使用Python连接数据库的接口自动化框架 在接口自动化测试中&#xff0c;经常需要使用数据库来操作测试数据&#xff0c;验证接口返回的数据是否正确。Python是一种功能强大的编程语言&#xff0c;可以轻松地连接数据库&#xff0c;并进行各种数据库操作。…

保证接口幂等性(token机制)

现在继续讲一讲保证接口的幂等性——使用token机制&#xff0c;并编写代码实现&#xff01; 1. 概念 Token机制是实现接口幂等性的一种常见策略&#xff0c;尤其是在处理如订单创建、支付确认等敏感操作时&#xff0c;确保即使用户因网络延迟、误操作等原因重复提交请求&#…

【TDengine】mac m1解决no taos in java.library.path

前言 使用macos搭建springbootmybatisplus&#xff0c;通过mqtt将数据更新到tdenigne 3.2.3&#xff0c;数据源使用远程服务器的tdengine。 问题 启动时报错&#xff1a; Caused by: java.lang.UnsatisfiedLinkError: no taos in java.library.path 以下是官方文档 打开本…

动手学深度学习——矩阵

1. 基本概念 1.1 标量 标量由只有一个元素的张量表示。 所以标量计算与程度开发中的普通变量计算没有差异。 import torchx torch.tensor(3.0) y torch.tensor(2.0)x y, x * y, x / y, x**y(tensor(5.), tensor(6.), tensor(1.5000), tensor(9.))1.2 向量 向量泛化自标量…

Hbase学习笔记

Hbase是什么 HBase是一个高可靠、高性能、面向列、可伸缩的分布式存储系统。它利用Hadoop HDFS作为其文件存储系统,并提供实时的读写的数据库系统。HBase的设计思想来源于Google的BigTable论文,是Apache的Hadoop项目的子项目。它适合于存储大表数据,并可以达到实时级别。HB…

部署YUM仓库及NFS共享服务

YUN仓库服务 YUM概述 基于RPM包构建的软件更新机制 可以自动解决依赖关系 所有软件包由YUM集中的软件仓库提供 yum软件仓库的常用类型 本地源仓库&#xff1a;baserulfile:// 在线源仓库&#xff1a;baserulhttp:// 或 https:// ftp源仓库&#xff1a;baserulftp:// RPM…

【Java那些事】关于前端收到后端返回的时间格式“2024-04-28T14:48:41“非想要的格式

问题&#xff1a; 后端操作后返回时间格式是"2024-04-28T14:48:41" 而我们想要的是&#xff1a;"2024-04-28 14:48:41", 两个解决方法&#xff1a; 方法一&#xff1a;使用 JsonFormat注解 Data AllArgsConstructor NoArgsConstructor public class Use…

代码随想录算法训练营第五十一天| LeetCode309.最佳买卖股票时机含冷冻期、714.买卖股票的最佳时机含手续费

一、LeetCode309.最佳买卖股票时机含冷冻期 题目链接/文章讲解/视频讲解&#xff1a;https://programmercarl.com/0309.%E6%9C%80%E4%BD%B3%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E6%97%B6%E6%9C%BA%E5%90%AB%E5%86%B7%E5%86%BB%E6%9C%9F.html 状态&#xff1a;已解决 1.思路 …

实验一: 设备密码配置与远程管理

1.实验环境 用路由器和交换机搭建实验环境 2.需求描述 实现管理员主机对交换机和路由器的远程管理 设备上配置的密码都要被加密 3.推荐步骤 对路由器配置的步骤如下&#xff1a; 实现路由器和PC的连通性配置VTY密码和特权模式密码在PC上Telnet 到路由器。 对交换机配置的…

03-JAVA设计模式-观察者模式

观察者模式 什么是观察者模式 Java中的观察者模式是一种常见的设计模式&#xff0c;它允许对象&#xff08;观察者&#xff09;订阅另一个对象&#xff08;被观察者&#xff09;的状态变化&#xff0c;并在状态变化时自动得到通知。 核心&#xff1a; 观察者模式主要用于1&a…

HTML学习笔记(二)

1.HTML图像 图像标签&#xff08;<img>)和源属性&#xff08;src&#xff09; HTML中&#xff0c;图像由<img>标签来定义&#xff0c;<img>是空标签&#xff0c;只包含属性&#xff0c;没有闭合标签。在页面上显示图像需要使用源属性&#xff08;src),src是指…

Docker基本操作 Linux里边操作

docker镜像操作命令: docker images:查看所有镜像; docker rmi:删除镜像 后边可以跟镜像的名字或者id指定要删除的镜像&#xff1b; docker pull:拉取镜像&#xff1b; docker push:推送镜像到服务&#xff1b; docker save :打包镜像 后边有用法; docker load:加载镜像&…

前端JS必用工具【js-tool-big-box】,字符串反转,驼峰转换以及版本号对比

这一小节&#xff0c;我们针对前端工具包&#xff08;npm&#xff09;js-tool-big-box的使用做一些讲解&#xff0c;主要是针对字符串反转&#xff0c;aa-bb-cc转驼峰&#xff0c;以及版本号对比的内容 目录 1 安装和引入 2 字符串反转 3 带有横岗的转驼峰 3.1 转小驼峰 3…

docker-compose编排集成工具,

一、引言 我们知道使用一个 Dockerfile 模板文件可以定义一个单独的应用容器&#xff0c;如果需要定义多个容器就需要服务编排。服务编排有很多种技术方案&#xff0c;今天给大家介绍 Docker 官方产品 Docker-Compose Dockerfile 可以定义一个单独的应用容器&#xff1…
最新文章