栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

手把手教你开发 MyBatis 插件

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

手把手教你开发 MyBatis 插件

小伙伴们元宵节快乐,记得吃元宵哦~

在日常开发中,小伙伴们多多少少都有用过 MyBatis 插件,松哥猜测大家用的最多的就是 MyBatis 的分页插件!不知道小伙伴们有没有想过有一天自己也来开发一个 MyBatis 插件?

其实自己动手撸一个 MyBatis 插件并不难,今天松哥就把手带大家撸一个 MyBatis 插件!

1.MyBatis 插件接口

即使你没开发过 MyBatis 插件,估计也能猜出来,MyBatis 插件是通过拦截器来起作用的,MyBatis 框架在设计的时候,就已经为插件的开发预留了相关接口,如下:

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  default void setProperties(Properties properties) {
    // NOP
  }

}

这个接口中就三个方法,第一个方法必须实现,后面两个方法都是可选的。三个方法作用分别如下:

  1. intercept:这个就是具体的拦截方法,我们自定义 MyBatis 插件时,一般都需要重写该方法,我们插件所完成的工作也都是在该方法中完成的。

  2. plugin:这个方法的参数 target 就是拦截器要拦截的对象,一般来说我们不需要重写该方法。Plugin.wrap 方法会自动判断拦截器的签名和被拦截对象的接口是否匹配,如果匹配,才会通过动态代理拦截目标对象。

  3. setProperties:这个方法用来传递插件的参数,可以通过参数来改变插件的行为。我们定义好插件之后,需要对插件进行配置,在配置的时候,可以给插件设置相关属性,设置的属性可以通过该方法获取到。插件属性设置像下面这样:


    
 
    

2.MyBatis 拦截器签名

拦截器定义好了后,拦截谁?

这个就需要拦截器签名来完成了!

拦截器签名是一个名为 @Intercepts 的注解,该注解中可以通过 @Signature 配置多个签名。@Signature 注解中则包含三个属性:

  • type: 拦截器需要拦截的接口,有 4 个可选项,分别是:Executor、ParameterHandler、ResultSetHandler 以及 StatementHandler。
  • method: 拦截器所拦截接口中的方法名,也就是前面四个接口中的方法名,接口和方法要对应上。
  • args: 拦截器所拦截方法的参数类型,通过方法名和参数类型可以锁定唯一一个方法。

一个简单的签名可能像下面这样:

@Intercepts(@Signature(
 type = ResultSetHandler.class,
 method = "handleResultSets",
 args = {Statement.class}
))
public class CamelInterceptor implements Interceptor {
    //...
}
3.被拦截的对象

根据前面的介绍,被拦截的对象主要有如下四个:

Executor

public interface Executor {

  ResultHandler NO_RESULT_HANDLER = null;

  int update(MappedStatement ms, Object parameter) throws SQLException;

   List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

   List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

   Cursor queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

  List flushStatements() throws SQLException;

  void commit(boolean required) throws SQLException;

  void rollback(boolean required) throws SQLException;

  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);

  boolean isCached(MappedStatement ms, CacheKey key);

  void clearLocalCache();

  void deferLoad(MappedStatement ms, metaObject resultObject, String property, CacheKey key, Class targetType);

  Transaction getTransaction();

  void close(boolean forceRollback);

  boolean isClosed();

  void setExecutorWrapper(Executor executor);

}

各方法含义分别如下:

  • update:该方法会在所有的 INSERT、 UPDATE、 DELETE 执行时被调用,如果想要拦截这些操作,可以通过该方法实现。
  • query:该方法会在 SELECT 查询方法执行时被调用,方法参数携带了很多有用的信息,如果需要获取,可以通过该方法实现。
  • queryCursor:当 SELECT 的返回类型是 Cursor 时,该方法会被调用。
  • flushStatements:当 SqlSession 方法调用 flushStatements 方法或执行的接口方法中带有 @Flush 注解时该方法会被触发。
  • commit:当 SqlSession 方法调用 commit 方法时该方法会被触发。
  • rollback:当 SqlSession 方法调用 rollback 方法时该方法会被触发。
  • getTransaction:当 SqlSession 方法获取数据库连接时该方法会被触发。
  • close:该方法在懒加载获取新的 Executor 后会被触发。
  • isClosed:该方法在懒加载执行查询前会被触发。

ParameterHandler

public interface ParameterHandler {

  Object getParameterObject();

  void setParameters(PreparedStatement ps) throws SQLException;

}

各方法含义分别如下:

  • getParameterObject:在执行存储过程处理出参的时候该方法会被触发。
  • setParameters:设置 SQL 参数时该方法会被触发。

ResultSetHandler

public interface ResultSetHandler {

   List handleResultSets(Statement stmt) throws SQLException;

   Cursor handleCursorResultSets(Statement stmt) throws SQLException;

  void handleOutputParameters(CallableStatement cs) throws SQLException;

}

各方法含义分别如下:

  • handleResultSets:该方法会在所有的查询方法中被触发(除去返回值类型为 Cursor 的查询方法),一般来说,如果我们想对查询结果进行二次处理,可以通过拦截该方法实现。
  • handleCursorResultSets:当查询方法的返回值类型为 Cursor 时,该方法会被触发。
  • handleOutputParameters:使用存储过程处理出参的时候该方法会被调用。

StatementHandler

public interface StatementHandler {

  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;

  void parameterize(Statement statement)
      throws SQLException;

  void batch(Statement statement)
      throws SQLException;

  int update(Statement statement)
      throws SQLException;

   List query(Statement statement, ResultHandler resultHandler)
      throws SQLException;

   Cursor queryCursor(Statement statement)
      throws SQLException;

  BoundSql getBoundSql();

  ParameterHandler getParameterHandler();

}

各方法含义分别如下:

  • prepare:该方法在数据库执行前被触发。
  • parameterize:该方法在 prepare 方法之后执行,用来处理参数信息。
  • batch:如果 MyBatis 的全剧配置中配置了 defaultExecutorType=”BATCH”,执行数据操作时该方法会被调用。
  • update:更新操作时该方法会被触发。
  • query:该方法在 SELECT 方法执行时会被触发。
  • queryCursor:该方法在 SELECT 方法执行时,并且返回值为 Cursor 时会被触发。

在开发一个具体的插件时,我们应当根据自己的需求来决定到底拦截哪个方法。

4.开发分页插件 4.1 内存分页

MyBatis 中提供了一个不太好用的内存分页功能,就是一次性把所有数据都查询出来,然后在内存中进行分页处理,这种分页方式效率很低,基本上没啥用,但是如果我们想要自定义分页插件,就需要对这种分页方式有一个简单了解。

内存分页的使用方式如下,首先在 Mapper 中添加 RowBounds 参数,如下:

public interface UserMapper {
    List getAllUsersByPage(RowBounds rowBounds);
}

然后在 XML 文件中定义相关 SQL:


    select * from user

最后我们进行测试:

@Test
public void test3() {
    UserMapper userMapper = sqlSessionFactory.openSession().getMapper(UserMapper.class);
    List list = userMapper.getAllUsersByPage(new RowBounds(1,2));
    for (User user : list) {
 System.out.println("user = " + user);
    }
}

这里在查询时,我们使用了 RowBounds 对象,就只会进行分页,而不会统计总记录数。需要注意的时,此时的分页已经不是内存分页,而是物理分页了,这点我们从打印出来的 SQL 中也能看到,如下:

可以看到,查询的时候就已经进行了分页了。

当然,我们也可以使用 PageRowBounds 进行测试,如下:

@Test
public void test4() {
    UserMapper userMapper = sqlSessionFactory.openSession().getMapper(UserMapper.class);
    PageRowBounds pageRowBounds = new PageRowBounds(1, 2);
    List list = userMapper.getAllUsersByPage(pageRowBounds);
    for (User user : list) {
 System.out.println("user = " + user);
    }
    System.out.println("pageRowBounds.getTotal() = " + pageRowBounds.getTotal());
}

此时通过 pageRowBounds.getTotal() 方法我们就可以获取到总记录数。

6.小结

好啦,今天主要和小伙伴们分享了我们如何自己开发一个 MyBatis 插件,插件功能其实都是次要的,最主要是希望小伙伴们能够理解 MyBatis 的工作流程。

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号