Mybatis (二) 插件的开发与使用案例

1、插件的应用场景

  • 分页功能

    mybatis的分页默认是基于内存分页的(查出所有,再截取),数据量大的情况下效率较低,不过使用mybatis插件可以改变该行为,只需要拦截StatementHandler类的prepare方法,改变要执行的SQL语句为分页语句即可;例如:Mybatis-plus的分页插件。

  • 公共字段统一赋值

    一般业务系统都会有创建者,创建时间,修改者,修改时间四个字段,对于这四个字段的赋值,实际上可以在DAO层统一拦截处理,可以用mybatis插件拦截Executor类的update方法,对相关参数进行统一赋值即可;

  • 性能监控

    对于SQL语句执行的性能监控,可以通过拦截Executor类的update, query等方法,用日志记录每个方法执行的时间;

2、插件原理

mybatis在开启sqlSession到执行一次流程的过程给死个重要的对象设计了插件的功能,本质上去使用Jdk的动态代理来实现自定的开发。这四个对象分别是:

  • 执行器Executor(update、query、commit、rollback等方法)
  • 参数处理器ParameterHandler(getParameterObject、setParameters方法)
  • 结果集处理器ResultSetHandler(handleResultSets、handleOutputParameters等方法)
  • SQL语法构建器StatementHandler(prepare、parameterize、batch、update、query等方法)

具体的原理在上一篇文章中简要的介绍过了。

首先在Plugin中包装成代理对象:


  public static Object wrap(Object target, Interceptor interceptor) {
  	// 解析@Intercepts注解中需要拦截的方法
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    // 获取需要代理的接口,然后使用Jdk代理,Invoke对象就是Plugin本身
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

具体的执行代码是在Plugin的invoke方法中了,源码如下:

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // 从缓存中获取需要拦截的方法
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      // 需要拦截则调用拦截器的intercept方法拦截
      if (methods != null && methods.contains(method)) {
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

3、使用案例

基于我上一篇文章中的分析案例使用

package com.xiao7.mybatis.plugins;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.lang.reflect.Method;
import java.util.Properties;

/**
 * @author: xiao7
 * @date: Created in 13:39 2021/5/18
 * @description: Executor插件
 * @version:
 */

@Intercepts({@Signature(type = Executor.class, method = "query",
        args = {
                MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class
        }
)})
public class ExecutorPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Method method = invocation.getMethod();
        Object target = invocation.getTarget();
        Object[] args = invocation.getArgs();
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

测试类:

package com.xiao7.mybatis;

import com.xiao7.mybatis.entity.User;
import com.xiao7.mybatis.mapper.UserMapper;
import com.xiao7.mybatis.plugins.ExecutorPlugin;
import com.xiao7.mybatis.plugins.ParameterHandlerPlugin;
import com.xiao7.mybatis.plugins.ResultSetHandlerPlugin;
import com.xiao7.mybatis.plugins.StatementHandlerPlugin;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

/**
 * @author: xiao7
 * @date: Created in 9:34 2021/5/18
 * @description: mybatis测试类
 * @version:
 */
public class MybatisExample {
    public static final String URL = "jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&characterEncoding=utf-8";
    public static final String USER = "root";
    public static final String PASSWORD = "123456";

    public static void main(String[] args) {
        String resource = "mybatis-config.xml";
        InputStream inputStream = null;
        SqlSession sqlSession = null;
        try {
            //读取mybatis-config.xml
            inputStream = Resources.getResourceAsStream(resource);
            //解析mybatis-config.xml配置文件,创建sqlSessionFactory
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            sqlSessionFactory.getConfiguration().addInterceptor(new ExecutorPlugin());
            sqlSessionFactory.getConfiguration().addInterceptor(new ParameterHandlerPlugin());
            sqlSessionFactory.getConfiguration().addInterceptor(new ResultSetHandlerPlugin());
            sqlSessionFactory.getConfiguration().addInterceptor(new StatementHandlerPlugin());


            //创建sqlSession
            sqlSession = sqlSessionFactory.openSession(true);
            //创建userMapper对象(UserMapper并没有实现类)
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            //调用userMapper对象的方法
            User user = userMapper.selectById("1");
            System.out.println(user);

            // 提交
            sqlSession.commit();

            SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
            UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
            User user1 = userMapper1.selectById("1");
            System.out.println(user1);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            sqlSession.close();
        }
    }
}

插件开发需要注意的是:

  • 不要定义过多的插件,代理嵌套过多,执行方法的时候,比较耗性能;
  • 拦截器实现类的intercept方法里最后不要忘了执行invocation.proceed()方法,否则多个拦截器情况下,执行链条会断掉;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值