【mybatis源码】3、插件源码分析

个人笔记,仅供参考

请先看: mybatis源码分析:https://blog.csdn.net/hancoder/article/details/110152732

Mybatis允许我们在四大对象执行的过程中对其指定方法进行拦截,这样就可以很方便了进行功能的增强,这个功能跟Spring的切面编程非常类似。


https://www.cnblogs.com/wuzhenzhao/p/11120848.html

分页的实现方式

1 借助集合数组进行分页

全部查到再subList,不好

2 map方式:

这种方式及sql进行分页

public List<User> selectUserBySql(int pageNo,int pageSize){
    
    
    HashMap<String,Object> map = new HashMap<>();
    map.put("currentIndex",(pageNo-1)*pageSize);
    map.put("pageSize",pageSize);
    return userMapper.selectUserBySql(map);
}

<select id="selectUserBySql" parameterType="map" resultMap="resultListUser">
   slect * from user limit #{
    
    currentIndex},#{
    
    pageSize}
</select>

3 RowBounds方式:

mybatis的rowBounds有两个参数,

  • offset:偏移的条数
  • limit:截取多少条

如果基础RowBounds,要注意一个问题:

即使子类声明了与父类完全一样的成员变量,也不会覆盖掉父类的成员变量。而是在子类实例化时,会同时定义两个成员变量,子类也可以同时访问到这两个成员变量,但父类不能访问到子类的成员变量(父类不知道子类的存在)。而具体在方法中使用成员变量时,究竟使用的是父类还是子类的成员变量,则由方法所在的类决定;即,方法在父类中定义和执行,则使用父类的成员变量,方法在子类中定义(包括覆盖父类方法)和执行,则使用子类的成员变量。

public List<User> selectUserByRowBounds(int start,int limit){
    
    
    return userMapper.selectUserBySql(new RowBounds(start,limit));
}

<select id="selectUserByRowBounds" resultMap="resultListUser">
   slect * from user 
</select>
RowBounds实现分页和通过数组分页原理差不多,都是一次获取所有符合条件的数据,然后在内存中对大数据进行操作,实现分页效果。他的缺点显然易见,一次性从数据库中获取的数据可能会很多,对内存的小号很大可能会导致性能变差,甚至保存内存溢出。
    所以,尤其在数据量很大的情况下,我们还是需要使用拦截器实现分页效果。RowBounds只适用于数据量相对较小的情况下使用。

怎么验证mybatis的方式

public class DefaultResultSetHandler implements ResultSetHandler {
    
    

    private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, 
                                                   ResultMap resultMap, 
                                                   ResultHandler<?> resultHandler, 
                                                   RowBounds rowBounds, 
                                                   ResultMapping parentMapping)
        throws SQLException {
    
    
        DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
        // 跳过RowBounds设置的offset值
        skipRows(rsw.getResultSet(), rowBounds);
        // 判断数据是否小于limit,如果小于limit的话就不断的循环取值
        while (shouldProcessMoreRows(resultContext, rowBounds) && 
               rsw.getResultSet().next()) {
    
    
            ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
            Object rowValue = getRowValue(rsw, discriminatedResultMap);
            storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
        }
    }

    //跳过不需要的行,应该就是rowbounds设置的limit和offset
    private void skipRows(ResultSet rs, 
                          RowBounds rowBounds) throws SQLException {
    
    
        if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
    
    
            if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
    
    
                rs.absolute(rowBounds.getOffset());
            }
        } else {
    
    
            //跳过RowBounds中设置的offset条数据。offset是0的话不丢弃数据
            for (int i = 0; i < rowBounds.getOffset(); i++) {
    
    
                rs.next();
            }
        }
    }

    private boolean shouldProcessMoreRows(ResultContext<?> context, 
                                          RowBounds rowBounds) throws SQLException {
    
    
        //判断数据是否小于limit,小于返回true
        return !context.isStopped() && 
            context.getResultCount() < rowBounds.getLimit();
    }

    

4 插件

helloworld

先看看mybatis插件是如何使用的。

比如我们写了如下的插件,然后用xml配置到配置文件中,spring启动时扫描到

插件实现功能:例如我们希望在sql语句之前前后进行时间打印,计算出sql执行的时间。此功能我们就可以拦截StatementHandler。这里我们需要时间Mybatis提供的Intercaptor接口。

package com.cl.mybatis.learn.intercaptor;

/**该注解签名告诉此拦截器拦截四大对象中的哪个对象的哪个方法,以及方法的签名信息*/
@Intercepts({
    
    
    @Signature(type = StatementHandler.class, 
               method = "query", 
               args = {
    
    Statement.class, ResultHandler.class})
})
public class SqlLogPlugin implements Interceptor {
    
    

    @Override // 在此实现自己的拦截逻辑,可从Invocation参数中拿到执行方法的对象,方法,方法参数,从而实现各种业务逻辑, 如下代码所示,从invocation中获取的statementHandler对象即为被代理对象,基于该对象,我们获取到了执行的原始SQL语句,以及prepare方法上的分页参数,并更改SQL语句为新的分页语句,最后调用invocation.proceed()返回结果。
    public Object intercept(Invocation invocation) throws Throwable {
    
    
        long begin = System.currentTimeMillis();
        try {
    
    
            // 继续执行
            return invocation.proceed();
        } finally {
    
    
            long time = System.currentTimeMillis() - begin;
            System.out.println("sql 运行了 :" + time + " ms");
        }
    }

    @Override // 生成代理对象;
    public Object plugin(Object target) {
    
    
        return Plugin.wrap(target, this);
    }

    @Override // 设置一些属性变量 String 和xml属性中配置对象的对象,如dialect;
    public void setProperties(Properties properties) {
    
    
        // dialect = Properties.getProperty("xml中配置的属性");
    }
}

接下来我们需要在mybatis-config.xml配置该拦截器:

<plugins>
    <plugin interceptor="com.cl.mybatis.learn.intercaptor.SqlLogPlugin">
        <property name="参数1" value="root"/>
        <property name="参数2" value="123456"/>
    </plugin>
</plugins>

启动spring就可以了,执行结果

DEBUG 11-24 17:51:34,877 ==>  Preparing: select * from user where id = ?   (BaseJdbcLogger.java:139) 
DEBUG 11-24 17:51:34,940 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:139) 
DEBUG 11-24 17:51:34,990 <==      Total: 1  (BaseJdbcLogger.java:139) 
sql 运行了 :51 ms
User{id=1, name='张三', age=42, sex=0}

预热

jdbc

作为预测知识,我们复习下jdbc的7个步骤:

jdbc流程:

  • 加载数据库驱动
  • 创建并获取数据库链接
  • 语句:创建jdbc statement对象(设置sql语句)
  • 参数:设置sql语句中的参数(使用preparedStatement)
  • 执行:通过statement执行sql并获取结果
  • 结果:对sql执行结果进行解析处理
  • 释放资源(resultSet、preparedstatement、connection)

而mybatis中有四大对象,对应的就是四种插件(拦截器)。

mybatis四大对象
  • StatementHandler:语法构建阶段
  • ParameterHandler:参数处理阶段
  • Executor:执行阶段
  • ResultSetHandler:结果集处理阶段

插件原理

插件的代理来源

在创建sqlSession的时候,会创建mybatis执行器,创建好三种执行器之一后(BatchExecutor、ReuseExecutor、SimpleExecutor),调用了一个executor = (Executor) interceptorChain.pluginAll(executor);,我们的插件逻辑就是在里面进行的,也就是说执行器经过了插件代理。

pluginAll就说遍历xml解析阶段解析好的插件(拦截器),依次调用该插件的plugin()

public class Plugin implements InvocationHandler {
    
    
    private Object target;
    private Interceptor interceptor;
    private Map<Class<?>, Set<Method>> signatureMap;
    // 构造函数就是对上面3个对象一对一赋值
    
    // 

代理执行器

这个plugin方法基本在自定义插件中基本都是这样写的

// 自定义插件.java
@Override
public Object plugin(Object target) {
    
    
    // 代理执行器,返回被代理的执行器
    return Plugin.wrap(target, this);
}

依次调用Interceptor接口的Object plugin(Object target)方法,返回代理好的对象,他里面调用了return Plugin.wrap(target,this),这个wrap里面就是Proxy.newInstance(类加载器,接口,new Plugin(target,interceptor,signatureMap))

// Plugin.java
public static Object wrap(Object target, Interceptor interceptor) {
    
    
    // ①
    // 读取自定义插件的注解信息,放入map。结果为type为key,value为Set<Method>。至此不再用@Signature注解
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    // ②获取接口。只会找到自定义插件中写到的接口
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);//不能直接type.getInterfaces(),因为获取不到多级接口
    if (interfaces.length > 0) {
    
    
        // ③
        // 创建动态代理
        return Proxy.newProxyInstance(
            type.getClassLoader(),
            interfaces,
            // ④
            new Plugin(target, interceptor, signatureMap));// 这里new了个Plugin类,他肯定实现了InvocationHanlder接口
    }
    return target;
}

之前在解析好xml后,插件我们已经解析好了,有了插件实例对象。该拦截器也被我们传入进来了。

虽然每个插件是一个拦截器,但插件上注解着@Intercepts代表有多个拦截条件

开本文档多窗口后,对应一下上面wrap代码,我们一次解析

①getSignatureMap(interceptor)

看一下在之前插件上写的注解信息,我们这步就是要解析这个@Intercepts({ @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}) })

之前提到过mybatis有四大对象。

这个静态方法扫描当前插件实例类上的注解信息,拿到要作用于的Mybatis四大对象,然后获取四大对象中对应要拦截的方法,插件上的注册信息被包装到一个map中(每个插件有一个map),key为要拦截的四大对象,value为要拦截四大对象的方法。

// @Intercepts({ @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = { Statement.class }) }) // 形参
// @Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    
    
    
    // 获取自定义拦截器类上的@Intercepts注解
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    
    if (interceptsAnnotation == null) {
    
     // issue #251 // 如果没有拦截器注解,就报错
        throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());      
    }
    // 获取该拦截器的value属性,是Signature数组,封装拦截信息
    Signature[] sigs = interceptsAnnotation.value();
    
    Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
    // 遍历每个@Signature,type为key,value是Set<Method>
    for (Signature sig : sigs) {
    
    
        // 如StatementHandler.class   ResultSetHandler.class
        Set<Method> methods = signatureMap.get(sig.type());
        if (methods == null) {
    
    
            // 创建set
            methods = new HashSet<Method>();
            signatureMap.put(sig.type(), methods); // type为key
        }
        try {
    
    
            // 获取如StatementHandler类指定的方法
            Method method = sig.type().getMethod(sig.method(), sig.args());
            // 放入set
            methods.add(method);
        } catch (NoSuchMethodException e) {
    
    
            throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
        }
    }
    return signatureMap;
}

②getAllInterfaces

获取自定义插件中四大对象的接口,这个接口要用于jdk动态代理中。

// Plugin.java
private static Class<?>[] getAllInterfaces(Class<?> type, 
                                           Map<Class<?>, Set<Method>> signatureMap) {
    
    
    // 新建要返回的接口set
    Set<Class<?>> interfaces = new HashSet<Class<?>>();
    // type刚开始应该是执行器类型
    while (type != null) {
    
    
        // 接口得是mybatis的四大对象,我们写了哪个接口才会拦截哪个接口
        for (Class<?> c : type.getInterfaces()) {
    
    //getInterfaces()反射只会找到当前类的接口,不会找到父类的接口。接口继承的接口都不好扫描到的
            if (signatureMap.containsKey(c)) {
    
    
                interfaces.add(c);
            }
        }
        // 我们之前提过执行器是个树找他的父类
        type = type.getSuperclass();
    }
    return interfaces.toArray(new Class<?>[interfaces.size()]);
}

③Proxy.newProxyInstance(

接下来开始创建动态代理,我们有了类加载器,要代理的接口,还需要一个invocationHandler指定代理逻辑。他是通过new Plugin(target, interceptor, signatureMap)创建的。注意上面的逻辑是Plugin.wrap()这个静态方法的执行过程,而他又是个InvocationHandler,那么去找他的invoke()看看逻辑

return Proxy.newProxyInstance(
    type.getClassLoader(),
    interfaces,
    // ④
    new Plugin(target, interceptor, signatureMap));// 这里new了个Plugin类,他肯定实现了InvocationHanlder接口

④new Plugin()

动态代理中只是实现拦截了找到拦截器,具体的拦截器逻辑还是在拦截器中,也就是interceptor.intercept()

Plugin插件类是一个InvocationHandler,但同时他持有动态代理需要的所有逻辑。我们如果以后有需要自己写动态代理,也应该模仿,在创建InvocationHandler的时候构造器传入我们需要的东西,然后在invoke()方法中做判断。

比如插件的时候,如何判断需不需要拦截:根据目标对象的类拿到要拦截的方法,

他的构造函数就是一对一映射到了属性上了而已。看他的invoke方法吧

public class Plugin implements InvocationHandler {
    
    
    private Object target;
    private Interceptor interceptor;
    private Map<Class<?>, Set<Method>> signatureMap;


    public Object invoke(Object proxy, 
                         Method method, // 接口的方法
                         Object[] args) throws Throwable {
    
    
        try {
    
    
            // 获取type指定的方法 // 获取的是接口,因为signatureMap存的时候是通过接口为key的
            Set<Method> methods = signatureMap.get(method.getDeclaringClass());//getDeclaringClass获取方法所在类
            // 如果类也相同,方法也相同,就执行拦截。否则不执行拦截逻辑
            // 如果当前方法被包含在要拦截的方法中
            if (methods != null && methods.contains(method)) {
    
    
                // 调用插件重写的的intercept()方法,
                return interceptor.intercept(new Invocation(target, method, args));//就是利用反射执行一个方法而已
            }
            // 没有拦截器直接执行方法
            return method.invoke(target, args);
        } catch (Exception e) {
    
    
            throw ExceptionUtil.unwrapThrowable(e);
        }
    }

他的invoke()方法首先参数中有个method,代表当前执行到的方法,调用它的getDeclaringClass()方法就知道他所在的类是四大对象中的哪个类,依次来判断本地invoke的四大对象和方法是否匹配,匹配的话执行拦截器的interceptor.intercept(new Invocation(target, method, args));

Inovocation

Inovocation就是包装了要执行的方法,而要执行一个方法我们需要目标对象,实参,哪个方法。然后用反射调用就可以。这个反射操作包装在inovocation.proceed()方法中

④.1 interceptor.intercept()

动态代理中只是实现拦截了找到拦截器,具体的拦截器逻辑还是在拦截器中,也就是interceptor.intercept(),这个逻辑是在自定义插件中写的。

然后接着往下执行拦截器,所以我们自定义插件的时候只需写好注解后重写这个方法即可。我们再看看我们当时重写了什么内容

@Override // Invocation是对原来对象,方法,实参的封装,以便去执行正常的逻辑
public Object intercept(Invocation invocation) throws Throwable {
    
    
    long begin = System.currentTimeMillis();
    try {
    
    
        // 继续执行,通过反射调用目标对象的方法
        return invocation.proceed();
    } finally {
    
    
        long time = System.currentTimeMillis() - begin;
        System.out.println("sql 运行了 :" + time + " ms");
    }
}

new Invocation又把执行器、方法、实参传出来了,Invocation就是他们3个的封装。唯一一个有点用的方法是

public Object proceed() throws InvocationTargetException, IllegalAccessException {
    
    
    return method.invoke(target, args);
}

执行完invocation.proceed();后,执行拦截器的after方法,执行完后返回。

然后退出intercept方法,回到InvocationHandler的invoke(),至此创建好了target的代理对象,退出wrap(),返回代理对象到plugin()方法,他也再返回出去。代理好了执行器

总结:

  • invoke()方法是InvocationHandler的,而InvocationHandler是Proxy.newProxyInstance()的参数,这是jdk动态代理的套路

  • 责任链模式:比如我们的拦截器逻辑已经自定义了,动态代理的过程中会委托给我们的自定义拦截器去处理。

  • 一般使用只需要像helloworld中写好注解、xml、和intercept方法即可,其他都是套路。

一些注意事项:

不要定义过多的插件,代理嵌套过多,执行方法的时候,比较耗性能;
拦截器实现类的intercept方法里最后不要忘了执行invocation.proceed()方法,否则多个拦截器情况下,执行链条会断掉;

插件场景

分页功能:

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

公共字段统一赋值

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

性能监控

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

其它

其实mybatis扩展性还是很强的,基于插件机制,基本上可以控制SQL执行的各个阶段,如执行阶段,参数处理阶段,语法构建阶段,结果集处理阶段,具体可以根据项目业务来实现对应业务逻辑。

方法

img

MetaObject

参考:https://blog.csdn.net/mz4138/article/details/81671319

https://my.oschina.net/u/3737136/blog/1817608

MetaObject用于反射创建对象、反射从对象中获取属性值、反射给对象设置属性值,参数设置和结果封装,用的都是这个MetaObject提供的功能。

MetaObject 主要是对实例化的对象进行赋值和取值用的,其底层也是利用的反射获取实例的 getter 和 setter 方法进行赋值,而这些 getter 和 setter 方法其实都是和 MetaObject 名类似的 MetaClass 通过反射去获取的,所以 MetaClass 主要是用来保存 Class 的一些元信息的

比如我们有下面一个类

public class Animal {
    
    
    private Animal parent;
    private String name;
    // 省略 getter, setter
}

可以通过MetaObject 更改对象的属性

@Test
public void testBean(){
    
     //javaBean
    Animal animal = new Animal();
    MetaObject metaObject = MetaObject.forObject(animal, 
                                                 new DefaultObjectFactory(), 
                                                 new DefaultObjectWrapperFactory(), 
                                                 new DefaultReflectorFactory());
    metaObject.setValue("name","bean");
    System.out.println(animal.getName());//bean
}

@Test
public void testTokenizer(){
    
    
    Animal animal = new Animal();
    animal.setParent(new Animal());
    MetaObject metaObject = MetaObject.forObject(animal, 
                                                 new DefaultObjectFactory(), 
                                                 new DefaultObjectWrapperFactory(), 
                                                 new DefaultReflectorFactory());
    metaObject.setValue("parent.name","tokenizer");
    System.out.println(animal.getParent().getName());// tokenizer
}

Collection

@Test
public void testCollection(){
    
    
    Animal animal = new Animal();
    animal.setName("collection");
    List<Animal> list = new ArrayList<>();
    MetaObject metaObject = MetaObject.forObject(list, 
                                                 new DefaultObjectFactory(), 
                                                 new DefaultObjectWrapperFactory(), 
                                                 new DefaultReflectorFactory());
    metaObject.add(animal);
    System.out.println(list.get(0).getName());
}

Map

@Test
public void testMap(){
    
    
    Animal animal = new Animal();
    animal.setName("map");
    Map<String,Animal> map = new HashMap<>();
    MetaObject metaObject = MetaObject.forObject(map, 
                                                 new DefaultObjectFactory(), 
                                                 new DefaultObjectWrapperFactory(), 
                                                 new DefaultReflectorFactory());
    metaObject.setValue("deer",animal);
    System.out.println(map.get("deer").getName());
}

MetaObject 内部具有5个字段,对外提供的所有方法都是基于这5个字段的。

将这5个字段的方法取一部分对外部提供服务的组合模式

// 原始对象
private final Object originalObject;

// 这几个对象解读:https://my.oschina.net/u/3737136/blog/1817608
private final ObjectWrapper objectWrapper;//对原来对象的封装
private final ObjectFactory objectFactory; // 通过Class对象反射创建对象实例的工厂类,比如创建一个User对象。
private final ObjectWrapperFactory objectWrapperFactory;//对目标对象进行包装,比如可以将Properties对象包装成为一个Map并返回Map对象。
private final ReflectorFactory reflectorFactory;//为了避免多次反射同一个Class对象,ReflectorFactory提供了Class对象的反射结果缓存。

getValue(String name):属性取值。
setValue(String name, Object value):属性赋值。
forObject()

MetaObject 的构造器是私有的,只提供 forObject 创建MetaObject对象.

// MetaObject.java
private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
    
    
    this.originalObject = object;// 原始对象
    this.objectFactory = objectFactory;
    this.objectWrapperFactory = objectWrapperFactory;
    this.reflectorFactory = reflectorFactory;
    // 对原始对象包装
    // 如果参数对象实现了 ObjectWrapper 接口
    if (object instanceof ObjectWrapper) {
    
    
        this.objectWrapper = (ObjectWrapper) object;
        // 如果 ObjectWrapperFactory 对此对象进行加工,调用 getWrapperFor 方法获取加工对象
    } else if (objectWrapperFactory.hasWrapperFor(object)) {
    
    
        this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
        // 如果是 Map 对象,则使用 MapWrapper 作为加工对象
    } else if (object instanceof Map) {
    
    
        this.objectWrapper = new MapWrapper(this, (Map) object);
        // 如果是 Collection 对象,则使用 CollectionWrapper 作为加工对象
    } else if (object instanceof Collection) {
    
    
        this.objectWrapper = new CollectionWrapper(this, (Collection) object);
        // 其他默认使用 BeanWrapper 作为加工对象
    } else {
    
    
        this.objectWrapper = new BeanWrapper(this, object);
    }
}

当参数object为null时,返回 SystemMetaObject.NullObject.class 对象的元数据

public static MetaObject forObject(Object object, 
                                   ObjectFactory objectFactory, 
                                   ObjectWrapperFactory objectWrapperFactory, 
                                   ReflectorFactory reflectorFactory) {
    
    
    if (object == null) {
    
    
        // 记录了Class 相关的getter
        return SystemMetaObject.NULL_META_OBJECT;
    } else {
    
    
        return new MetaObject(object, //originalObject
                              objectFactory, 
                              objectWrapperFactory, 
                              reflectorFactory);
    }
}
BeanWrapper
BeanWrapper
提供了 object.setName,object.setAge 以及类似 object.getParent().setName(),object.getParent().getParent().setName() 的赋值方式
    
MapWrapper
提供了 object.getName,object.getAge 以及类似 object.getParent().getName(),object.getParent().getParent().getName() 的取值方式
    
CollectionWrapper
提供了 集合的add,addAll 方法, setValue,getValue hasSetter 等方法均抛出异常
getValue
// MetaObject.java
public Object getValue(String name) {
    
    
    // 先经过属性表达式解析
    PropertyTokenizer prop = new PropertyTokenizer(name);
    // 如果有下一级属性
    if (prop.hasNext()) {
    
    
        // 构造其 MetaObject 对象
        MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
        // 如果与 SystemMetaObject.NULL_META_OBJECT 相等,即传入的 NullObject.class
        if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
    
    
            return null;
        } else {
    
     // 否则递归调用
            return metaValue.getValue(prop.getChildren());
        }
    } else {
    
    
        return objectWrapper.get(prop);
    }
}
public void setValue(String name, Object value) {
    
    
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
    
    
        MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
        if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
    
    
            // 如果值是 null,就不用初始化下一级元素了
            if (value == null) {
    
    
                // don't instantiate child path if value is null
                return;
                // 否则初始化 metaValue,然后递归调用
            } else {
    
    
                metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
            }
        }
        metaValue.setValue(prop.getChildren(), value);
    } else {
    
    
        // 给指定找到最后一个对象,对其赋值
        objectWrapper.set(prop, value);
    }
}

// 根据传入 name 值构造 MateObject 对象
public MetaObject metaObjectForProperty(String name) {
    
    
    Object value = getValue(name);
    return MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
}

PropertyTokenizer

PropertyTokenizer 是mybatis的属性分词器

提供了对象和集合的一种概念形式, 只是记录了属性有没有子属性

例如: object.parent.name

只是记录了 object 而已,可以通过next方法创建一个对象,返回new PropertyTokenizer(“parent”)
再次调用next 返回new PropertyTokenizer(“name”)

DefaultObjectFactory封装构造器

// 他就是对反射进封装,封装构造器
public class DefaultObjectFactory implements ObjectFactory, Serializable {
    
    

    private static final long serialVersionUID = -8855120656740914948L;

    @Override
    public <T> T create(Class<T> type) {
    
    
        return create(type, null, null);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T create(Class<T> type, 
                        List<Class<?>> constructorArgTypes, // 构造器的类型
                        List<Object> constructorArgs) {
    
     // 构造器传入的参数
        // 要创建的类型
        Class<?> classToCreate = resolveInterface(type);
        // we know types are assignable
        return (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
    }

    @Override
    public void setProperties(Properties properties) {
    
    
        // no props for default
    }

    <T> T instantiateClass(Class<T> type, // 类
                           List<Class<?>> constructorArgTypes, //构造器参数类型
                           List<Object> constructorArgs) {
    
     // 构造器实参
        try {
    
    
            Constructor<T> constructor;
            // 如果构造器类型或实参为空,就调用默认的构造函数
            if (constructorArgTypes == null || constructorArgs == null) {
    
    
                constructor = type.getDeclaredConstructor();
                if (!constructor.isAccessible()) {
    
    
                    constructor.setAccessible(true);
                }
                return constructor.newInstance();
            }
            // 获得指定的构造器
            constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));
            if (!constructor.isAccessible()) {
    
    
                constructor.setAccessible(true);
            }
            return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
        } catch (Exception e) {
    
    
            StringBuilder argTypes = new StringBuilder();
            if (constructorArgTypes != null && !constructorArgTypes.isEmpty()) {
    
    
                for (Class<?> argType : constructorArgTypes) {
    
    
                    argTypes.append(argType.getSimpleName());
                    argTypes.append(",");
                }
                argTypes.deleteCharAt(argTypes.length() - 1); // remove trailing ,
            }
            StringBuilder argValues = new StringBuilder();
            if (constructorArgs != null && !constructorArgs.isEmpty()) {
    
    
                for (Object argValue : constructorArgs) {
    
    
                    argValues.append(String.valueOf(argValue));
                    argValues.append(",");
                }
                argValues.deleteCharAt(argValues.length() - 1); // remove trailing ,
            }
            throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
        }
    }

    protected Class<?> resolveInterface(Class<?> type) {
    
    
        // 要创建的类型
        Class<?> classToCreate;
        if (type == List.class || type == Collection.class || type == Iterable.class) {
    
    
            classToCreate = ArrayList.class;
        } else if (type == Map.class) {
    
    
            classToCreate = HashMap.class;
        } else if (type == SortedSet.class) {
    
     // issue #510 Collections Support
            classToCreate = TreeSet.class;
        } else if (type == Set.class) {
    
    
            classToCreate = HashSet.class;
        } else {
    
    
            classToCreate = type;
        }
        return classToCreate;
    }

    @Override
    public <T> boolean isCollection(Class<T> type) {
    
    
        return Collection.class.isAssignableFrom(type);
    }

}

PageHelper源码分析

https://my.oschina.net/zudajun/blog/745232

用法:

PageHelper.startPage(1, 3);
List<Blog> blogs = blogMapper.selectBlogById2(blog);
PageInfo page = new PageInfo(blogs, 3);
public class Page<E> extends ArrayList<E> {
    
    
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
	<property name="dialect" value="mysql" />
	<!-- 该参数默认为false -->
	<!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
	<!-- 和startPage中的pageNum效果一样 -->
	<property name="offsetAsPageNum" value="false" />
	<!-- 该参数默认为false -->
	<!-- 设置为true时,使用RowBounds分页会进行count查询 -->
	<property name="rowBoundsWithCount" value="true" />

	<!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
	<!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型) <property name="pageSizeZero" value="true"/> -->

	<!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
	<!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
	<!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
	<property name="reasonable" value="true" />
	<!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->
	<!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 -->
	<!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值 -->
	<!-- 不理解该含义的前提下,不要随便复制该配置 <property name="params" value="pageNum=start;pageSize=limit;"/> -->
</plugin>

PageHelper类作为我们的插件类,所以要手动注入到mybatis环境中。Page类是包装结果的类,其中也有totals

Page

PageHelper的两种使用方式

第一种、直接通过RowBounds参数完成分页查询 。

List<Student> list = studentMapper.find(new RowBounds(0, 10));
Page page = (Page) list;
// RowBounds方式的原理就是他在里面会拿到offset和limit拼接成字符串,然后把RowBounds值删除

第二种、PageHelper.startPage()静态方法

//获取第1页,10条内容,默认查询总数count
PageHelper.startPage(1, 10);
//紧跟着的第一个select方法会被分页
List<Country> list = studentMapper.find();
// 返回结果list,已经是Page对象,Page对象是一个ArrayList。
Page page = ((Page) list;

注:返回结果list,已经是Page对象,Page对象是一个ArrayList。

原理:使用ThreadLocal来传递和保存Page对象,每次查询,都需要单独设置PageHelper.startPage()方法。

public class SqlUtil implements Constant {
    
    
    private static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();
}
PageHelper
@Intercepts(@Signature(type = Executor.class, method = "query", args = {
    
    MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class PageHelper implements Interceptor {
    
    
    //sql工具类
    private SqlUtil sqlUtil;
    //属性参数信息
    private Properties properties;
    //配置对象方式
    private SqlUtilConfig sqlUtilConfig;
    //自动获取dialect,如果没有setProperties或setSqlUtilConfig,也可以正常进行
    private boolean autoDialect = true;
    //运行时自动获取dialect
    private boolean autoRuntimeDialect;
    //多数据源时,获取jdbcurl后是否关闭数据源
    private boolean closeConn = true;
    //缓存
    private Map<String, SqlUtil> urlSqlUtilMap = new ConcurrentHashMap<String, SqlUtil>();
    private ReentrantLock lock = new ReentrantLock();
// ...
}

SqlUtil:数据库类型专用sql工具类,一个数据库url对应一个SqlUtil实例,SqlUtil内有一个Parser对象,如果是mysql,它是MysqlParser,如果是oracle,它是OracleParser,这个Parser对象是SqlUtil不同实例的主要存在价值。执行count查询、设置Parser对象、执行分页查询、保存Page分页对象等功能,均由SqlUtil来完成。

SqlUtilConfig:Spring Boot中使用,忽略。

autoRuntimeDialect:多个数据源切换时,比如mysql和oracle数据源同时存在,就不能简单指定dialect,这个时候就需要运行时自动检测当前的dialect。

Map<String, SqlUtil> urlSqlUtilMap:它就用来缓存autoRuntimeDialect自动检测结果的,key是数据库的url,value是SqlUtil。由于这种自动检测只需要执行1次,所以做了缓存。

ReentrantLock lock:这个lock对象是比较有意思的现象,urlSqlUtilMap明明是一个同步ConcurrentHashMap,又搞了一个lock出来同步ConcurrentHashMap做什么呢?是否是画蛇添足?在《Java并发编程实战》一书中有详细论述,简单的说,ConcurrentHashMap可以保证put或者remove方法一定是线程安全的,但它不能保证put、get、remove的组合操作是线程安全的,为了保证组合操作也是线程安全的,所以使用了lock。
PageSqlSource
public abstract class PageSqlSource implements SqlSource {
    
    
public class PageStaticSqlSource extends PageSqlSource {
    
    
    
getDefaultBoundSql:获取原始的未经改造的BoundSql。

getCountBoundSql:不需要写count查询,插件根据分页查询sql,智能的为你生成的count查询BoundSql。

getPageBoundSql:获取分页查询的BoundSql。

举例:

DefaultBoundSql:select  stud_id as studId , name, email, dob, phone from students

CountBoundSql:select  count(0) from students --由PageHelper智能完成

PageBoundSql:select  stud_id as studId , name, email, dob, phone from students limit ?, ?

参考:https://my.oschina.net/zudajun/blog/745232

关键源码语句

sqlBuilder.append(" limit ?,?");

args[2] = RowBounds.DEFAULT;

猜你喜欢

转载自blog.csdn.net/hancoder/article/details/110914534
今日推荐