Mybatis插件原理和PageHelper结合实战分页插件

2023-11-02

今天和大家分享下mybatis的一个分页插件PageHelper,在讲解PageHelper之前我们需要先了解下mybatis的插件原理。PageHelper

的官方网站:https://github.com/pagehelper/Mybatis-PageHelper



一、Plugin接口


mybatis定义了一个插件接口org.apache.ibatis.plugin.Interceptor,任何自定义插件都需要实现这个接口PageHelper就实现了改接口


package org.apache.ibatis.plugin;

import java.util.Properties;

/**
 * @author Clinton Begin
 */
public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  Object plugin(Object target);

  void setProperties(Properties properties);

}



1:intercept 拦截器,它将直接覆盖掉你真实拦截对象的方法。

2:plugin方法它是一个生成动态代理对象的方法

3:setProperties它是允许你在使用插件的时候设置参数值。


看下com.github.pagehelper.PageHelper分页的实现了那些



    /**
     * Mybatis拦截器方法
     *
     * @param invocation 拦截器入参
     * @return 返回执行结果
     * @throws Throwable 抛出异常
     */
    public Object intercept(Invocation invocation) throws Throwable {
        if (autoRuntimeDialect) {
            SqlUtil sqlUtil = getSqlUtil(invocation);
            return sqlUtil.processPage(invocation);
        } else {
            if (autoDialect) {
                initSqlUtil(invocation);
            }
            return sqlUtil.processPage(invocation);
        }
    }


这个方法获取了是分页核心代码,重新构建了BoundSql对象下面会详细分析

    /**
      * 只拦截Executor
      *
      * @param target
      * @return
      */
     public Object plugin(Object target) {
         if (target instanceof Executor) {
             return Plugin.wrap(target, this);
         } else {
             return target;
         }
     }


这个方法是正对Executor进行拦截


    /**
     * 设置属性值
     *
     * @param p 属性值
     */
    public void setProperties(Properties p) {
        checkVersion();
        //多数据源时,获取jdbcurl后是否关闭数据源
        String closeConn = p.getProperty("closeConn");
        //解决#97
        if(StringUtil.isNotEmpty(closeConn)){
            this.closeConn = Boolean.parseBoolean(closeConn);
        }
        //初始化SqlUtil的PARAMS
        SqlUtil.setParams(p.getProperty("params"));
        //数据库方言
        String dialect = p.getProperty("dialect");
        String runtimeDialect = p.getProperty("autoRuntimeDialect");
        if (StringUtil.isNotEmpty(runtimeDialect) && runtimeDialect.equalsIgnoreCase("TRUE")) {
            this.autoRuntimeDialect = true;
            this.autoDialect = false;
            this.properties = p;
        } else if (StringUtil.isEmpty(dialect)) {
            autoDialect = true;
            this.properties = p;
        } else {
            autoDialect = false;
            sqlUtil = new SqlUtil(dialect);
            sqlUtil.setProperties(p);
        }
    }



基本的属性设置



二、Plugin初始化


初始化和所有mybatis的初始化一样的在之前的文章里面已经分析了 《Mybatis源码分析之SqlSessionFactory(一)》


private void pluginElement(XNode parent) throws Exception {  
  if (parent != null) {  
    for (XNode child : parent.getChildren()) {  
      String interceptor = child.getStringAttribute("interceptor");  
      Properties properties = child.getChildrenAsProperties();  
      Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();  
      interceptorInstance.setProperties(properties);  
      configuration.addInterceptor(interceptorInstance);  
    }  
  }  
}


这里是讲多个实例化的插件对象放入configuration,addInterceptor最终存放到一个list里面的,以为这可以同时存放多个Plugin



三、Plugin拦截


插件可以拦截mybatis的4大对象ParameterHandler、ResultSetHandler、StatementHandler、Executor,源码如下图

在Configuration类里面可以找到

PageHelper使用了Executor进行拦截,上面的的源码里面已经可以看到了。

我看下上图newExecutor方法

executor = (Executor) interceptorChain.pluginAll(executor);


这个是生产一个代理对象,生产了代理对象就运行带invoke方法




四、Plugin运行


mybatis自己带了Plugin方法,源码如下

public class Plugin implements InvocationHandler {
  private Object target;
  private Interceptor interceptor;
  private Map<Class<?>, Set<Method>> signatureMap;
  private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
    this.target = target;
    this.interceptor = interceptor;
    this.signatureMap = signatureMap;
  }
  public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }
  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    // issue #251
    if (interceptsAnnotation == null) {
      throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());      
    }
    Signature[] sigs = interceptsAnnotation.value();
    Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
    for (Signature sig : sigs) {
      Set<Method> methods = signatureMap.get(sig.type());
      if (methods == null) {
        methods = new HashSet<Method>();
        signatureMap.put(sig.type(), methods);
      }
      try {
        Method method = sig.type().getMethod(sig.method(), sig.args());
        methods.add(method);
      } catch (NoSuchMethodException e) {
        throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
      }
    }
    return signatureMap;
  }
  private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    Set<Class<?>> interfaces = new HashSet<Class<?>>();
    while (type != null) {
      for (Class<?> c : type.getInterfaces()) {
        if (signatureMap.containsKey(c)) {
          interfaces.add(c);
        }
      }
      type = type.getSuperclass();
    }
    return interfaces.toArray(new Class<?>[interfaces.size()]);
  }
}

wrap方法是为了生成一个动态代理类。

invoke方法是代理绑定的方法,该方法首先判定签名类和方法是否存在,如果不存在则直接反射调度被拦截对象的方法,如果存在则调度插件的interceptor方法,这时候会初始化一个Invocation对象



我们在具体看下PageHelper,当执行到invoke后程序将跳转到PageHelper.intercept


 

 public Object intercept(Invocation invocation) throws Throwable {
        if (autoRuntimeDialect) {
            SqlUtil sqlUtil = getSqlUtil(invocation);
            return sqlUtil.processPage(invocation);
        } else {
            if (autoDialect) {
                initSqlUtil(invocation);
            }
            return sqlUtil.processPage(invocation);
        }
    }


我们在来看sqlUtil.processPage方法


 /**
     * Mybatis拦截器方法,这一步嵌套为了在出现异常时也可以清空Threadlocal
     *
     * @param invocation 拦截器入参
     * @return 返回执行结果
     * @throws Throwable 抛出异常
     */
    public Object processPage(Invocation invocation) throws Throwable {
        try {
            Object result = _processPage(invocation);
            return result;
        } finally {
            clearLocalPage();
        }
    }


继续跟进

   /**
     * Mybatis拦截器方法
     *
     * @param invocation 拦截器入参
     * @return 返回执行结果
     * @throws Throwable 抛出异常
     */
    private Object _processPage(Invocation invocation) throws Throwable {
        final Object[] args = invocation.getArgs();
        Page page = null;
        //支持方法参数时,会先尝试获取Page
        if (supportMethodsArguments) {
            page = getPage(args);
        }
        //分页信息
        RowBounds rowBounds = (RowBounds) args[2];
        //支持方法参数时,如果page == null就说明没有分页条件,不需要分页查询
        if ((supportMethodsArguments && page == null)
                //当不支持分页参数时,判断LocalPage和RowBounds判断是否需要分页
                || (!supportMethodsArguments && SqlUtil.getLocalPage() == null && rowBounds == RowBounds.DEFAULT)) {
            return invocation.proceed();
        } else {
            //不支持分页参数时,page==null,这里需要获取
            if (!supportMethodsArguments && page == null) {
                page = getPage(args);
            }
            return doProcessPage(invocation, page, args);
        }
    }


这些都只是分装page方法,真正的核心是doProcessPage


  /**
     * Mybatis拦截器方法
     *
     * @param invocation 拦截器入参
     * @return 返回执行结果
     * @throws Throwable 抛出异常
     */
    private Page doProcessPage(Invocation invocation, Page page, Object[] args) throws Throwable {
        //保存RowBounds状态
        RowBounds rowBounds = (RowBounds) args[2];
        //获取原始的ms
        MappedStatement ms = (MappedStatement) args[0];
        //判断并处理为PageSqlSource
        if (!isPageSqlSource(ms)) {
            processMappedStatement(ms);
        }
        //设置当前的parser,后面每次使用前都会set,ThreadLocal的值不会产生不良影响
        ((PageSqlSource)ms.getSqlSource()).setParser(parser);
        try {
            //忽略RowBounds-否则会进行Mybatis自带的内存分页
            args[2] = RowBounds.DEFAULT;
            //如果只进行排序 或 pageSizeZero的判断
            if (isQueryOnly(page)) {
                return doQueryOnly(page, invocation);
            }
            //简单的通过total的值来判断是否进行count查询
            if (page.isCount()) {
                page.setCountSignal(Boolean.TRUE);
                //替换MS
                args[0] = msCountMap.get(ms.getId());
                //查询总数
                Object result = invocation.proceed();
                //还原ms
                args[0] = ms;
                //设置总数
                page.setTotal((Integer) ((List) result).get(0));
                if (page.getTotal() == 0) {
                    return page;
                }
            } else {
                page.setTotal(-1l);
            }
            //pageSize>0的时候执行分页查询,pageSize<=0的时候不执行相当于可能只返回了一个count
            if (page.getPageSize() > 0 &&
                    ((rowBounds == RowBounds.DEFAULT && page.getPageNum() > 0)
                            || rowBounds != RowBounds.DEFAULT)) {
                //将参数中的MappedStatement替换为新的qs
                page.setCountSignal(null);
                BoundSql boundSql = ms.getBoundSql(args[1]);
                args[1] = parser.setPageParameter(ms, args[1], boundSql, page);
                page.setCountSignal(Boolean.FALSE);
                //执行分页查询
                Object result = invocation.proceed();
                //得到处理结果
                page.addAll((List) result);
            }
        } finally {
            ((PageSqlSource)ms.getSqlSource()).removeParser();
        }
        //返回结果
        return page;
    }


上面的有两个 Object result = invocation.proceed()执行,第一个是执行统计总条数,第二个是执行执行分页的查询的数据

里面用到了代理。最终第一回返回一个总条数,第二个把分页的数据得到。





五:PageHelper使用


以上讲解了Mybatis的插件原理和PageHelper相关的内部实现,下面具体讲讲PageHelper使用

1:先增加maven依赖:


<dependency>  
    <groupId>com.github.pagehelper</groupId>  
    <artifactId>pagehelper</artifactId>  
    <version>4.1.6</version>  
</dependency



2:配置configuration.xml文件加入如下配置(plugins应该在environments的上面 )

<plugins>
         <!-- PageHelper4.1.6 --> 
        <plugin interceptor="com.github.pagehelper.PageHelper">
            <property name="dialect" value="mysql"/>
            <property name="offsetAsPageNum" value="false"/>
            <property name="rowBoundsWithCount" value="false"/>
            <property name="pageSizeZero" value="true"/>
            <property name="reasonable" value="false"/>
            <property name="supportMethodsArguments" value="false"/>
            <property name="returnPageInfo" value="none"/>
        </plugin>
    </plugins>

相关字段说明可以查看SqlUtilConfig源码里面都用说明


注意配置的时候顺序不能乱了否则报错

Caused by: org.apache.ibatis.builder.BuilderException: Error creating document instance.  Cause: org.xml.sax.SAXParseException; lineNumber: 57; columnNumber: 17; 元素类型为 "configuration" 的内容必须匹配 "(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,plugins?,environments?,databaseIdProvider?,mappers?)"。
at org.apache.ibatis.parsing.XPathParser.createDocument(XPathParser.java:259)
at org.apache.ibatis.parsing.XPathParser.<init>(XPathParser.java:120)
at org.apache.ibatis.builder.xml.XMLConfigBuilder.<init>(XMLConfigBuilder.java:66)
at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:49)
... 2 more

意思是配置里面的节点顺序是properties->settings->typeAliases->typeHandlers->objectFactory->objectWrapperFactory->plugins->environments->databaseIdProvider->mappers   plugins应该在environments之前objectWrapperFactory之后  这个顺序不能乱了



3:具体使用

  1:分页


  SqlSession sqlSession = sessionFactory.openSession();
   UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
   PageHelper.startPage(1,10,true);  //第一页 每页显示10条
   Page<User> page=userMapper.findUserAll();

  2:不分页

 PageHelper.startPage(1,-1,true);

  3:查询总条数

PageInfo<User>  info=new PageInfo<>(userMapper.findUserAll());




本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Mybatis插件原理和PageHelper结合实战分页插件 的相关文章

  • Rustful接口开发

    Rustful接口开发 什么是api api简称接口 就是一些预先定义的函数 目的是能够让应用程序或者开发人员具有能够访问指定网络资源的能力 而又无需关心访问的源码 或理解内部工作机制的细节 api的产生 web Android iOS 由
  • matlab实现各向异性扩散

    看书一直不理解各向异性扩散究竟是什么 直到看了某大佬的解释 主要是用来平滑图像的 克服了高斯模糊的缺陷 各向异性扩散在平滑图像时是保留图像边缘的 和双边滤波很像 通常我们有将图像看作矩阵的 看作图的 看作随机过程的 记得过去还有看作力场的
  • flutter 状态栏文字颜色

    SystemChrome setSystemUIOverlayStyle SystemUiOverlayStyle dark 还有一种方法是在appbar里面的一个参数 brightness 但是 两者都会对当前的widget重构 比如页面
  • 毕业1年从事软件测试拿下11.5k,没有给98后丢脸吧...

    自我介绍一下 本人是20年毕业的专科生 专业软件技术 21年8月拿到月薪11 5k的offer 开启我的软件测试之路 现在把本人的经历写出来 希望能给像我一样想要从事软件测试的朋友们一些建议 学还是不学 这是个问题 19年底我找到软件开发的
  • 大语言模型之九- BERT

    Natural Language Processing NLP 包括自然语言理解和自然语言生成 自然语言理解的应用包括语义分析 机器客服 语音识别 机器翻译等 transformer这一深度网络架构在NLP领域占有举足轻重的地位 BERT是
  • linux7重置密码操作,linux重置管理员密码的操作方法

    linux重置管理员密码的操作方法 发布时间 2020 04 02 11 08 01 来源 亿速云 阅读 34 作者 小新 今天小编给大家分享的是linux重置管理员密码的操作方法 很多人都不太了解 今天小编为了让大家更加了解linux重置
  • mysql myisam 数据丢失_用Myisamchk进行崩溃恢复MySQL

    由MySQL用来存储数据的文件格式以已经被广泛地测试过 但是总是有外部情况可以导致数据库表被破坏 mysqld进程在一个写入当中被杀死 计算机的意外关闭 例如 如果计算机掉电 一个硬件错误 这章描述如何检查和处理在MySQL数据库中的数据损
  • 这 7个 AI 写作助手,太实用了

    想象一下 你正在办公桌前为你的广告输入标题 但你突然思维阻塞并卡住了 可惜这时还没有神奇的软件可以帮助你想出点子 或许是有的 2023 年 AI 写作工具似乎不可避免地会很快融入我们的工作流程中 现代知识工作者已经看到了 ChatGPT 的
  • 流形学习(Manifold Learning)以及推导

    流形学习 Manifold Learning 前言 流行学习简介 主要的代表方法 1 Isomap 等距映射 Isomap算法步骤 2 LLE Locally Linear Embedding 局部线性嵌入 LLE基本思想 LLE算法步骤
  • ggplot2读书笔记5:工具箱——误差线、加权数、展示数据分布

    今天我们学习第三章的最后几节 其中的 绘制地图 部分 因为我木有顺利安装maps package 而且在我们的工作中也不常用 暂时跳过 下面继续 6 添加误差线和误差范围 数据中的不确定信息的展示也很重要 ggplot2中 四类几何对象可以
  • 【嵌入式百科】001——字长、比特、字节、字、双字

    一 字长 计算机的每个字所包含的位数称为字长 计算的字长是指它一次可处理的二进制数字的数目 一般地 大型计算机的字长为32 64位 小型计算机为12 32位 而微型计算机为4 16位 二 比特 bit 数据传输大多是以 位 bit 又名 比
  • 异常、业务状态码、错误码的使用分析

    url http www iteye com topic 1112683 url 好吧 看了各位的发言 我突然觉的自己蛋疼了 我的公司也蛋疼了 不过可别说我经历的项目初级 从日pv超百万的论坛和价值几亿的银行项目我都经历过 现在我经历的最大
  • QT 面试题 个人标注重点

    一 讲述Qt信号槽机制与优势与不足 优点 类型安全 需要关联的信号槽的签名必须是等同的 即信号的参数类型和参数个数同接受该信号的槽的参数类型和参数个数相同 若信号和槽签名不一致 编译器会报错 松散耦合 信号和槽机制减弱了Qt对象的耦合度 激
  • VMware虚拟机添加新磁盘

    一 VMware虚拟机添加磁盘 1 关闭你要添加硬盘的虚拟机 点击 编辑虚拟机设置 2 在打开的界面中点击 添加 按钮 3 在新打开的界面中点击 硬盘 下一步 4 在打开的界面中 选择硬盘类型 保持默认即可 点击 下一步 5 在打开的界面中
  • 贴片钽电容封封装及规格和参数资料

    贴片钽电容简述 贴片钽电容 以下简称钽电容 作为电解电容器中的一类 广泛应用于各类电子产品 特别是一些高密度组装 内部空间体积小产品 如手机 便携式打印机 钽电容是一种用金属钽 Ta 作为阳极材料而制成的 按阳极结构的不同可分为箔式和钽烧粉
  • 第一章 Vue项目的创建

    1 第一步 Node js的下载 方法 去node js官网进行下载 描述 node js自带npm的包 管理依赖 2 第二步VUE脚手架下载安装 方法 1 打开cmd 2 输入 npm install g vue cli 3 第三步 检查
  • 14.ES 之 nested 详解(2019-05-22)

    1 问题引入 由于在 ES 里新建 删除 更新单个文档都是原子性的 那么将相关实体保存在同一文档里面是有意义的 PUT blog doc 1 title Nest eggs body Making your money work tags
  • html 纯数字、英文不换行的两种解决办法

    1 在需要纯数字或英文换行的标签加入样式 word break break all 强制换行 2 如果需要将html代码通过html2canvas转为图片 word break break all 结果失效 我的解决办法是将纯数字使用sub
  • Dart基础语法1

    Dart基础 学习一门新的语言 我们可以以自己现有的熟悉的语言来类比 比如我们非常熟悉Java 那么剩下的就是需要掌握与Java不同的Dart语法 剩下的就需要靠自己多写多看来慢慢熟悉 国际惯例 使用Dart完成一个 Hello World
  • web前端面试之基础面试题(一)(含答案)

    前端面试题 一 今天我们整理一下前端面试题 15个 此面试题答案自己书写总结 有问题或疑问请指出问题 谢谢 1 HTML中行内元素与块元素的区别 并举例 行内元素怎么转化为块级元素 块级元素 block element 在浏览器中占据整行

随机推荐

  • 一文看懂yolov7;yolov7详解

    免责声明 1 此方法仅提供参考 2 搬了其他博主的操作方法 以贴上路径 3 场景一 yolo v7 场景二 yolo系列未完待续 Yolo系列强推 gt Yolo v1 v5 Yolox 场景一 yolo v7 强推先看 gt yolov7
  • 7-52 两个有序链表序列的交集

    include
  • 用遗传算法进行特征选择

    文章目录 一 问题举例 二 算法描述 1 基于类内类间距离的可分性判据 2 遗传算法 Genetic Algorithm 1 初始化种群 2 计算当前种群 M t 中每条染色体的适应度值 f m 3 基于适应度值的选择 4 交叉 5 变异
  • STM32直流电机测速

    任务要求 以STM32单片机 DSPTMS320F28335芯片或ARM系列芯片为核心 测量电机旋转速度 并利用开发板实现如下功能 在LCD屏幕上显示捕捉的电机转速 转速高于阈值报警 LED闪烁指示处于工作状态 扩展功能 0 96寸OLED
  • ent orm笔记1---快速尝鲜

    前几天看到消息Facebook孵化的ORM ent转为正式项目 出去好奇 简单体验了一下 使用上自己感觉比GORM好用 于是打算把官方的文档进行整理 也算是学习一下如何使用 安装 ent orm 需要使用entc命令进行自动代码生成 所以需
  • ChatGPT+小红书爆文,牛!

    随着AI技术的不断发展 它已经逐渐渗透到了我们的生活之中 包括内容营销领域 我们通过AI算法生成文本 优化搜索引擎排名 提高用户体验等 现在AI已逐渐在改变时代的进步 AI也将成为下一个十年的一个变革 我们每个创业者 内容创作者以及普通人都
  • Visual Studio 卸载 Visual Assist番茄助手

    在VS的Visual Studio 2010 Extension Manager 中可卸载 详细可参考文章 https blog csdn net fangxinggood article details 6052950
  • Selenium常用操作之单选复选框、下拉列表、键盘、截屏、断言、(显式隐式)等待

    目录 1 窗口最大化 2 单选框操作 3 复选框操作 4 下拉列表 5 selenium 三种等待 6 键盘操作 7 截屏 8 断言 9 Selenium操作JS弹窗控件 10 鼠标悬停与释放 1 窗口最大化 driver maximize
  • 算法03-任务混部

    公司创新实验室正在研究如何最小化资源成本 最大化资源利用率 请你设计算法帮他们 解决一个任务混部问题 有 taskNum 项任务 每个任务有开始时间 startTime 结束时间 endTime 并行度 parallelism 三个属性 并
  • 福建中烟RFID托盘运输环节的出入库管理

    1 项目设计 在木托盘上加一张带有RFID芯片的纸滑托盘 利用专用推拉器RFID智能叉车将纸滑托盘连同放在其上面的整托盘卷烟叉起 经过出库扫描仪扫描芯片信息后装车 便完成了基于RFID托盘运输环节的出入库作业 基于工业的RFID托盘运输管理
  • C/C++编程:名称

    两个概念 如果一个名称使用域解析符 或者成员访问运算符 gt 来显式表明它所属的作用域 我们就称该名称为受限名称 this gt count是受限名称 count不是 即使count实际上引用的也是一个类名称 如果一个名称 以某种方式 依赖
  • 如何基于 Kubernetes 实现优质开发者平台体验?

    内部开发者平台 或 IDP 是使开发团队能够更快 更轻松 更一致地交付应用程序的基础设施 Kubernetes 本身是一个功能强大的平台 但它引入了太多复杂性和功能 因此不能简单地将其作为 IDP 交给开发团队 若要期望他们能取得成功 非常
  • QString 乱谈(3)-Qt5与中文

    两个月前 简单写过QTextCodec中的setCodecForTr等终于消失了 Qt5 在Qt论坛上 不少用户都对去掉这两个函数表示特别的不了解 为什么会这样 我想多少能说明不少用户对C 中源码字符集和执行字符集的不太了解 从而造成对这种
  • SQL查询~ 存在一个表而不在另一个表中的数据

    A B两表 找出ID字段中 存在A表 但是不存在B表的数据 A表总共13w数据 去重后大约3W条数据 B表有2W条数据 且B表的ID字段有索引 方法一 使用 not in 容易理解 效率低 执行时间为 1 395秒 1 select dis
  • 微信小程序中调用手机拨号功能

    在微信小程序中 如何实现点击电话按钮后跳转至手机的拨号界面 并且传入指定的号码 要实现在微信小程序中点击电话按钮后跳转至手机的拨号界面 并传入指定的号码 你可以使用 wx makePhoneCall 方法 微信小程序中调用手机拨号功能 首先
  • Netty编程面试题

    1 Netty 是什么 Netty是 一个异步事件驱动的网络应用程序框架 用于快速开发可维护的高性能协议服务器和客户端 Netty是基于nio的 它封装了jdk的nio 让我们使用起来更加方法灵活 2 Netty 的特点是什么 高并发 Ne
  • java数组定义错误_JAVA定义数组 int a[]=new int[100000] 错误

    我用JAVA定义了一个1W的数组可以使用 但是定义一个10W的数组提示Exceptioninthread main java lang ArrayIndexOutOfBoundsException 2147479015atJavaappli
  • php微信企业付款到银行卡获取RSA加密公钥

    微信企业付款到银行卡需要对收款方银行卡号 收款方用户名进行加密 这个过程需要获取到加密公钥 对于一些第一次接刚触到的小伙伴来说 可能比较陌生 在此记录一下自己生成 RSA公钥的过程 1 调用官方提供的接口 接口默认输出PKCS 1格式的公钥
  • 可视化库D3.js(1)-入门篇

    从今天开始可视化库 D 3 j s color red D3 js D3 js的第一章 入门篇咯 什么是D3 js D3指的是Data Dri
  • Mybatis插件原理和PageHelper结合实战分页插件

    今天和大家分享下mybatis的一个分页插件PageHelper 在讲解PageHelper之前我们需要先了解下mybatis的插件原理 PageHelper 的官方网站 https github com pagehelper Mybati