Java mybatis 开发自定义插件

作者:索码理 时间:2022-11-26 03:29:24 

介绍

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

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<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自定义插就到这里了,其实操作也简单,用好了也很强大。

来源:https://juejin.cn/post/7132293317231902757

标签:Java,mybatis,自定义,插件
0
投稿

猜你喜欢

  • eclipse的web项目实现Javaweb购物车的方法

    2023-07-28 15:55:53
  • Java中的AQS同步队列问题详解

    2021-08-16 19:50:23
  • spring-cloud-stream结合kafka使用详解

    2022-05-19 14:32:50
  • Spring AOP源码深入分析

    2023-08-15 13:01:16
  • C#中常用的运算符总结

    2023-10-10 04:09:32
  • Java实现指定线程执行顺序的三种方式示例

    2021-08-16 15:11:34
  • SpringBoot整合XxlJob分布式任务调度平台

    2022-07-09 09:47:37
  • 解决@ConfigurationProperties注解的使用及乱码问题

    2023-09-08 06:55:10
  • 关于Spring BeanPostProcessor的执行顺序

    2022-03-17 23:53:41
  • springboot+mybatis报错找不到实体类的问题

    2023-05-20 02:04:10
  • 通过实例深入了解java序列化

    2022-12-01 17:37:39
  • java和matlab画多边形闭合折线图示例讲解

    2021-06-23 08:39:15
  • Kotlin启动协程的三种方式示例详解

    2023-06-07 02:58:53
  • Flutter系列重学Container示例详解

    2023-07-19 00:46:10
  • C#实现汉字转拼音或转拼音首字母的方法

    2022-10-22 17:26:01
  • Android组件化开发路由的设计实践

    2021-06-20 00:27:24
  • Java输入/输出流体系详解

    2023-03-01 06:37:00
  • Android游戏开发学习②焰火绽放效果实现方法

    2023-10-26 08:54:53
  • 详解App保活实现原理

    2022-01-20 17:59:10
  • C#中判断某类型是否可以进行隐式类型转换

    2023-03-28 15:29:36
  • asp之家 软件编程 m.aspxhome.com