当前位置:

首页 > 编程开发 > 如何使用Java MyBatis开发自定义插件?

如何使用Java MyBatis开发自定义插件?

这篇文章主要介绍“Javamybatis怎么开发自定义插件”,在日常操作中,相信很多人在Javamybatis怎么开发自定义插件问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Javamybatis怎么开发自定义插件”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!介绍MyBatis允许你在映射语句执行过程中的某一点进行拦截调用。比如执行前、执行后或者对SQL结果集处理、sql入参处理等,这样就可以在不修改mybatis源码的情况下对sql执行的过程或结果进行修改,实现了解耦

本文目录

    这篇文章主要介绍“Java mybatis怎么开发自定义插件”,在日常操作中,相信很多人在Java mybatis怎么开发自定义插件问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java mybatis怎么开发自定义插件”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

    介绍

    MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。比如执行前、执行后或者对SQL结果集处理、sql入参处理等,这样就可以在不修改mybatis源码的情况下对sql执行的过程或结果进行修改,实现了解耦。mybatis 是在动态代理的基础上实现的。

    使用场景

    如果业务中需要设置一些通用数据库操作,比如创建时间、创建人等通用字段又或者是分页操作等,这类都可以使用插件开发方式,PageHelper就是基于Interceptor的一个mybatis插件。

    Interceptor拦截器

    public interface Interceptor {
    
      /**
       * 子类拦截器必须要实现的方法,
       * 在该方法对内自定义拦截逻辑
       * @param invocation
       * @return
       * @throws Throwable
       */
      Object intercept(Invocation invocation) throws Throwable;
    
      /**
       生成目标类的代理对象
       * 也可以根据需求不返回代理对象,这种情况下这个拦截器将不起作用
       * 无特殊情况使用默认的即可
       * @param target
       * @return
       */
      default Object plugin(Object target) {
        return Plugin.wrap(target, this);
      }
    
      /**
       * 设置变量
       * 在注册拦截器的时候设置变量,在这里可以获取到
       * @param properties
       */
      default void setProperties(Properties properties) {
        // NOP
      }
    
    }

    InterceptorChain拦截器链

    在org.apache.ibatis.plugin包下有个InterceptorChain类,该类有个interceptors属性,所有实现了Interceptor接口的拦截器都会被存储到interceptors中。

    源码如下:

    public class InterceptorChain {
    
      private final List interceptors = new ArrayList<>();
      /**
       *  让目标类在所有的拦截器中生成代理对象,并返回代理对象
       * @param target
       * @return
       */
      public Object pluginAll(Object target) {
        for (Interceptor interceptor : interceptors) {
          target = interceptor.plugin(target);
        }
        return target;
      }
    
      /**
       * 添加过滤器
       * @param interceptor
       */
      public void addInterceptor(Interceptor interceptor) {
        interceptors.add(interceptor);
      }
    
      public List getInterceptors() {
        return Collections.unmodifiableList(interceptors);
      }
    
    }

    拦截方法

    默认情况下,MyBatis 允许使用插件来拦截Executor 、ParameterHandler 、ResultSetHandler 、StatementHandler 接口下面的方法。如果系统中有配置自定义插件,默认情况下,系统会把上面四个类的默认子类都作为目标类来让所有的拦截器进行拦截, 以保证所有的拦截器都能对Executor 、ParameterHandler 、ResultSetHandler 、StatementHandler子类进行拦截。

    源码如下: 在org.apache.ibatis.session.Configuration类中

    public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
        // 使用拦截器进行拦截
        parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
        return parameterHandler;
      }
    
      public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
          ResultHandler resultHandler, BoundSql boundSql) {
        ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
        // 使用拦截器进行拦截
        resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
        return resultSetHandler;
      }
    
      public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        // 使用拦截器进行拦截
        statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
        return statementHandler;
      }
    
      public Executor newExecutor(Transaction transaction) {
        return newExecutor(transaction, defaultExecutorType);
      }
    
      public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Executor executor;
        if (ExecutorType.BATCH == executorType) {
          executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
          executor = new ReuseExecutor(this, transaction);
        } else {
          executor = new SimpleExecutor(this, transaction);
        }
        if (cacheEnabled) {
          executor = new CachingExecutor(executor);
        }
        // 使用拦截器进行拦截
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
      }

    注解

    Intercepts

    Intercepts的作用是拦截Signature注解数组中指定的类的方法。

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Intercepts {
      /**
       * Returns method signatures to intercept.
       * Signature注解列表
       * @return method signatures
       */
      Signature[] value();
    }

    Signature

    Signature注解作用是拦截指定类的方法。

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({})
    public @interface Signature {
      /**
       * Returns the java type.
       * 要拦截的类
       * @return the java type
       */
      Class type();
    
      /**
       * Returns the method name.
       * 要拦截的类的方法
       * @return the method name
       */
      String method();
    
      /**
       * Returns java types for method argument.
       * 要拦截的类的方法的参数列表
       * @return java types for method argument
       */
      Class[] args();
    }

    示例

    步骤

    • 1、实现org.apache.ibatis.plugin.Interceptor接口

    • 2、添加Intercepts和Signature注解

    • 3、根据需求实现Interceptor方法逻辑

    入门使用

    这里会写两个使用示例,一个是动态给属性赋值,一个是打印SQL。

    表结构:

    CREATE TABLE `users` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `gender` varchar(20) DEFAULT NULL,
      `userName` text NOT NULL,
      `create_date` datetime DEFAULT NULL COMMENT '创建日期',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

    实体类:

    public class UserInfo {
        private Long id;
    
        private String gender;
        private String userName;
        private Date createDate;
        // 省略get、set方法
    }

    动态给属性赋值

    在创建表时,有些是每个表都有的参数,比如创建时间、修改时间等,这类参数如果在每个类进行保存或修改的时候都进行设值的话就有点重复操作了,所以可以通过mybatis插件进行处理。

    1、Interceptor 实现类InsertInterceptor:

    @Intercepts({
            @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
    })
    public class InsertInterceptor implements Interceptor {
    
        private Properties properties;
    
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
    
            final Object[] args = invocation.getArgs();
    
            MappedStatement mappedStatement= (MappedStatement) args[0];
            Object parameter = args[1];
    
            Executor executor = (Executor) invocation.getTarget();
            final Class parameterClass = parameter.getClass();
    
            final String createDate = properties.getProperty("createDate");
    
            //获取createDate 属性描述器
            final PropertyDescriptor propertyDescriptor = new PropertyDescriptor(createDate , parameterClass);
            //获取createDate 写方法
            final Method writeMethod = propertyDescriptor.getWriteMethod();
            //调用createDate 写方法
            writeMethod.invoke(parameter , new Date());
    
            return executor.update(mappedStatement, parameter);
        }
    
        @Override
        public Object plugin(Object target) {
            return Plugin.wrap(target , this);
        }
    
        /**
         * 设置变量
         *
         * @param properties
         */
        @Override
        public void setProperties(Properties properties) {
            this.properties = properties;
        }
    }

    2、mybatis配置文件中注册InsertInterceptor

    
        
        
          
        
      

    3、测试

    public class UserTest {
    
      private final static SqlSessionFactory sqlSessionFactory;
      static {
        String resource = "mybatis-config.xml";
        Reader reader = null;
        try {
          reader = Resources.getResourceAsReader(resource);
        } catch (IOException e) {
          System.out.println(e.getMessage());
        }
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
      }
    
      @Test
      public void insert(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserInfo userInfo = new UserInfo();
        userInfo.setUserName("test1");
        userInfo.setGender("male");
        UserInfoMapper mapper = sqlSession.getMapper(UserInfoMapper.class);
        mapper.insertUser(userInfo);
        sqlSession.commit();
        sqlSession.close();
      }
    
    }

    查看数据库,可以看到在没有给createDate属性收到赋值的情况下,通过拦截器进行赋值,最后是保存到数据库中了。

    如何使用Java MyBatis开发自定义插件?

    打印SQL

    Interceptor 实现类PrintSqlPlugin:

    @Intercepts({
            @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
            @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
            @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
    })
    public class PrintSqlPlugin implements Interceptor {
    
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            //被代理对象
            Object target = invocation.getTarget();
            //代理方法
            Method method = invocation.getMethod();
            //方法参数
            Object[] args = invocation.getArgs();
    
            MappedStatement mappedStatement= (MappedStatement) args[0];
            Object parameter = args[1];
    
            final BoundSql mappedStatementBoundSql = mappedStatement.getBoundSql(parameter);
            System.err.println("BoundSql="+mappedStatementBoundSql.getSql());
    
            final Configuration configuration = mappedStatement.getConfiguration();
    
            final String showSql = showSql(configuration, mappedStatementBoundSql);
            System.err.println("sql="+showSql);
    
            //方法执行
            final Object returnValue = invocation.proceed();
            return returnValue;
        }
    
        @Override
        public Object plugin(Object target) {
            return Plugin.wrap(target, this);
        }
    
        @Override
        public void setProperties(Properties properties) {
    
        }
    
        /**
         * 获取参数
         * @param obj
         * @return
         */
        private static String getParameterValue(Object obj) {
            String value = null;
            if (obj instanceof String) {
                value = "'" + obj.toString() + "'";
                value = value.replaceAll("\\\\", "\\\\\\\\");
                value = value.replaceAll("\\$", "\\\\\\$");
            } else if (obj instanceof Date) {
                DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
                value = "'" + formatter.format(obj) + "'";
            } else {
                if (obj != null) {
                    value = obj.toString();
                } else {
                    value = "";
                }
    
            }
            return value;
        }
    
        /**
         * 打印SQL
         * @param configuration
         * @param boundSql
         * @return
         */
        public static String showSql(Configuration configuration, BoundSql boundSql) {
            Object parameterObject = boundSql.getParameterObject();
            List parameterMappings = boundSql.getParameterMappings();
            String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
            if (parameterMappings.size() > 0 && parameterObject != null) {
                TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
                if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                    sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));
    
                } else {
                    MetaObject metaObject = configuration.newMetaObject(parameterObject);
                    for (ParameterMapping parameterMapping : parameterMappings) {
                        String propertyName = parameterMapping.getProperty();
                        if (metaObject.hasGetter(propertyName)) {
                            Object obj = metaObject.getValue(propertyName);
                            sql = sql.replaceFirst("\\?", getParameterValue(obj));
                        } else if (boundSql.hasAdditionalParameter(propertyName)) {
                            Object obj = boundSql.getAdditionalParameter(propertyName);
                            sql = sql.replaceFirst("\\?", getParameterValue(obj));
                        }
                    }
                }
            }
            return sql;
        }
    }

    使用同样的方法进行测试,查看控制台打印结果:

    BoundSql=insert into users (gender,  userName ,create_date) values(? , ?, ?)
    sql=insert into users (gender, userName ,create_date) values('male' , 'test2', '2022-1-14 18:40:08')

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发 Java
    相关文章 更多
    Java单元测试入门:使用JUnit 5编写第一个测试用例
    Java单元测试入门:使用JUnit 5编写第一个测试用例

    面向初学者的JUnit 5实用入门指南。从测试概念、Maven依赖配置,到手把手编写首个单元测试用例及断言验证,助你掌握Java单元测试的核心流程。

    Java变量未初始化报错解析:局部变量与成员变量默认值差异
    Java变量未初始化报错解析:局部变量与成员变量默认值差异

    深入解析Java中局部变量未初始化编译报错与成员变量自动赋予默认值的机制差异,涵盖JVM内存分配、编译期确定性赋值检查与实际排查修复方案。

    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat
    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat

    详细讲解在Java中获取当前系统时间并进行格式化输出的两种核心方案:基于Java 8全新日期时间API的DateTimeFormatter与传统遗留的SimpleDateFormat,包含完整代码示例、线程安全机制对比及生产选型建议。

    Java中final与static final常量的定义及初始化规则
    Java中final与static final常量的定义及初始化规则

    深入解析Java中final变量与static final常量的定义、内存语义、初始化时机及合法赋值路径,涵盖编译期常量优化与实战避坑指南。

    Java抽象方法与接口方法的区别及选型建议
    Java抽象方法与接口方法的区别及选型建议

    深入对比Java中抽象类抽象方法与接口方法的语法演进、设计意图及运行期差异,并提供清晰的架构选型决策流程与实战范例。

    Java实现HTTP请求与JSON解析的完整代码示例
    Java实现HTTP请求与JSON解析的完整代码示例

    本文提供Java标准库HttpClient发起HTTP GET/POST请求并结合Jackson进行JSON数据反序列化与序列化的完整代码示例与工程实践指南。

    Java递归出现StackOverflowError的原因分析及栈空间优化方案
    Java递归出现StackOverflowError的原因分析及栈空间优化方案

    详细分析Java递归调用出现StackOverflowError的原因,结合代码说明线程栈、栈帧和递归深度之间的关系,并介绍终止条件排查、迭代改写、减少栈帧开销以及通过-Xss调整JVM栈空间的方法。

    Java空指针异常(NPE)的代码排查与修复实例
    Java空指针异常(NPE)的代码排查与修复实例

    针对Java中常见的NullPointerException(NPE),本文从堆栈日志定位、典型代码触发场景、具体修复代码对比到防御体系进行系统讲解,提供实用且易上手的排查与修复方案。

    selenium上传文件教程本地文件路径设置方法
    selenium上传文件教程本地文件路径设置方法

    深入解析Selenium处理文件上传的技术细节,涵盖标准HTML控件的路径直接注入法与非标准控件的系统级模拟方案,提供Windows与Linux环境下的路径设置最佳实践及常见异常排查。

    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景
    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景

    针对大数据工程场景,系统对比Java、Scala与Python在执行性能、生态结合、开发效率和维护成本上的核心差异,给出清晰的架构选型依据与落地方案。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。