第五章 MyBatis插件开发笔记

本文深入分析了Spring如何集成MyBatis,包括SqlSessionFactoryBean、MapperFactoryBean和MapperScannerConfigurer的源码解析。接着探讨了MyBatis插件开发,介绍了插件的原理、开发快速入门及源码中的责任链模式。最后,详细讲解了Mybatis分页插件PageHelper的使用、源码分析及注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、spring集成Mybatis的原理分析

分析源码之前也需要源码下载并安装到本地仓库和开发工具中,方便给代码添加注释;安装过程和mybatis源码的安装过程是一样的,这里就不再重复描述了;下载地址:https://github.com/mybatis/spring

1、SqlSessionFactoryBean源码分析

applicationContext.xml配置mybatis相关代码:

<!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
   <property name="dataSource" ref="dataSource" />
   <property name="typeAliasesPackage" value="com.chj.mybatis.entity" />
   <property name="mapperLocations" value="classpath:sqlmapper/*.xml" />
</bean>

对应的源码入口:

@Override
//在spring容器中创建全局唯一的sqlSessionFactory
public void afterPropertiesSet() throws Exception {
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
            "Property 'configuration' and 'configLocation' can not specified with together");
    this.sqlSessionFactory = buildSqlSessionFactory();
}

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    Configuration configuration;
    XMLConfigBuilder xmlConfigBuilder = null;

    ......

//扫描指定的包typeAliasesPackage,注册别名

if (hasLength(this.typeAliasesPackage)) {
        String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
            ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        for (String packageToScan : typeAliasPackageArray) {
            configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
            }
        }
    }

......
//为typeAliases指定的类注册别名
if (!isEmpty(this.typeAliases)) {
    for (Class<?> typeAlias : this.typeAliases) {
        configuration.getTypeAliasRegistry().registerAlias(typeAlias);
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Registered type alias: '" + typeAlias + "'");
        }
    }
}

//根据mapperLocations的配置,处理映射配置文件以及相应的mapper接口
if (!isEmpty(this.mapperLocations)) {
    for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
            continue;
        }
        try {
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                    configuration, mapperLocation.toString(), configuration.getSqlFragments());
            xmlMapperBuilder.parse();
        } catch (Exception e) {
            throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
            ErrorContext.instance().reset();
        }
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
        }
    }
} else {
    if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
    }
}
//最终使用sqlSessionFactoryBuilder创建sqlSessionFactory
return this.sqlSessionFactoryBuilder.build(configuration);

这里面用到了xmlConfigBuilder与xmlMapperBuilder两个builder来解析配置文件

private void parseConfiguration(XNode root) {
    try {
        this.propertiesElement(root.evalNode("properties"));
        Properties settings = this.settingsAsProperties(root.evalNode("settings"));
        this.loadCustomVfs(settings);
        this.typeAliasesElement(root.evalNode("typeAliases"));
        this.pluginElement(root.evalNode("plugins"));
        this.objectFactoryElement(root.evalNode("objectFactory"));
        this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
        this.settingsElement(settings);
        this.environmentsElement(root.evalNode("environments"));
        this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        this.typeHandlerElement(root.evalNode("typeHandlers"));
        this.mapperElement(root.evalNode("mappers"));
    } catch (Exception var3) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
    }
}

xmlMapperBuilder解析代码如下:

private void configurationElement(XNode context) {
    try {
        String namespace = context.getStringAttribute("namespace");
        if (namespace != null && !namespace.equals("")) {
            this.builderAssistant.setCurrentNamespace(namespace);
            this.cacheRefElement(context.evalNode("cache-ref"));
            this.cacheElement(context.evalNode("cache"));
            this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
            this.resultMapElements(context.evalNodes("/mapper/resultMap"));
            this.sqlElement(context.evalNodes("/mapper/sql"));
            this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
        } else {
            throw new BuilderException("Mapper's namespace cannot be empty");
        }
    } catch (Exception var3) {
        throw new BuilderException("Error parsing Mapper XML. The XML location is '" + this.resource + "'. Cause: " + var3, var3);
    }
}

2、MapperFactoryBean源码分析

按照spring注入配置mapper接口方式实现:

<bean id="tUserMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
   <property name="mapperInterface" value="com.chj.mybatis.mapper.TUserMapper"></property>
   <property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>

对应的源码实现如下:

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
    private Class<T> mapperInterface;
    private boolean addToConfig = true;

@Override
//MapperFactoryBean在容器初始化时,要确保mapper接口被注册到mapperRegistry
protected void checkDaoConfig() {
    super.checkDaoConfig();
    Configuration configuration = getSqlSession().getConfiguration();//通过SqlSession从容器中拿到configuration
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
        try {
            // 如果mapperRegistry中不包含当前接口的动态代理工厂,则添加一个
            configuration.addMapper(this.mapperInterface);
        } catch (Exception e) {
            logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
            throw new IllegalArgumentException(e);
        } finally {
            ErrorContext.instance().reset();
        }
    }
}
// 通过在容器中的mapperRegistry,返回当前mapper接口的动态代理
@Override
public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
}

Configuration的addMapper方法:

public <T> void addMapper(Class<T> type) {
    this.mapperRegistry.addMapper(type);
}

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
        if (this.hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
            this.knownMappers.put(type, new MapperProxyFactory(type));
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                this.knownMappers.remove(type);
            }
        }
    }
}

MapperFactoryBean.getObject()方法的具体实现:

DefaultSqlSession.getMapper();

public <T> T getMapper(Class<T> type) {
    return this.configuration.getMapper(type, this);
}

Configuration.getMapper();

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return this.mapperRegistry.getMapper(type, sqlSession);
}

public class MapperRegistry {
    private final Configuration config;
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
    public MapperRegistry(Configuration config) {
        this.config = config;
    }
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }

3、MapperScannerConfigurer源码分析

DAO接口所在包名,Spring会自动查找其下的类,对应的配置入口如下:

<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
   <property name="basePackage" value="com.chj.mybatis.mapper" />
</bean>

对应的源码分析如下:

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
    private String basePackage;//用于指定要扫描的包
    private boolean addToConfig = true;
    private SqlSessionFactory sqlSessionFactory;
    private SqlSessionTemplate sqlSessionTemplate;
    private String sqlSessionFactoryBeanName;
    private String sqlSessionTemplateBeanName;
    private Class<? extends Annotation> annotationClass;// mapper接口上有指定的annotation才会被扫描
    private Class<?> markerInterface;// mapper接口继承与指定的接口才会被扫描
    private ApplicationContext applicationContext;//容器上下文
    private String beanName;
    private boolean processPropertyPlaceHolders;
    private BeanNameGenerator nameGenerator;

    ......

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {//占位符处理
        processPropertyPlaceHolders();
    }
    //实例化ClassPathMapperScanner,并对scanner相关属性进行配置
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.registerFilters();//根据上述配置,生成过滤器,只扫描合条件的class
    //扫描指定的包以及其子包
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

ClassPathBeanDefinitionScanner

public int scan(String... basePackages) {
    int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
    this.doScan(basePackages);
    if (this.includeAnnotationConfig) {
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }
    return this.registry.getBeanDefinitionCount() - beanCountAtScanStart;
}

二、MyBatis插件开发与原理

1、插件概述

插件是用来改变或者扩展mybatis的原有的功能,mybaits的插件就是通过继承Interceptor拦截器实现的;在没有完全理解插件之前禁止使用插件对mybaits进行扩展,又可能会导致严重的问题;

mybatis中能使用插件进行拦截的接口和方法如下:

Executor(update、query 、 flushStatment 、 commit 、 rollback 、 getTransaction 、 close 、 isClose)

StatementHandler(prepare 、 paramterize 、 batch 、 update 、 query)

ParameterHandler( getParameterObject 、 setParameters )

ResultSetHandler( handleResultSets 、 handleCursorResultSets 、 handleOutputParameters )

这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看MyBatis的发行包中的源代码。假设你想做的不仅仅是方法的调用,那么你应该很好的了解正在重写的方法的行为。因为如果在视图修改或重写已有方法的行为的时候,你很有可能在破坏MyBatis的核心模块。这些都是更低层的类和方法,所以使用插件的时候要特别担心。

通过MyBatis提供强大的机制,使用插件是非常简单的,只需要实现Interceptor接口,并指定想要拦截的方法签名即可。

2、插件开发快速入门

定义一个阈值,当查询操作运行时间超过这个阈值记录日志供运维人员定位慢查询,插件实现步骤:

  • 实现Interceptor接口方法
  • 确定拦截的签名 @Intercepts({@Signature()})
  • 在配置文件中配置插件
  • 运行测试用例

代码示例:

@Intercepts({
   @Signature(type=StatementHandler.class,method="query",args={Statement.class, ResultHandler.class}),
   @Signature(type=StatementHandler.class,method="query",args={MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})
})
public class ThresholdInterceptor implements Interceptor {
    private long threshold;
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long begin = System.currentTimeMillis();
        Object ret = invocation.proceed();
        long end=System.currentTimeMillis();
        long runTime = end - begin;
        if(runTime>=threshold){
            Object[] args = invocation.getArgs();
            Statement stat = (Statement) args[0];
           // 反射工具类
           MetaObject metaObjectStat = SystemMetaObject.forObject(stat);
           // 通过动态代理的变量‘h’获取PreparedStatementLogger(变量h 就是增强的InvocationHandler)
           // 实际上获取的就是PreparedStatementLogger extends BaseJdbcLogger implements InvocationHandler

            PreparedStatementLogger statementLogger = (PreparedStatementLogger)metaObjectStat.getValue("h");
            Statement statement = statementLogger.getPreparedStatement();
        System.out.println("sql语句:“"+statement.toString()+"”执行时间为:"+runTime+"毫秒,已经超过阈值!");
        }
        return ret;
    }
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
    @Override
    public void setProperties(Properties properties) {
        this.threshold = Long.valueOf(properties.getProperty("threshold"));
    }
}

PreparedStatement proxy to add logging

public final class PreparedStatementLogger extends BaseJdbcLogger implements InvocationHandler {
  private final PreparedStatement statement;
  private PreparedStatementLogger(PreparedStatement stmt, Log statementLog, int queryStack) {
    super(statementLog, queryStack);
    this.statement = stmt;
  }
  @Override
  public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, params);
      }
      if (EXECUTE_METHODS.contains(method.getName())) {
        if (isDebugEnabled()) {
          debug("Parameters: " + getParameterValueString(), true);
        }
        clearColumnInfo();
        if ("executeQuery".equals(method.getName())) {
          ResultSet rs = (ResultSet) method.invoke(statement, params);
          return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
        } else {
          return method.invoke(statement, params);
        }
      } else if (SET_METHODS.contains(method.getName())) {
        if ("setNull".equals(method.getName())) {
          setColumn(params[0], null);
        } else {
          setColumn(params[0], params[1]);
        }
        return method.invoke(statement, params);
      } else if ("getResultSet".equals(method.getName())) {
        ResultSet rs = (ResultSet) method.invoke(statement, params);
        return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
      } else if ("getUpdateCount".equals(method.getName())) {
        int updateCount = (Integer) method.invoke(statement, params);
        if (updateCount != -1) {
          debug("   Updates: " + updateCount, false);
        }
        return updateCount;
      } else {
        return method.invoke(statement, params);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

在mybatis-config.xml核心配置文件plugins配置中添加拦截器配置代码:

<plugin interceptor="com.chj.mybatis.Interceptors.ThresholdInterceptor">
    <property name="threshold" value="10"/>
</plugin>

代码运行结果:

sql语句:“com.mysql.jdbc.JDBC4PreparedStatement@6dc17b83:

Select id, user_name, real_name, sex, mobile, email, note, position_id from t_user where id = 2”执行时间为:46毫秒,已经超过阈值!

TUser [id=2, userName=james, realName=陈大雷, sex=1, mobile=18677885200, email=james@qq.com, note=james的备注, position=com.chj.mybatis.entity.TPosition@4cc451f2, jobs=null, roles=null]

进行代码调试结果如下:

 

上面的插件将会拦截Executor实例中所有的“query”方法调用,这里的Executor是负责执行底层映射语句的内部对象。

 

 

覆盖配置类:除了用插件来修改MyBatis核心行为之外,还可以通过完全覆盖配置类来达到目的。只需继承后覆盖其中的每个方法,再把它传递到sqlSessionFactoryBuilder.build(myConfig)方法即可。再次重申,这可能会严重影响Mybatis的行为,务请慎之又慎!

3、源码分析之责任链模式

责任链模式:就是把一件工作分别经过链上的各个节点,让这些节点依次处理这个工作;和装饰器模式不同,每个节点都知道后继者是谁;适合为完成同一个请求需要多个处理类的场景;

 

3.1、要素分析

Handler:定义了一个处理请求的标准接口;

ConcreteHandler:具体的处理者,处理它负责的部分,根据业务可以结束处理流程,也可以将请求转发给它的后继者;

client :发送者,发起请求的客户端;

3.2、责任链模式优点:

降低耦合度。它将请求的发送者和接收者解耦。

简化了对象。使得对象不需要知道链的结构。

增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。

增加新的请求处理类很方便。

3.3、mybatis插件模块源码分析

插件的初始化  (XMLConfigBuilder.pluginElement)

插件的加载     (Configuration.new*方法,四大对象的创建)

插件的调用    (Plugin. wrap、 Plugin. invoke)

mybatis插件理解:Executor 拦截器高级教程QueryInterceptor 规范

https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/Interceptor.md

三、Mybatis分页插件PageHelper

1、分页插件的使用;

1)pom文件引入分页插件

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.4</version>
</dependency>

2)mybatis-config.xml配置分页插件

<plugin interceptor="com.github.pagehelper.PageInterceptor">
    <property name="pageSizeZero" value="true" />
</plugin>

3)查询代码

Page<TUser> startPage = PageHelper.startPage(2, 3); // 
List<TUser> list2 = mapper.selectByEmailAndSex2(email, sex);

中文文档:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/README_zh.md

使用手册:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md

2、分页源码如下所示:

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

})
public class PageInterceptor implements Interceptor {
    //缓存count查询的ms
    protected Cache<String, MappedStatement> msCountMap = null;
    private Dialect dialect;
    private String default_dialect_class = "com.github.pagehelper.PageHelper";
    private Field additionalParametersField;
    private String countSuffix = "_COUNT";
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            Object[] args = invocation.getArgs();
            MappedStatement ms = (MappedStatement) args[0];
            Object parameter = args[1];
            RowBounds rowBounds = (RowBounds) args[2];
            ResultHandler resultHandler = (ResultHandler) args[3];
            Executor executor = (Executor) invocation.getTarget();
            CacheKey cacheKey;
            BoundSql boundSql;
            //由于逻辑关系,只会进入一次
            if(args.length == 4){//4 个参数时
                boundSql = ms.getBoundSql(parameter);
                cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
            } else { //6 个参数时
                cacheKey = (CacheKey) args[4];
                boundSql = (BoundSql) args[5];
            }
            List resultList;
            //调用方法判断是否需要进行分页,如果不需要,直接返回结果
            if (!dialect.skip(ms, parameter, rowBounds)) {
                //反射获取动态参数
                String msId = ms.getId();
                Configuration configuration = ms.getConfiguration();
                Map<String, Object> additionalParameters = (Map<String, Object>) additionalParametersField.get(boundSql);
                //判断是否需要进行 count 查询
                if (dialect.beforeCount(ms, parameter, rowBounds)) {
                    String countMsId = msId + countSuffix;
                    Long count;
                    //先判断是否存在手写的 count 查询
                    MappedStatement countMs = getExistedMappedStatement(configuration, countMsId);
                    if(countMs != null){
                        count = executeManualCount(executor, countMs, parameter, boundSql, resultHandler);
                    } else {
                        countMs = msCountMap.get(countMsId);
                        //自动创建
                        if (countMs == null) {
                            //根据当前的 ms 创建一个返回值为 Long 类型的 ms
                            countMs = MSUtils.newCountMappedStatement(ms, countMsId);
                            msCountMap.put(countMsId, countMs);
                        }
                        count = executeAutoCount(executor, countMs, parameter, boundSql, rowBounds, resultHandler);
                    }
                    //处理查询总数
                    //返回 true 时继续分页查询,false 时直接返回
                    if (!dialect.afterCount(count, parameter, rowBounds)) {
                        //当查询总数为 0 时,直接返回空的结果
                        return dialect.afterPage(new ArrayList(), parameter, rowBounds);
                    }
                }
                //判断是否需要进行分页查询
                if (dialect.beforePage(ms, parameter, rowBounds)) {
                    //生成分页的缓存 key
                    CacheKey pageKey = cacheKey;
                    //处理参数对象
                    parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey);
                    //调用方言获取分页 sql
                    String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);
                    BoundSql pageBoundSql = new BoundSql(configuration, pageSql, boundSql.getParameterMappings(), parameter);
                    //设置动态参数
                    for (String key : additionalParameters.keySet()) {
                        pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
                    }
                    //执行分页查询
                    resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
                } else {
                    //不执行分页的情况下,也不执行内存分页
                    resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
                }
            } else {
                //rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
                resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
            }
            return dialect.afterPage(resultList, parameter, rowBounds);
        } finally {
            dialect.afterAll();
        }
    }

执行executor.query()方法开始就是正常的数据库查询操作

//处理参数对象
dialect.processParameterObject(ms, parameter, boundSql, pageKey);

PageHelper.processParameterObject()方法:

@Override
public Object processParameterObject(MappedStatement ms, Object parameterObject, BoundSql boundSql, CacheKey pageKey) {
    return autoDialect.getDelegate().processParameterObject(ms, parameterObject, boundSql, pageKey);
}

AbstractHelperDialect

@Override
public Object processParameterObject(MappedStatement ms, Object parameterObject, BoundSql boundSql, CacheKey pageKey) {
    //处理参数
    Page page = getLocalPage();
    //如果只是 order by 就不必处理参数
    if (page.isOrderByOnly()) {
        return parameterObject;
    }
    Map<String, Object> paramMap = null;
    if (parameterObject == null) {
        paramMap = new HashMap<String, Object>();
    } else if (parameterObject instanceof Map) {
        //解决不可变Map的情况
        paramMap = new HashMap<String, Object>();
        paramMap.putAll((Map) parameterObject);
    } else {
        paramMap = new HashMap<String, Object>();
        //动态sql时的判断条件不会出现在ParameterMapping中,但是必须有,所以这里需要收集所有的getter属性
        //TypeHandlerRegistry可以直接处理的会作为一个直接使用的对象进行处理
        boolean hasTypeHandler = ms.getConfiguration().getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass());
        MetaObject metaObject = MetaObjectUtil.forObject(parameterObject);
        //需要针对注解形式的MyProviderSqlSource保存原值
        if (!hasTypeHandler) {
            for (String name : metaObject.getGetterNames()) {
                paramMap.put(name, metaObject.getValue(name));
            }
        }
        //下面这段方法,主要解决一个常见类型的参数时的问题
        if (boundSql.getParameterMappings() != null && boundSql.getParameterMappings().size() > 0) {
            for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
                String name = parameterMapping.getProperty();
                if (!name.equals(PAGEPARAMETER_FIRST)  && !name.equals(PAGEPARAMETER_SECOND)
                        && paramMap.get(name) == null) {
                    if (hasTypeHandler || parameterMapping.getJavaType().equals(parameterObject.getClass())) {
                        paramMap.put(name, parameterObject);
                        break;
                    }
                }
            }
        }
    }
    return processPageParameter(ms, paramMap, page, boundSql, pageKey);
}

 

MySqlDialect源码实现如下:

public class MySqlDialect extends AbstractHelperDialect {
    @Override
    public Object processPageParameter(MappedStatement ms, Map<String, Object> paramMap, Page page, BoundSql boundSql, CacheKey pageKey) {
        paramMap.put(PAGEPARAMETER_FIRST, page.getStartRow());
        paramMap.put(PAGEPARAMETER_SECOND, page.getPageSize());
        //处理pageKey
        pageKey.update(page.getStartRow());
        pageKey.update(page.getPageSize());
        //处理参数配置
        if (boundSql.getParameterMappings() != null) {
            List<ParameterMapping> newParameterMappings = new ArrayList<ParameterMapping>();
            if (boundSql != null && boundSql.getParameterMappings() != null) {
                newParameterMappings.addAll(boundSql.getParameterMappings());
            }
            if (page.getStartRow() == 0) {
                newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), PAGEPARAMETER_SECOND, Integer.class).build());
            } else {
                newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), PAGEPARAMETER_FIRST, Integer.class).build());
                newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), PAGEPARAMETER_SECOND, Integer.class).build());
            }
            MetaObject metaObject = MetaObjectUtil.forObject(boundSql);
            metaObject.setValue("parameterMappings", newParameterMappings);
        }
        return paramMap;
    }
    @Override
    public String getPageSql(String sql, Page page, CacheKey pageKey) {
        StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
        sqlBuilder.append(sql);
        if (page.getStartRow() == 0) {
            sqlBuilder.append(" LIMIT ? ");
        } else {
            sqlBuilder.append(" LIMIT ?, ? ");
        }
        pageKey.update(page.getPageSize());
        return sqlBuilder.toString();
    }
}

分页插件初始化入口,在mybatis初始化节点解析配置文件开始:

// 1.读取mybatis配置文件创SqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    build(parser.parse());

        parseConfiguration(parser.evalNode("/configuration"));

             pluginElement(root.evalNode("plugins"));

private void pluginElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      String interceptor = child.getStringAttribute("interceptor");
      Properties properties = child.getChildrenAsProperties();
      Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
      interceptorInstance.setProperties(properties);
      configuration.addInterceptor(interceptorInstance);
    }
  }
}

 

关于分页查询的初始化:

@Override
public Object plugin(Object target) {
//TODO Spring bean 方式配置时,如果没有配置属性就不会执行下面的 setProperties 方法,就不会初始化,因此考虑在这个方法中做一次判断和初始化,https://github.com/pagehelper/Mybatis-PageHelper/issues/26
    return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
    //缓存 count ms
    msCountMap = CacheFactory.createCache(properties.getProperty("msCountCache"), "ms", properties);
    String dialectClass = properties.getProperty("dialect");
    if (StringUtil.isEmpty(dialectClass)) {
        dialectClass = default_dialect_class;
    }
    try {
        Class<?> aClass = Class.forName(dialectClass);
        dialect = (Dialect) aClass.newInstance();
    } catch (Exception e) {
        throw new PageException(e);
    }
    dialect.setProperties(properties);
    String countSuffix = properties.getProperty("countSuffix");
    if (StringUtil.isNotEmpty(countSuffix)) {
        this.countSuffix = countSuffix;
    }
    try {
        //反射获取 BoundSql 中的 additionalParameters 属性
        additionalParametersField = BoundSql.class.getDeclaredField("additionalParameters");
        additionalParametersField.setAccessible(true);
    } catch (NoSuchFieldException e) {
        throw new PageException(e);
    }
}

3、分页插件的注意事项;

PageHelper.startPage方法重要提示

只有紧跟在PageHelper.startPage方法后的第一个Mybatis的查询(Select)方法会被分页。

请不要配置多个分页插件

请不要在系统中配置多个分页插件(使用Spring时,mybatis-config.xml和Spring<bean>配置方式,请选择其中一种,不要同时配置多个分页插件)!

分页插件不支持带有for update语句的分页

对于带有for update的sql,会抛出运行时异常,对于这样的sql建议手动分页,毕竟这样的sql需要重视。

分页插件不支持嵌套结果映射

由于嵌套结果方式会导致结果集被折叠,因此分页查询的结果在折叠后总数会减少,所以无法保证分页结果数量正确。
注意事项:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/Important.md

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值