idea插件开发案例:将批量插入方法转换成分批批量插入

代码: idea-plugin-demo


1.背景

excel导入时都会使用批量插入或者批量更新到数据库,这在mysql下没有问题。
但因为公司国产化需求,换成达梦数据库就不行了,报sql超长。
一开始想写mybatis拦截器处理,又怕出现bug,这个问题是正式上线以后暴露的,测试没有用大数据测过。
后来想到一个将下划线和驼峰式转换的idea插件:CamelCasePlugin插件,如果可以实现类似的代码转换-将批量插入方法改写成分批批量插入,就比较保险了。

2.前期准备

  • 需要写一个通用的分批批量插入的方法

精简版

  • BatchUtil:通用分批操作工具类

    import org.springframework.util.CollectionUtils;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.function.Function;
    
    /**
     * 通用分批操作 处理批量sql超长
     */
    public class BatchUtil {
    
        private final static int BATCH_NUM = 10; // 每一批处理多少个
    
        public static void main(String[] args) {
    
            List<String> list = new ArrayList<>();
            for (int i = 0; i < 25; i++) {
                list.add("2");
            }
    
            System.out.println("----一个参数---");
            batchHandle(list, t1 -> t1.size());
    
            System.out.println("----多个参数---");
            // 只有list是可变的,其他参数一般都不会改变,所以归根结底,本质上也是一个参数
            batchHandle(list, t1 -> t1.size() + 1 + 2 + "1111".length());
    
        }
    
        /**
         * 分批处理 可能超过sql长度限制
         *
         * @param list 要求要分批的集合必须放第一个
         * @param function 将方法当成参数传递
         * @param <T>
         */
        public static <T> void batchHandle(List<T> list, Function<List<T>, Integer> function) {
            if (CollectionUtils.isEmpty(list) || function == null) {
                return;
            }
            int sum = list.size();
            // 每批多少条
            int count = BATCH_NUM;
            if (sum > count) {
                int limit = getSize(sum, count);
                for (int i = 0; i < limit; i++) {
                    List subList = list.subList(i * count, i == (limit - 1) ? sum : (i + 1) * count);
                    Integer apply = function.apply(subList);
                    System.out.println("apply: " + apply);
                }
            } else {
                Integer apply = function.apply(list);
                System.out.println("apply: " + apply);
            }
        }
    
        /**
         * 方法描述:根据数据总数获取需要循环次数
         *
         * @param count 数据总数
         * @return loop 每次数量
         */
        public static int getSize(long count, int loop) {
            if (loop < 1) {
                return 0;
            }
            int size = 1;
            if (count > loop) {
                if (count % loop == 0) {
                    size = (int) count / loop;
                } else {
                    size = (int) count / loop + 1;
                }
            }
            return size;
        }
    
    }
    
    
  • Mybatis测试

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    
    import static com.ycxh.module.mybatis.batch.BatchUtil.batchHandle;
    
    @RestController
    @RequestMapping("/mybatis")
    public class MybatisController {
    
        @Autowired
        private TestTable01Dao dao;
    
        @GetMapping("batchTest")
        public void batchTest() throws Exception {
            List<TestTable01> list = new ArrayList<>();
            for (int i = 0; i < 100; i++) {
                TestTable01 testTable01 = new TestTable01();
                testTable01.setId(i + 300);
                testTable01.setName(i + 300 + "name");
                testTable01.setAge(i + 300);
                testTable01.setSex(i % 2 + 1);
                testTable01.setCreatedTime(new Date());
    
                list.add(testTable01);
            }
    
            /**
             * select * from test_table_01 where id >= 300
             */
            dao.batchDelete(list);
    
            // dao.batchInsert(list);
            // 分批处理 单个参数
            batchHandle(list, t1 -> dao.batchInsert(t1));
    
            // dao.batchUpdateSex(list, 2);
            // 分批处理 多个参数
            // BatchUtil_bak.batchHandle((t1, t2) -> dao.batchUpdateSex(t1, (Integer)t2[0]), list, 2);
            // 简化 一开始想复杂了 只有t1是变化的,所以用形参 其他都是不用变化的 所以它本质其实也是一个参数
            batchHandle(list, t1 -> dao.batchUpdateSex(t1, 2));
    
            // dao.batchUpdate(list);
    
            // dao.batchDeleteByNameAndSex(list);
    
            System.out.println();
    
        }
    
    
    }
    
    

想太多,复杂版,仅记录

import org.apache.commons.collections.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

@FunctionalInterface
public interface MoreParamFunction<T1, T2, R> {

    // 可变参 第2个参数是一个数组
    R apply(T1 t1, T2... t2);

}

class Test {

    public static void main(String[] args) {
        
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 25; i++) {
            list.add("2");
        }

        System.out.println("----一个参数---");
        batchHandle(list, t1 -> t1.size());
        
        System.out.println("----多个参数---");
        MoreParamFunction<List<String>, Object, Integer> function = (t1, t2) -> {
            // 可变参 第2个形参是一个数组
            return t1.size() + ((Integer) t2[0] + (Integer) t2[1]) + ((String) t2[2]).length();
        };
        batchHandle(function, list, 1, 2, "1111");

    }

    /**
     * 分批处理 可能超过sql长度限制
     * 一个参数 简洁一点,不需要写强转这些
     * @param list
     * @param function 将方法当成参数传递
     * @param <T>
     */
    public static <T> void batchHandle(List<T> list, Function<List<T>, Integer> function) {
        batchHandle((t1, t2) -> function.apply(t1), list);
    }
    
    /**
     * 分批处理 可能超过sql长度限制
     * 可变参
     */
    public static <T> void batchHandle(MoreParamFunction<List<T>, Object, Integer> function, List<T> list, Object... params) {
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        int sum = list.size();
        // 每批多少条
        int count = 10;
        if (sum > count) {
            int limit = getSize(sum, count);
            for (int i = 0; i < limit; i++) {
                List subList = list.subList(i * count, i == (limit - 1) ? sum : (i + 1) * count);
                Integer apply = function.apply(subList, params);
                System.out.println("apply: " + apply);
            }
        } else {
            Integer apply = function.apply(list, params);
            System.out.println("apply: " + apply);
        }
    }

    /**
     * 方法描述:根据数据总数获取需要循环次数
     *
     * @param count 数据总数
     * @return loop 每次数量
     */
    public static int getSize(long count, int loop) {
        if (loop < 1) {
            return 0;
        }
        int size = 1;
        if (count > loop) {
            if (count % loop == 0) {
                size = (int) count / loop;
            } else {
                size = (int) count / loop + 1;
            }
        }
        return size;
    }

}

3.目标:插件要实现的功能

// 将批量插入方法改写成分批批量插入
dao.batchUpdateSex(list, 2);
转换为
batchHandle(list, t1 -> dao.batchUpdateSex(t1, 2));

4.搭建一个idea插件项目

插件的创建、配置、运行、打包流程,以及 action

直接从网上找一篇博客照着操作即可

4.1.主要的插件类型

  • UI主题插件
  • 自定义语言支持插件
  • 框架集成插件
  • 工具集成插件

4.2.开发插件的三种方式

  • Using Gradle

  • Using GitHub Template

  • Using DevKit

    • 前2种是idea官方推荐的方式,但是都要使用Gradle
    • 如果不需要引入额外jar包,我觉得使用最后这种简单的方式即可

4.3.使用DevKit开发插件

4.3.1.设置开发环境
1.确认启用 Plugin DevKit插件

该插件是IDEA自带的插件

导航到 Settings | Plugins

image-20240314221908242

2.配置IntelliJ Platform Plugin SDK

IntelliJ Platform Plugin SDK 就是开发 IntelliJ 平台插件的 SDK, 是基于 JDK 之上运行的,类似于开发 Android 应用需要 Android SDK。

导航到 File | Project Structure,选择对话框左侧栏 Platform Settings 下的 SDKs

点击 + 按钮,如果还没有添加 JDK,先添加jdk,指定 JDK 的路径

再创建 IntelliJ Platform Plugin SDK,指定 home path 为 IDEA 的安装路径,然后还需要指定对应的jdk

image-20240314222638072

  • 选择idea安装目录

image-20240314222749489

  • 选择jdk

image-20240314222842304

  • 添加成功

image-20240314223013937

配置sdk源码路径,此步可以跳过,方便debug
  • 查看 build 号:打开 IDEA,Help | About,查看版本号及 build 号
  • IDEA Community 源码(https://github.com/JetBrains/intellij-community):切换到与build 号相同的分支,点击 Clone or download 按钮,选择 Download ZIP

image-20240314223705026

image-20240314223943561

  • 下载

网页:https://github.com/JetBrains/intellij-community/tree/183.4886

下载链接:https://codeload.github.com/JetBrains/intellij-community/zip/refs/heads/183.4886

  • github下载到最后,速度变为0,换镜像网站下载
    • 如果https://hub.nuaa.cf不能使用,就找找新的镜像站

网页:https://hub.nuaa.cf/JetBrains/intellij-community/tree/183.4886

下载链接:https://archive.nuaa.cf/JetBrains/intellij-community/archive/refs/heads/183.4886.zip

  • 选择 SDKs-> 选中之前在第 3 步添加的 sdk 点击 SourcePath 后,点击右侧+添加一个 sourcePath,选择上面下载额源码后点击 OK、点击 Apply
  • 要不要解压啊???不解压似乎也可以

image-20240314225914143

Sandbox 沙箱

IntelliJ IDEA 插件以 Debug/Run 模式运行时是在 SandBox 中进行的,不会影响当前的 IntelliJ IDEA;但是同一台机器同时开发多个插件时默认使用的同一个 sandbox,即在创建 IntelliJ Platform SDK 时默认指定的 Sandbox Home

如果需要每个插件的开发环境是相互独立的,可以创建多个 IntelliJ Platform SDK,为 Sandbox Home 指定不同的目录 。

设置沙箱目录, 插件项目的setting信息会保存在这个目录, 可以指定任意目录

image-20240314230657694

4.3.2.创建一个模块工程

选择 File | New | Project 或者 Modele,左侧栏中选择 IntelliJ Platform Plugin 类型

image-20240314231805530

image-20240314232107094

点击 Next,设置名称及位置,点击 Finish 完成创建。可以到 File | Project Structure 来自定义设置。

image-20240314232151456

4.3.3.插件工程结构

image-20240314232435316

  • src 实现插件功能的 classes
  • resources/META-INF/plugin.xml 插件的配置文件,指定插件名称、描述、版本号、支持的 IntelliJ IDEA 版本、插件的 components 和 actions 以及软件商等信息。
4.3.4.plugin.xml
  • 下面示例描述了可在 plugin.xml 文件配置的主要元素:
<idea-plugin>
  <!-- 插件名称,别人在官方插件库搜索你的插件时使用的名称 -->
  <name>MyPlugin</name>
  <!-- 插件唯一id,不能和其他插件项目重复,所以推荐使用com.xxx.xxx的格式
       插件不同版本之间不能更改,若没有指定,则与插件名称相同 -->
  <id>com.example.plugin.myplugin</id>
  <!-- 插件的描述 -->
  <description>my plugin description</description>
  <!-- 插件版本变更信息,支持HTML标签;
       将展示在 settings | Plugins 对话框和插件仓库的Web页面 -->
  <change-notes>Initial release of the plugin.</change-notes>
  <!-- 插件版本 -->
  <version>1.0</version>
  <!-- 供应商主页和email-->
  <vendor url="http://www.jetbrains.com" email="support@jetbrains.com" />
  <!-- 插件所依赖的其他插件的id -->
  <depends>MyFirstPlugin</depends>
  <!-- 插件兼容IDEA的最大和最小 build 号,两个属性可以任选一个或者同时使用
       官网详细介绍:http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html-->
  <idea-version since-build="3000" until-build="3999"/>
  <!-- application components -->
  <application-components>
    <component>
      <!-- 组件接口 -->
      <interface-class>com.plugin.demo.Component1Interface</interface-class>
      <!-- 组件的实现类 -->
      <implementation-class>com.plugin.demo.impl.Component1Impl</implementation-class>
    </component>
  </application-components>
  <!-- project components -->
  <project-components>
    <component>
      <!-- 接口和实现类相同 -->
      <interface-class>com.plugin.demo.impl.Component2</interface-class>
    </component>
  </project-components>
  <!-- module components -->
  <module-components>
    <component>
      <interface-class>com.plugin.demo.impl.Component3</interface-class>
    </component>
  </module-components>
  <!-- Actions -->
  <actions>
    ...
  </actions>
  <!-- 插件定义的扩展点,以供其他插件扩展该插件 -->
  <extensionPoints>
    ...
  </extensionPoints>
  <!-- 声明该插件对IDEA core或其他插件的扩展 -->
  <extensions xmlns="com.intellij">
    ...
  </extensions>
</idea-plugin>
4.3.5.创建 Action

Action 是实现插件功能的类, 一个 Action 类需要继承 AnAction 并且实现 actionPerformed 方法。当用户点击菜单或者工具栏按钮, 按快捷键,或者通过 Help | Find Action 点击时, IntelliJ Platform 系统会回调对应 Action 的 actionPerformed 方法。

一个 Action 表示 IDEA 菜单里的一个 menu item 或工具栏上的一个按钮,通过继承 AnAction class 实现,当选择一个 menu item 或点击工具栏上的按钮时,就会调用 AnAction 类的 actionPerformed 方法。

实现自定义 Action 分两步:

  • 定义一个或多个 action
  • 注册 action,将 item 添加到菜单或工具栏上
4.3.5.1.定义 Action

定义一个 Java class,继承 AnAction 类,并重写 actionPerformed 方法, 如

public class ActionDemo extends AnAction {

    public void actionPerformed(AnActionEvent event) {
        Project project = event.getData(PlatformDataKeys.PROJECT);
        Messages.showInputDialog(
          project,
          "What is your name?",
          "Input your name",
          Messages.getQuestionIcon());
    }
}
4.3.5.3.注册 Action

在 plugin.xml 文件的 <actions> 元素内注册

<actions>
  <group id="MyPlugin.SampleMenu" text="Sample Menu" description="Sample menu">
    <add-to-group group-id="MainMenu" anchor="last"  />
       <action id="Myplugin.ActionDemo" class="Mypackage.ActionDemo" text="Text Boxes" description="A test menu item" />
  </group>
</actions>
  • 元素会定义一个 action,指定 action 的 id、实现类、显示文本、描述
  • 元素会定义一个 action group(多个 action),设置 action group 的 id、文本、描述
  • 元素指定其外部 action 或 action group 被添加到的位置

上面示例会定义一个被添加到 IDEA 主菜单的最后面的 “SampleMenu” 的菜单,点击该菜单将弹出一个 “Text Boxes” item,如图

img

  • 示例 多级菜单

    <!-- 插件动作声明,在这里可以声明自定义动作 -->
      <actions>
    
        <!--自己创建菜单组-->
        <!--https://blog.csdn.net/a__int__/article/details/127762837-->
        <!--https://zhuanlan.zhihu.com/p/408739679?utm_id=0-->
        <!--https://gitee.com/pillowtree/scream-tool/blob/master/resources/META-INF/plugin.xml-->
        <!--http://www.taodudu.cc/news/show-4799909.html?action=onClick-->
        <!--多个action可以组成一个group,形成层级菜单-->
        <!--popup="true"如果不设置,那么group的text不会在EditorPopupMenu上展示,无法形成层级菜单-->
        <group id="ScreamTool.Tool" text="Scream Tool" popup="true" description="ScreamTool">
          <!--anchor 位置 first last -->
          <add-to-group group-id="EditorPopupMenu" anchor="first"/>
          <action id="ScreamTool.BeanToJsonString" class="helloBoy"
                  text="BeanToJsonString"
                  description="BeanToJsonString" />
    
          <action id="ScreamTool.BeanToJsonString" class="helloBoy2" text="changeCode">
            <!-- 快捷键冲突会使用不了 -->
            <keyboard-shortcut keymap="$default" first-keystroke="ctrl F2"/>
          </action>
        </group>
    
        <action id="ScreamTool.BeanToJsonString" class="helloBoy2" text="sayHello2">
          <add-to-group group-id="ScreamTool.Tool" anchor="first"/>
          <!-- 快捷键冲突会使用不了 -->
          <!--<keyboard-shortcut keymap="$default" first-keystroke="ctrl F2"/>-->
        </action>
    
        <!-- Add your actions here -->
        <!-- 需要等一会才能从灰色变为可用 -->
        <action id="com.idea.hello" class="helloBoy" text="sayHello">
          <!--系统默认有一些组-->
          <!--ToolsMenu	Tools菜单栏-->
          <!--EditorPopupMenu代码编辑页面右击出现-->
          <add-to-group group-id="ToolsMenu" anchor="first"/>
          <!-- 快捷键冲突会使用不了 -->
          <keyboard-shortcut keymap="$default" first-keystroke="ctrl F2"/>
        </action>
    
      </actions>
    
4.3.5.3.快速创建 Action

https://idea.javaguide.cn/tips/plug-in-development-intro.html#_03-手动创建-action

Idea插件添加EditorPopupMenu显示JAVA对象的JSON字符串

IntelliJ Platform 提供了 New Action 向导,它会帮助我们创建 action class 并配置 plugin.xml 文件:

在目标 package 上右键,选择 New | Plugin DevKit | Action:

  • Action ID: action 唯一 id,推荐 format: PluginName.ID
  • Class Name: 要被创建的 action class 名称
  • Name: menu item 的文本
  • Description: action 描述,toolbar 上按钮的提示文本,可选
  • Add to Group:选择新 action 要被添加到的 action group(Groups, Actions)以及相对其他 actions 的位置(Anchor)
  • Keyboard Shortcuts:指定 action 的第一和第二快捷键

image-20240324202628908

image-20240324210041026

创建完成之后,我们的 plugin.xml<actions>节点下会自动生成我们刚刚创建的 Action 信息:

 <actions>
    <!-- Add your actions here -->
    <action id="BatchToSplitBatchTool" class="BatchToSplitBatchAction" text="BatchToSplitBatch" description="批量转分批批量">
      <add-to-group group-id="EditorPopupMenu" anchor="first"/>
      <keyboard-shortcut keymap="$default" first-keystroke="ctrl F2"/>
    </action>
  </actions>

并且 java 目录下会生成一个叫做 BatchToSplitBatchAction 的类。这个类继承了 AnAction ,并覆盖了 actionPerformed() 方法。这个 actionPerformed 方法就好比 JS 中的 onClick 方法,会在你点击的时候触发对应的动作。

另外,每个动作都会归属到一个 Group 中,这个 Group 可以简单看作 IDEA 中已经存在的菜单。

注意:该向导只能向主菜单中已存在的 action group 或工具栏上添加 action,若要创建新的 action group,需要另想办法。

4.3.6.运行调试插件

运行 / 调试插件可直接在 IntelliJ IDEA 进行,选择 Run | Edit Configurations…,若左侧栏没有 Plugin 类型的 Configuration, 点击右上角 + 按钮,选择 Plugin 类型,如图

image-20240324210646990

  • 如果报错,则设置一下输出目录

image-20240324210715844

image-20240324210851159

点击run|debug之后,Intel IDEA 会另启一个装有该插件的 IDEA窗口,该idea中已经安装了你开发的插件。

在该idea中可以直接使用插件,使用过程中,可以在项目里面断点跟进。

启动的idea和正常idea操作是一样的,可以打开一个测试demo,来测试你的插件是否正常运行。

上面设置创建的group-id为EditorPopupMenu,所以可以在编辑窗口右键菜单中发现该插件。

image-20240324211136486

  • 存在2个快捷键一样的插件,是因为之前调试的另一个插件还在沙盒里面,可以删掉

    image-20240324233908841

  • 假如创建的 Action 的所属 Group 是 ToolsMenu(Tools) 。这样的话,创建的 Action 所在的位置就在 Tools 这个菜单下。

img

  • 假如创建的 Action 所属的 Group 是MainMenu (IDEA 最上方的主菜单栏)下的 FileMenu(File) 的话。
<actions>
    <!-- Add your actions here -->
    <action id="test.hello" class="HelloAction" text="Hello" description="IDEA 插件入门">
      <add-to-group group-id="FileMenu" anchor="first"/>
    </action>
</actions>

我们创建的 Action 所在的位置就在 File 这个菜单下。

img

4.3.6.1.可能需要等一会,菜单项才能从灰色变为可用
4.3.6.2.调试 会打开一个新的idea窗口,可能需要重新激活
idea 2018.3永久简单激活。激活码
https://blog.csdn.net/weixin_44991324/article/details/136153394

1.打开hosts文件将 0.0.0.0 account.jetbrains.com 添加到文件末尾
C:\Windows\System32\drivers\etc\hosts

2.注册码:
4.3.4.打包安装插件
4.3.4.1.打包插件

选择 Build | Prepare Plugin Module ‘module name’ for Deployment 来打包插件:

image-20240324212811394

插件包位置:一般在工程根目录下
如果插件没有依赖任何 library,插件会被打包成一个 .jar ,否则会被打包成一个 .zip ,zip 中包含了所有的插
件依赖

image-20240324212921612

4.3.4.2.安装插件

导航到 File | Settings | Plugins 页面,点击 Install plugin from disk…

image-20240324213154166

5.导入一个idea插件项目

idea plugin 工程导入

idea中一个普通工程变为插件工程

  • 将.iml文件中的type属性值JAVA_MODULE其修改为PLUGIN_MODULE。

    • idea_plugin_demo/.idea/idea_plugin_demo.iml
  • 项目打开默认是java项目

<module type="JAVA_MODULE" version="4">
  • 修改为插件项目
<module type="PLUGIN_MODULE" version="4">

还要加上

<component name="DevKit.ModuleBuildProperties" url="file://$MODULE_DIR$/resources/META-INF/plugin.xml" />
  • 也可以直接创建一个idea插件项目,对比一下文件差异

  • 最终效果

<module type="PLUGIN_MODULE" version="4">
  <component name="DevKit.ModuleBuildProperties" url="file://$MODULE_DIR$/resources/META-INF/plugin.xml" />
  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8" inherit-compiler-output="true">
    <exclude-output />
    <content url="file://$MODULE_DIR$">
      <sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
    </content>
    <orderEntry type="jdk" jdkName="IntelliJ IDEA IU-183.4886.37" jdkType="IDEA JDK" />
    <orderEntry type="sourceFolder" forTests="false" />
  </component>
</module>
  • 26
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值