博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
PageHelper源码学习
阅读量:4210 次
发布时间:2019-05-26

本文共 6011 字,大约阅读时间需要 20 分钟。

做java项目离不来PageHelper,简单说pagehelper就是分页的插件,但是都知道分页其实是对我们要执行的sql之外加上一层,所以相对来说分页其实就是项目开发中的公因式,公因式大家都懂的,开发一个包就可以了。那么我们具体看看pageHelper的原理是是什么的。或者它是怎么做的,我们看看源码的根本想法就是获得一点感悟,这些感悟不仅仅对工作有好处,往远的说还或许影响我们看待事物的角度,久而久之会形成我们的价值观。

从源码中我们看到pagehelper主要有几个包和一些类组成,按照分门别类的思想,那么每个包都对应一个相同的点,而最底下的类应该就是一些功能聚合类。

Page page = PageHelper.startPage(1, 10);

一般来说,我们用pagehelper大概都是上述的方式。

在设置当前页和每页大小的时候,我们看到这里从localpag方法中获取;

public staticPagestartPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {//申明一个分页实体        Page page = new Page(pageNum, pageSize, count);        page.setReasonable(reasonable);        page.setPageSizeZero(pageSizeZero);//从当前线程中获取分页变量        Page oldPage = getLocalPage();        if (oldPage != null && oldPage.isOrderByOnly()) {            page.setOrderBy(oldPage.getOrderBy());        }//将分页变量设置到线程的threadlocal        setLocalPage(page);        return page;    }//具体的threadLocal    protected static final ThreadLocalLOCAL_PAGE = new ThreadLocal();    protected static boolean DEFAULT_COUNT = true;    public PageMethod() {    }    protected static void setLocalPage(Page page) {        LOCAL_PAGE.set(page);    }    public staticPagegetLocalPage() {        return (Page)LOCAL_PAGE.get();    }

通过上述分析,我们得出的结论就是pageHelper是通过threadlocal来实现分页的,具体就是将分页的参数放到threadlocal中,这是我们设置完毕之后在没有传递参数的原因。除此之外我们还发现如果一次请求中包含多个分页查询时,其实是用的第一个page,并没有重新定义。

如上图所示,pagehelper应该还是支持上述的数据库的。但是并不知道这块是如何整合的。但是作者发现ibatis的接口intercept存在于pagehelper中,我们发现这个接口提供了一个几个方法和一些注解。

该注解是ibatis提供的,type表示拦截的层次,method表示拦截的方法,args表示参数,这是使用了注解,那么在jdk类加载的时候就会获取到。所以只需要判断是否有这个注解就可以完成判断

@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})})

方法setProperties是实例化一个具体的数据库分页实体比如mysql,oracle等。设置好之后,在上边的intercept中就会进行使用。

public void setProperties(Properties properties) {        this.msCountMap = CacheFactory.createCache(properties.getProperty("msCountCache"), "ms", properties);//拿到数据库类型        String dialectClass = properties.getProperty("dialect");        if (StringUtil.isEmpty(dialectClass)) {            dialectClass = this.default_dialect_class;        }        try {//通过反射拿到对应的pagehelper            Class aClass = Class.forName(dialectClass);            this.dialect = (Dialect)aClass.newInstance();        } catch (Exception var6) {            throw new PageException(var6);        }//设置一些参数        this.dialect.setProperties(properties);        String countSuffix = properties.getProperty("countSuffix");        if (StringUtil.isNotEmpty(countSuffix)) {            this.countSuffix = countSuffix;        }        try {            this.additionalParametersField = BoundSql.class.getDeclaredField("additionalParameters");            this.additionalParametersField.setAccessible(true);        } catch (NoSuchFieldException var5) {            throw new PageException(var5);        }    }

执行分页,因为在setProperties方法中已经已经实例化pagehelper,那么这里直接取执行。

public Object intercept(Invocation invocation);

在intercept方法中,我们发现最终也是调用用我们获取分页的相关sql并执行了。

public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {        String sql = boundSql.getSql();        Page page = this.getLocalPage();        String orderBy = page.getOrderBy();        if (StringUtil.isNotEmpty(orderBy)) {            pageKey.update(orderBy);            sql = OrderByParser.converToOrderBySql(sql, orderBy);        }        return page.isOrderByOnly() ? sql : this.getPageSql(sql, page, pageKey);    }

分析到这里,我们大概明白了pagehelper仅仅是用来想threadlocal中设置分页参数的,而最周的执行是通过intercept接口来执行的,而接口能被扫描到的原因就是pagehelper中添加了@intercepts注解。既然如此,我们有必要研究一ibatis是如何调用pagehelper的这个接口的。

通过代码跟踪,我们发现plugin类调用了intercept方法,而intercept接口的解析则是方法getSignatureMap完成。

private static Map, Set> getSignatureMap(Interceptor interceptor) {//解析注解        Intercepts interceptsAnnotation = (Intercepts)interceptor.getClass().getAnnotation(Intercepts.class);        if (interceptsAnnotation == null) {            throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());        } else {//拿到注解的参数            Signature[] sigs = interceptsAnnotation.value();            Map, Set> signatureMap = new HashMap();            Signature[] var4 = sigs;            int var5 = sigs.length;            for(int var6 = 0; var6 < var5; ++var6) {                Signature sig = var4[var6];                Set methods = (Set)signatureMap.computeIfAbsent(sig.type(), (k) -> {                    return new HashSet();                });                try {                    Method method = sig.type().getMethod(sig.method(), sig.args());                    methods.add(method);                } catch (NoSuchMethodException var10) {                    throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + var10, var10);                }            }            return signatureMap;        }    }

但是方法wrap方法是谁调用的?作者通过跟踪,发现如下所示。那么我们基本可以得出这个类是ibatis的一个变量,那么是否可以说明ibatis中可以随意调用。

warp方法最后又回调到了plugin类,但是传入的确实this,那么意思就是将自己加入进去。

public Object plugin(Object target) {        //传入pagehelperintercept        return Plugin.wrap(target, this);    }    public static Object wrap(Object target, Interceptor interceptor) {        Map, Set> signatureMap = getSignatureMap(interceptor);        Class type = target.getClass();        Class[] interfaces = getAllInterfaces(type, signatureMap);        return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;    }

作者通过查阅相关资料,发现跟作者想的一样,这里的interceptorChain是一个私有变量。并且在创建相应的拦截器的时候都会调用。

但是作者好奇的是这里初始的intercept是在哪里设置的,最后通过作者的跟踪,发现是pagehelper的自动配置包做了这件事情。

问题分析到这里好像是清晰了好多,但是问题是ibatis是如何调用上边的intercept方法的,虽然我们知道他们走的是jdk反射提供的invoke方法,但是什么时候会invoke?

转载地址:http://xekmi.baihongyu.com/

你可能感兴趣的文章
专业计划
查看>>
小米笔试:最大子数组乘积
查看>>
常见的排序算法
查看>>
5.PyTorch实现逻辑回归(二分类)
查看>>
6.PyTorch实现逻辑回归(多分类)
查看>>
8.Pytorch实现5层全连接结构的MNIST(手写数字识别)
查看>>
9.PyTorch实现MNIST(手写数字识别)(2卷积1全连接)
查看>>
hdu 3460 Ancient Printer(trie tree)
查看>>
中间数
查看>>
KMP求前缀函数(next数组)
查看>>
KMP
查看>>
poj 3863Business Center
查看>>
Android编译系统简要介绍和学习计划
查看>>
Android编译系统环境初始化过程分析
查看>>
user2eng 笔记
查看>>
DRM in Android
查看>>
ARC MRC 变换
查看>>
Swift cell的自适应高度
查看>>
【linux】.fuse_hiddenXXXX 文件是如何生成的?
查看>>
【LKM】整合多个LKM为1个
查看>>