Java mybatis 开发自定义插件

目录
  • 介绍
  • 使用场景
  • Interceptor拦截器
  • InterceptorChain拦截器链
  • 拦截方法
  • 注解
    • Intercepts
    • Signature
  • 示例
    • 步骤
    • 入门使用
    • 动态给属性赋值
    • 打印SQL

介绍

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<Interceptor> 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<Interceptor> 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

<plugins>
    <plugin interceptor="plugin.PrintSqlPlugin"/>
    <plugin interceptor="plugin.InsertInterceptor">
      <property name="createDate" value="createDate"/>
    </plugin>
  </plugins>

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属性收到赋值的情况下,通过拦截器进行赋值,最后是保存到数据库中了。

打印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<ParameterMapping> 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')

mybatis自定义插就到这里了,其实操作也简单,用好了也很强大。

到此这篇关于Java mybatis 开发自定义插件的文章就介绍到这了,更多相关Java mybatis 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java Mybatis框架由浅入深全解析下篇

    目录 前言 什么是Maven Maven环境配置 Maven 构建生命周期 Maven项目的创建 目录结构 pom.xml文件 什么是pom.xml文件 加入项目所需依赖 添加资源文件的指定 总结 前言 上一篇我们第一次测试了Mybatis框架,并且成功了. 本想直接推进学习框架配置,但是很多小伙伴对Maven不了解,今天就来浅谈一下Maven. 今天我们就来剖析pom.xml配置文件,这个pom.xml文件,是我们构建maven项目的配置文件,既然我们使用到了,就利用本篇文章学习一下吧.这里只

  • Java MyBatis是如何执行一条SQL语句的

    目录 背景 阅读环境 阅读过程 加载XML的过程 创建Mapper 获得一个Mapper 执行一个Mapper的方法 结论 背景 在前两天的一次面试中,面试官问了一个和标题一样的问题,由于一直认为MyBatis只是一个ORM框架,所以并没有对他有过深入的了解,于是被问到了,那么这一篇文章来从源码探究一下MyBatis是如何执行一条SQL语句的. 阅读环境 源码环境,github直接下载的main分支代码,版本号为 3.5.11-SNAPSHOT Ide Jetbrain Idea 2021.2.

  • Java Mybatis使用resultMap时,属性赋值顺序错误的巨坑

    目录 Mybatis使用resultMap属性赋值顺序错误 ids是后加入的字段 resultMap中是这样写的 解决办法 Mybatis使用resultMap时需注意 Mybatis使用resultMap属性赋值顺序错误 今天发现个坑,新建的表使用生成工具生成的mapper文件和实体类后,发现少了个字段就又手动加了下,结果发现一个问题 ids是后加入的字段 @Data @Builder public class QueryRecordPo {        //若干其他属性....     p

  • java mybatis如何操作postgresql array数组类型

    目录 我定义了几个基础数据类型的数组 java mybatis操作 postgresql array数组类型备忘 找了半天没有找到postgresql中关于array数组类型的字段如何对应到java中的数据类型,后来找到了mybatis的TypeHandler,只要实现一个自定义的TypeHandler就行了,如下, 我定义了几个基础数据类型的数组 public class ArrayTypeHandler extends BaseTypeHandler<Object[]> { private

  • 利用Java如何获取Mybatis动态生成的sql接口实现

    目录 前言 1.编写xml: SqlGenarate.mapper.xml 2.定义接口 3.实现接口 总结 前言 如果你有使用 JDBC 或其他类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句有多么痛苦.拼接的时候要确保不能忘了必要的空格,还要注意省掉列名列表最后的逗号.利用动态 SQL 这一特性可以彻底摆脱这种痛苦.通常使用动态 SQL 不可能是独立的一部分,MyBatis 当然使用一种强大的动态 SQL 语言来改进这种情形,这种语言可以被用在任意的 SQL 映射语句中. 目的:利

  • Java中使用MyBatis-Plus操作数据库的实例

    目录 MyBatis-Plus 官网 使用 测试数据插入数据库 测试查询所有 测试删除数据 测试修改数据 MyBatis-Plus MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发.提高效率而生. MyBatis可以直接在xml中通过SQL语句操作数据库,很灵活.但其操作都要通过SQL语句进行,就必须写大量的xml文件,很麻烦.mybatis

  • Java简单实现SpringMVC+MyBatis分页插件

    1.封装分页Page类 package com.framework.common.page.impl; import java.io.Serializable; import com.framework.common.page.IPage; /** * * * */ public abstract class BasePage implements IPage, Serializable { /** * */ private static final long serialVersionUID

  • MyBatis ofType和javaType的区别说明

    目录 ofType和javaType的区别 MyBatis笔记 javatype和ofType ofType和javaType的区别 JavaType和ofType都是用来指定对象类型的,但是JavaType是用来指定pojo中属性的类型,而ofType指定的是 映射到list集合属性中pojo的类型 . pojo类: public class User {      private int id ;      private String username ;      private Str

  • Java mybatis 开发自定义插件

    目录 介绍 使用场景 Interceptor拦截器 InterceptorChain拦截器链 拦截方法 注解 Intercepts Signature 示例 步骤 入门使用 动态给属性赋值 打印SQL 介绍 MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用.比如执行前.执行后或者对SQL结果集处理.sql入参处理等,这样就可以在不修改mybatis源码的情况下对sql执行的过程或结果进行修改,实现了解耦.mybatis 是在动态代理的基础上实现的. 使用场景 如果业务中需要设置一些

  • idea 安装 Mybatis 开发帮助插件 MyBatisCodeHelper-Pro 插件破解版的方法

    1,简述 MyBatisCodeHelper-Pro 插件可以帮助我们快速的开发 mybatis,其中最重要的一项功能就是在 mapper 接口 和 xml 文件之间互相跳转,从而锁定对应的方法和SQL片段,解决了,项目越来越大,越来越复杂时,难以寻找映射文件的困扰,即使是做了命名规范,也不如点击一下,直. 接跳转来的方便!但是很可惜,这个插件是收费的! 2,安装 1,查找适合自己的插件版本,我们打开 idea => file => setting => plugin 图上的 2.7.8

  • jQuery自定义插件详解及实例代码

     jQuery自定义插件 jQuery是一个功能强大的库,提供了开发JavaScript项目所需的所有核心函数.然而,有时候我们还是需要使用自定义代码来扩展这些核心函数来提高开发效率. jQuery库是为了加快JavaScript的开发速度而设计的,通过简化编写JavaScript的方式,减少代码量. jQuery编写插件有两种方式 1.添加jQuery对象级别的插件,原理是给jQuery类添加方法. 写法如下: (function($){ $.fn.extend({ 函数名:function(

  • Java Mybatis框架多表操作与注解开发详解分析

    目录 一对一查询 多对多查询 Mybatis的注解开发 Mybatis的增删查改 MyBatis的注解实现复杂映射开发 一对一查询 一对一查询的模型 用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户. 一对一查询的需求:查询一个订单,与此同时查询出该订单所属的用户 一对一查询的语句 对应的sql语句: select * from orders o,user u where o.uid=u.id;查询的结果如下: 创建Order和User实体 创建OrderMapper接口 p

  • 使用 Java 开发 Gradle 插件的步骤

    目录 1创建项目 2动手开发 3发布插件 3.1发布到Maven仓库 3.2发布到Gradle官方插件门户 4更多 4.1在插件中添加任务 4.2添加扩展 5小结 6参考内容 Gradle 插件代码可以在 build.gradle 中,buildSrc 项目中,以及独立的插件项目中编写.本文将介绍如何在一个独立的项目中使用 Java 语言编写 Gradle 插件,并发布到仓库中. 1 创建项目 Gradle 插件项目和普通的 Java 项目没有什么不同,普通项目是基于其它三方包进行开发,而 Gr

  • java微信开发API第四步 微信自定义个性化菜单实现

    微信如何实现自定义个性化菜单,下面为大家介绍 一.全局说明 详细说明请参考前两篇文章. 二.本文说明 本文分为五部分:     * 工具类AccessTokenUtils的封装     * 自定义菜单和个性化菜单文档的阅读解析     * 菜单JSON的分析以及构建对应bean     * 自定义菜单的实现     * 个性化菜单的实现 微信自定义菜单所有类型菜单都给出演示 本文结束会给出包括本文前四篇文章的所有演示源码 工具类AccessTokenUtils的封装 在上文中关于AccessTo

  • Java 8 开发的 Mybatis 注解代码生成工具

    MybatisAnnotationTools MybatisAnnotationTools 是基于 Java8 开发的一款可以用于自动化生成 MyBatis 注解类的工具,支持配置数据源.类路径,表名去前缀.指定类名前后缀等功能.同时支持 Java 8 和 Mybatis 3.5+ 的一些新特性,比如时间类 LocalDateTime/LocalDate .接口方法返回 Optional 等. 此工具生成的代码是基于注解的 Mybatis 接口方法,所以不会生成 XML 配置文件. 源码地址:G

  • spring+springmvc+mybatis 开发JAVA单体应用

    myshop 概述 myshop项目是根据视频教程 Java 单体应用 做的一个练习项目,目前完成了登录功能.用户管理.类别管理后续有时间会继续做其它的功能.趁着双11花了99元一年买了台阿里云服务器,演示地址>> myshop 项目地址  https://gitee.com/yehuabin/myshop 项目结构 my-shop-dependencies:管理jar包的版本,所有项目依赖此项目 my-shop-commons:项目通用工具.实体类 my-shop-domain: POJO类

  • Java中使用WebUploader插件上传大文件单文件和多文件的方法小结

    一.使用webuploader插件的原因说明 被现在做的项目坑了. 先说一下我的项目架构spring+struts2+mybatis+MySQL 然后呢.之前说好的按照2G上传就可以了,于是乎,用了ajaxFileUpload插件,因为之前用图片上传也是用这个,所以上传附件的时候就直接拿来用了 各种码代码,测试也测过了,2G文件上传没问题,坑来了,项目上线后,客户又要求上传4G文件,甚至还有20G以上的..纳尼,你不早说哦... 在IE11下用ajaxFileUpload.js插件上传超过4G的

  • 搭建MyBatis开发环境及基本的CURD介绍

    一.MyBatis概述 首先就是之前Java web中的三层结构 界面层: 和用户打交道的, 接收用户的请求参数, 显示处理结果的.(jsp ,html ,servlet) 业务逻辑层: 接收了界面层传递的数据,计算逻辑,调用数据库,获取数据 数据访问层: 就是访问数据库, 执行对数据的查询,修改,删除等等的 三层中对应的包: 界面层: controller包 (servlet) 业务逻辑层: service 包(XXXService类) 数据访问层: dao包(XXXDao类) 三层中类的交互

随机推荐