mybatis源码整体结构分析与plus的扩展(结构图完成)

​​​​​​mybatis – MyBatis 3 | Getting started        在介绍前,先想想mybatis的用法,通常是写mapper接口与对应的xml,很明显这些接口要被动态代理,实现将请求参数转换成可执行的sql语句,使用sqlSession来进行操作,最后返回结果。这样在spring使用的话,应该是有注册beanDefination,里面是一个FactoryBean,由它的getObject()产生真正的接口实现类,这一点上,与dubbo的接口可以变成一个远程调用差不多机制。

        下图就是最后根据分析,整理的结构索引图。分析完代码其实也很容易忘记,看图方便记忆。

  对用户写的mapper类,典型的处理过程:

  1. ClassPathMapperScanner扫描包下的mapper接口类,产生beanDefination。
  2. beanDefination中设置为MapperFactoryBean,可用getObject()生成接口的实现类。
  3. getObject()中,又使用sessionFactory产生的sessionTemplate创建,创建时委托configuration创建的同时,把自己当this传进去。
  4. Configuration中又找到MapperRegistry后,从其map中找到mapper对应的MapperProxyFactory(MapperFactoryBean初始化时放入map中的)。
  5. MapperProxyFactory动态生成是,会用MapperProxy 这个invocationHandler来动态生成。
  6. MapperProxy的invoker方法中,又会用plainMethodInvoker和新的参数MapperMethod(包括了用户method与sessionTemplatemethod的关系)来处理。
  7. MapperMethod因为有了对应关系,最后还是前面this时放入的sessionTemplate处理。
  8. sessionTemplate内部又用动态代理生成sqlSessionProxy,委托它处理。
  9. sqlSessionProxy的invocationHandler是SqlSessionInterceptor,它又会产生一个DefaultSqlSession来处理,并用configuration产生一个executor给它。它会先从configuration中获取新的MappedStatement,再用executor使用MappedStatement处理。
  10. executor执行时,又从configuration获取一个新的StatementHandler来处理MappedStatement中的参数对象。
  11. StatementHandler内有生成的parameterHandler,resultSetHandler,typeHandlerRegistry等,可以处理请求参数,可以处理返回值,可以对参数中的属性,按类型进行转换。
  12. StatementHandler会用连接DB后生成的java.sql.Statement来处理转换后产生的真正的SQL语句,最后还会用resultSetHandler处理返回值。

      下面详细介绍分析的过程,我们还是先看一下官方的介绍,找到非spring环境中的通常使用方法,来构建出mybatis的运行时关系图,以及创建运行时关系的过程。

一、mybatis-3.5.9.jar

1、官方说明与初步分析
mybatis – MyBatis 3 | Getting started

Every MyBatis application centers around an instance of SqlSessionFactory. A SqlSessionFactory instance can be acquired by using the SqlSessionFactoryBuilder. SqlSessionFactoryBuilder can build a SqlSessionFactory instance from an XML configuration file, or from a custom prepared instance of the Configuration class.

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory =
  new SqlSessionFactoryBuilder().build(inputStream);
.........
//使用方式一:
try (SqlSession session = sqlSessionFactory.openSession()) {
  Blog blog = session.selectOne(
    "org.mybatis.example.BlogMapper.selectBlog", 101);
}
//使用方式二:
try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);//interface
  Blog blog = mapper.selectBlog(101);
}

SqlSessionFactory就是核心,而且简单的看,就是从配置文件来构建这样一个核心类。怎么与前面的接口设想有点不一样了,当然sqlSesssion很重要,接口的实现都要使用它,所以它的工厂也是入口操作类也正常。从两种使用方式来看,可以设想mapper 就是那个接口实现类。

不如直接查一下DefaultSqlSession的getMapper怎么用,果然是把session传进去生成接口的实现类,并且是给这个实现类用。后面分别向下介绍session的操作,向上介绍接口的处理,最后理出一个关系出来。

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

2、Session的功能

 看看上面使用方法:session.selectOne(),所使用的selectList方法。几个参数很明确,大概可以分析出执行过程:

  • 一个从配置中获取MappedStatement,这些都是预先解析好的configuration的数据。
  • StatementHandler是为每一个sql操作而产生的处理类对象,stmt也是如此。而configuration,mappedStatement,executor,connection,transaction这些都可以看做是单例对象。
  • SimpleExecutor中的StatementHandler对象本来想的不需要在doQuery()这里出现吧? 产生的StatementHandler后,它又当参数生成stmt,stmt之后又当参数给StatementHandler处理用,感觉关系有点不清晰。
  • 私以为类关系最好不要循环,但在prepareStatement()与getConnection()中,都用到了SimpleExecutor的transaction对象,所以不能下沉到StatementHandler中处理,当然我写的话,可能会把transaction传递下去,在StatementHandler中处理。也许是因为Executor或者其它接口有多个实现类,为了统一代码的结构而这么设计吧,暂不深入。

上述分析中要分清单例类(包括配置数据对象)与每一次处理产生的类/对象,最后理出一个运行关系图。

//DefaultSession.java
  private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

//SimpleExecutor.java
  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

//SimpleStatementHandler.java
  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    statement.execute(sql);
    return resultSetHandler.handleResultSets(statement);
  }

3. MappedStatement

从上面可以看出,配置中的MappedStatement是一个重要的对象,可以认为是配置对象,从名字可以看到是mapped的声明,我们知道mapper有很多种方式,包括xml,包括接口,最终应该都被特定的parse类,解析成一个个MappedStatement,存起来来用吧。

这个过程应该是启动后的初始化中完成,之后可以正常处理用户过来的请求。

再回看一下官方的 Building SqlSessionFactory without XML,有助于我们理解类关系。

DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory =
  new JdbcTransactionFactory();
Environment environment =
  new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory =
  new SqlSessionFactoryBuilder().build(configuration);

 这里就比较清楚了,需要用配置configuration来产生SqlSessionFactory。而configuration中包括了:dataSource,TransactionFactory,以及BlogMapper.class这些接口类产生的mapper。所以前面说这些都是单例对象,包括解释用户接口产生的。我们猜测这个BlogMapper.class应该会产生MappedStatement与接口实现类。实现类中用方法名字与参数,使用sqlSession时,会找到MappedStatement,这样进行数据库操作。

从以下代码跟踪发现,会从XML配置文件中,产生MappedStatement。

//configuration.java  
protected void buildAllStatements() {
...
incompleteStatements.removeIf(x -> {
          x.parseStatementNode();
          return true;
        });
...
//这里还有从接口解析出statement,后面说明。
}

public void addIncompleteStatement(XMLStatementBuilder incompleteStatement) {
    incompleteStatements.add(incompleteStatement);
  }

//XMLMapperBuilder.java
  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

//XMLStatementBuilder.java 可以发现产生MappedStatement对象了。
  public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");
...
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

那么,如何从BlogMapper.class这样的接口中产生MappedStatement呢?我们从前面说的官方without XML的使用示例configuration.getMapper()开始。

  • 解析的时候,按接口放入一个MapperProxyFactory。
  • 使用的时候,用MapperProxyFactory产生实例,这时候传入sqlSession进去。
  • sqlSession是在MapperMethod的executor中使用的,sqlSession会从configuration中得到MappedStatement。configuration.getMappedStatement(statement);
  • 但是前面没有看到从接口产生MappedStatement的代码啊?只看了从xml产生地代码。实际上在buildAllStatements();中,之前只分析了一种情况,还有另两种情况,其中一个是incompleteMethods中解析。

上述前两点,正好说明与前面的猜测一致。接口是要产生一个实现类,这个实现类用sqlSession访问数据库。

//configuratin.java  
public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }

//MapperRegistry.java
  public <T> void addMapper(Class<T> type) {
    ...
      try {
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      }
    ...
  }

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    ...
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

//MapperProxyFactory.java
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

//MapperMethod.java
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
    ...
   }

      }

//MapperAnnotationBuilder.java
  void parseStatement(Method method) {
    final Class<?> parameterTypeClass = getParameterType(method);
    final LanguageDriver languageDriver = getLanguageDriver(method);
    ...
    final String mappedStatementId = type.getName() + "." + method.getName();//id就是类.方法名
    ...
}

4. configuration

前面的分析,已经了解了mybatis的大概类之间关系。configuration是一个重要的配置数据类,内容非常丰富。因为有足够的配置信息,它还会new一些实例类,有部分类工厂的功能。

这里我就仅分析一下TypeHandlerRegistry,因为我们生产环境出现了对enum类型参数,在高并发时解析出错的情况。enum的handler是使用时才注册的。此版本前一版本中,是发现jdbcHandlerMap中拿type对应的map为null时,new一个hashmap放进去,之后才设置map中的值,导致有可能后面线程发现已经不是null,却拿不到里面的值的情况。

//Configuration.java
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);

//TypeHandlerRegistry.java
  public TypeHandlerRegistry(Configuration configuration) {
    this.unknownTypeHandler = new UnknownTypeHandler(configuration);

    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    register(JdbcType.BIT, new BooleanTypeHandler());
    ...
}


  @SuppressWarnings("unchecked")
  private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
    if (ParamMap.class.equals(type)) {
      return null;
    }
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
    TypeHandler<?> handler = null;
    if (jdbcHandlerMap != null) {
      handler = jdbcHandlerMap.get(jdbcType);
      if (handler == null) {
        handler = jdbcHandlerMap.get(null);//在上一个版本中,如果null,会new HashMap并放进去,之后再给这个map设置handler,这样在高并发时,可能造成得不到正确的handler,比如enum时,因为这个类型的handler,是使用时才会设置,并不是一开始就设置好。
      }
      if (handler == null) {
        // #591
        handler = pickSoleHandler(jdbcHandlerMap);
      }
    }
    // type drives generics here
    return (TypeHandler<T>) handler;
  }

  private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(type);
    if (jdbcHandlerMap != null) {
      return NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap) ? null : jdbcHandlerMap;
    }
    if (type instanceof Class) {
      Class<?> clazz = (Class<?>) type;
      if (Enum.class.isAssignableFrom(clazz)) {
        Class<?> enumClass = clazz.isAnonymousClass() ? clazz.getSuperclass() : clazz;
        jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(enumClass, enumClass);
        if (jdbcHandlerMap == null) {//当get不到时,会注册enum的handler。
          register(enumClass, getInstance(enumClass, defaultEnumTypeHandler));
          return typeHandlerMap.get(enumClass);
        }
      } else {
        jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
      }
    }
    typeHandlerMap.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);
    return jdbcHandlerMap;
  }

二、mybatis-spring-2.0.6.jar

        前面的分析只是在非spring的情况下分析的,通常我们使用的是spring的环境,这时候要使用mybatis-spring的包了。而且后面的mybatis-plus的分析也基于spring环境中的使用。

        这里就直接从mybatis plus的官方的使用示例开始,也就是其中的@MapperScan。

@SpringBootApplication
@MapperScan("com.baomidou.mybatisplus.samples.quickstart.mapper")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)  //注解的处理类
@Repeatable(MapperScans.class)
public @interface MapperScan

//注解处理中,产生的bean定义:MapperScannerConfigurer
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);

通过跟踪发现:

  • MapperScan注解上导入MapperScannerRegistrar.class,里面再注册一个bean:MapperScannerConfigurer
  • 这个类又实现了BeanDefinitionRegistryPostProcessor,InitializingBean等重要接口。从名字看,本身是一个configurer,所以用它在spring扩展中产生需要的bean是很合理的。
  • postProcessBeanDefinitionRegistry中对找到的beanDefination进行了设置。比如:definition.setBeanClass(this.mapperFactoryBeanClass);//mapperFactoryBeanClass = MapperFactoryBean.class;
  • 而class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T>。正好是一个FactoryBean,与最前面说的,类似dubbo的consumer接口,所有这种接口都会被定义为FactoryBean放入spring容器,这样用getObject()产生真正的实现类。
  • getObject()中正好是:getSqlSession().getMapper(this.mapperInterface);与前面分析中非XML时使用的官方示例(使用方式二:BlogMapper mapper = session.getMapper(BlogMapper.class);//interface)一样了。
  • spring就是给最终客户一种简便的方式,将客户的配置纳入原有的体系中来。

其它就不分析了,关于mybatis-spring-boot-start中的autoconfiguration,后面会提到,现在快速进入mybatis-plus部分。

三、mybatis-plus-3.0.jar

是不是上面的方式用着还不够爽?jpa是不是也有自己的方便之处?拿来一些给mybatis助力吧。另外如果在执行sql过程前后想插入自己的处理,怎么办?plus都提供了机制。

1. mybatis plus的扩展之一

还是使用前面的mybatis plus的官方示例,发现用了这么一个base接口,还有泛型pojo类。

public interface UserMapper extends BaseMapper<User> {

}

这还是一个mapper接口,方法都在base类中,也对,每一个mapper中写的多数都一样,那整一个abstract类就行MybatisSqlSessionFactoryBean了。至于处理的数据对象不一样,那使用通用的反射代码,也都能获取各自的参数与sql,估计jpa也是这么弄的吧。想想这条路是可行的,也确实方便了使用者。自己有特殊的SQL,继承后另外写就行了。

2. MybatisPlusAutoConfiguration中的变化

MybatisPlusAutoConfiguration代替了mybatis的MybatisAutoConfiguration,看看有什么变化呢?

//MybatisPlusAutoConfiguration.java  
package com.baomidou.mybatisplus.autoconfigure;

import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;


    // TODO 入参使用 MybatisSqlSessionFactoryBean
    private void applyConfiguration(MybatisSqlSessionFactoryBean factory) {
        // TODO 使用 MybatisConfiguration
        MybatisConfiguration configuration = this.properties.getConfiguration();
        if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
            configuration = new MybatisConfiguration();//使用扩展继承的configuration
        }
...
        factory.setConfiguration(configuration);
    }

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        // TODO 使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBean
        MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();//使用扩展继承的
    ...
    applyConfiguration(factory);//上面的方法
    ...
   }

MybatisSqlSessionFactoryBean:中增加了一个globalConfig,配置了一些东西,包括MybatisConfiguration。

MybatisConfiguration:使用了新的mapperRegistry来处理用户的接口

    @Override
    public <T> void addMapper(Class<T> type) {
        mybatisMapperRegistry.addMapper(type);
    }

3. MybatisMapperRegistry

        前面说了,plus中有了一个通用的接口,这里注册时,会使用pojo解析mappedStatement功能吗?实际上猜错了,这里没有找到,mappedstatement中还是记录的pojo对象,转化是后面executor中才有,在处理mappedstatement中的参数泛型对象时,而不是先转化好放入mappedstatement。

//这里跟踪,没有找到处理 pojo的@Table注解的功能,实际上是有一个chain来处理参数。
    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
           ...
            try {
                // TODO 这里也换成 MybatisMapperProxyFactory 而不是 MapperProxyFactory
                knownMappers.put(type, new MybatisMapperProxyFactory<>(type));
                // It's important that the type is added before the parser is run
                // otherwise the binding may automatically be attempted by the
                // mapper parser. If the type is already known, it won't try.
                // TODO 这里也换成 MybatisMapperAnnotationBuilder 而不是 MapperAnnotationBuilder
                MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
                parser.parse();
                loadCompleted = true;
            }...
        }

//config中有parameterHandler,这里组成chain,来处理pojo等参数问题。
  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

上面getLang(),提示我们在MybatisXMLLanguageDriver中的MybatisParameterHandler中找到了pojo注解的处理过程。并且interceptorChain中有三个handler组成,最后给executor使用。

  • interceptorChain.pluginAll(parameterHandler);
  • interceptorChain.pluginAll(resultSetHandler);
  • interceptorChain.pluginAll(statementHandler);
  • executor = (Executor) interceptorChain.pluginAll(executor);

说明executor在执行时,会使用这些handler分别处理请求的pojo泛型参数,还会处理返回值。

4. mybatis plus的扩展之二---插件

插件也是对终端用户提供的一个非常有用的功能。一般的扩展机制,最多的就是interceptor,filter这类的责任链设计模式的使用,而plus的插件有点不同,虽然也是写interceptor。

先看一下写的interceptor如果加载到框架中,给谁使用吧。有两种试,一种是配置xml中写plugin后解析,一种是注解为spring的bean。介绍后一种吧。在自动配置类的构造函数中,会用ObjectProvider找到容器中所有的Interceptor。如:MybatisPlusAutoConfiguration(...,ObjectProvider<Interceptor[]> interceptorsProvider...),下面的代码又说明了会从xml中找到interceptor,也都给configuration,放入interceptorChain中 。

configuration有一个功能就是产生成4个重要的类对象,executor,statementHandler,parameterHandler,resultSetHandler。生成时,会有例如:interceptorChain.pluginAll(executor);的处理,产生一个个代理对象。由于interceptor注解上有说明使用的对象以及方法签名信息,所以只会对应的有效果。

//MyplusAutoConfiguration.java中,会设置给MybatisSqlSessionFactoryBean
        if (!ObjectUtils.isEmpty(this.interceptors)) {
            factory.setPlugins(this.interceptors);
        }

//MybatisSqlSessionFactoryBean.java 的buildSqlSessionFactory()中,会设置给mybatisConfiguration类自己的plugins。它的又是通过解析XNode 来的。而自动配置中从容器找到的,也会加过来,最后都给configuration。
        if (!isEmpty(this.plugins)) {
            Stream.of(this.plugins).forEach(plugin -> {
                targetConfiguration.addInterceptor(plugin);
                LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
            });
        }

//MybatisXMLConfigBuilder.java中,会解析配置文件中的plugin
    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).getDeclaredConstructor().newInstance();
                interceptorInstance.setProperties(properties);
                configuration.addInterceptor(interceptorInstance);
            }
        }
    }

Plus extension包中的MybatisPlusInterceptor就是这样的功能,不过它内部又包含了一个innerInterceptor.java的内部子拦截器列表。怎么加载此内部拦截器?方法上有个注释说明了:

     * 使用内部规则,拿分页插件举个栗子:
     * <p>
     * - key: "@page" ,value: "com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor"
     * - key: "page:limit" ,value: "100"
     * <p>

根据需要,可以自己定义拦截器,我们有组件使用拦截器,实现动态路由不同的数据库。

四、结束

上面的基本过程简单分析完了,我们项目还进一步扩展mybatis-plus,支持更多的base方法等。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值