梳理一下mybatis到plus的基本代码逻辑(待完善)

        最近看一些对mybatis plus的扩展的实现,但之前没怎么看过mybatis与plus,做应用就按示例直接用就行。而做组件扩展就要研究源码了。为了快速弄清楚,跟踪了一下代码,梳理了一下实现逻辑。

         看懂是一方面,仔细思考plus扩展mybatis时的各种技巧更重要,比如extends改写,或者用新的interface实现,这些后续再补吧。

        大概调用过程图,展示重要的对象,以及主要过程。里面两个细节,一个是plus的injector,用通用的SQL结合mapper接口的方法,产生对应的语句。另一个是interceptor拦截执行过程。

一、主要处理过程

        主要过程,先从xml文件读取各种配置,包括mapper的类与sql,把得到的配置对象,特别是用mapper得到的MappedStatement(包括解析xml,inject固定方法,产生对应的MappedStatement),都存在一个config对象中。另外,对于获取mapper接口实现类时,会动态代理接口,内部找到接口方法对应的MappedStatement,最后用一个execator执行(包括用一个handler处理statement,并plugin来代理实现拦截的handler)。

        本文分析时,混合mybatis/plus进行。先找到使用组件的启动入口。

        非spring时,mybatis一个使用方式如下:

//不考虑spring时
@Test
public void testMyBatisBuild() throws IOException {
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = factory.openSession();
TestMapper mapper = sqlSession.getMapper(TestMapper.class);
Ttest one = mapper.getOne(1L);
System.out.println(one);
sqlSession.close();
}

        使用spring时,上面的一些类要自动产生,比如SqlSessionFactory/TestMapper等,启动spring后直接装配好。所以关注:mybatis-plus-boot-starter-3.3.1.jar 中的MybatisPlusAutoConfiguration.java。

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    // TODO 使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBean
    MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
    factory.setDataSource(dataSource);
...
//静态类:AutoConfiguredMapperScannerRegistrar 会注册一个mapperscan来处理mapper接口
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
...
//上面的MapperScannerConfigurer实现了bean定义注册接口,所以它可以注册新的bean到IOC中,这时又New一个类,委托它来处理注册mapper。
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
...
//它在doScan时,对于每一个接口,增加一个bean定义,定义中类都是一个FactoryBean(这点重要)
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      definition.setBeanClass(this.mapperFactoryBeanClass);!!!!!!都是同一个类
      definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
      
//所有的接口,注册到ioc中,都是这个 FactoryBean。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T>
//最重要的方法就是getObject,可以看到这里与非spring的代码中sqlSession.getMapper(TestMapper.class)一样了。
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

1、入口 -- MybatisSqlSessionFactoryBuilder

        先从plus的MybatisSqlSessionFactoryBuilder作为入口进行代码跟踪。它extends重写了mybatis的SqlSessionFactoryBuilder。

        它的核心过程就两步:

  • 用MybatisXMLConfigBuilder来处理从xml文件中读到的配置,产生MybatisConfiguration。

  • 另外用MybatisConfiguration再产生SqlSessionFactory。

        plus的MybatisConfiguration 也是extends Configuration(内容非常多),plus主要加了MybatisMapperRegistry属性。

        这一点也容易理解,持久化工具层次要使用sqlSession,扩展的组件,最好把新功能放在构建这个sqlSession的过程中,产生自己要用的,而且包含了sqlSession的新持久化对象mapper。

//虽然MybatisSqlSessionFactoryBuilder是改写的,但返回的还是supper类产生的DefaultSqlSessionFactory
SqlSessionFactory sqlSessionFactory = super.build(configuration);
...
//supper中返回的是这样
public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}
​
//DefaultSqlSessionFactory作为一个工厂,得到的SqlSession如下:
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);//配备了一个执行器
​
​
//前面看到,要用DefaultSqlSession得到一个接口的实现类。
  @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }
  
 //configuration是被改写过的MybatisConfiguration,还是原来的那个。方法也不一样了,发现用的是改写时新增加的mybatisMapperRegistry。
 /**
 * 使用自己的 MybatisMapperRegistry
 */
 @Override
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
 return mybatisMapperRegistry.getMapper(type, sqlSession);
 }
 
 //这里又是用plus的MybatisMapperProxyFactory来产生代理对象
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
 // TODO 这里换成 MybatisMapperProxyFactory 而不是 MapperProxyFactory
 final MybatisMapperProxyFactory<T> mapperProxyFactory = (MybatisMapperProxyFactory<T>) knownMappers.get(type);
 return mapperProxyFactory.newInstance(sqlSession);
 
 //动态代理Proxy.newProxyInstance核心的就是handler,这里就是
 new MybatisMapperProxy<>(sqlSession, mapperInterface, methodCache);
 
 //MybatisMapperProxy作为InvocationHandler,它的invoker方法中核心是:
 final MybatisMapperMethod mapperMethod = cachedMapperMethod(method);
 return mapperMethod.execute(sqlSession, args);
 
 //MybatisMapperMethod执行时,比如
 sqlSession.insert(command.getName(), param)
 
 //前面提到这里是mybatis的DefaultSqlSession,没有plus的
 //比如public int update(String statement, Object parameter)中,还是交executor。
 //另外,这里说明MappedStatement是核心语句对象。
 MappedStatement ms = configuration.getMappedStatement(statement);
 return executor.update(ms, wrapCollection(parameter));
 
 //好几个executor,看配置的是哪个,plus主要是MybatisCachingExecutor,它代理包装了mybaits的其它executor。
 //CachingExecutor 主要修改了分页缓存逻辑
 //它核心还是靠mybatis代理的executor-delegate来工作。
 // 切勿将这提取至上方,如果先查的话,需要提前将boundSql拷贝一份
 result = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
 
 //所以看一下SimpleExecutor的例子,它使用StatementHandler具体工作。
 Configuration configuration = ms.getConfiguration();
 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
 stmt = prepareStatement(handler, ms.getStatementLog());
 return handler.query(stmt, resultHandler);
 
 //看到mybatis的config中相关方法。看到是RoutingStatementHandler,不过有一个interceptorChain。
 StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
 
 //pluginAll中的一个个interceptor中,就是wrap成动态代理。
   public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }
  
  
//Plugin又是动态代理的invocationHandler,它的核心方法invoke,实际是用interceptorChain中的每个interceptor,执行intercept。参数就是Invocation。
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
        return interceptor.intercept(new Invocation(target, method, args));
      }
      
//自己实现一个 interceptor,它核心方法就是intercept,可以对请求方法/对象/参数/返回值进行自己想要的切入逻辑。
public Object intercept(Invocation invocation) throws Throwable {
..//before
Object object = invocation.proceed();
...//after
​
​
//interceptor不说了,介绍里面的StatementHandler,前面RoutingStatementHandler中又有其它的,比如
SimpleStatementHandler extends BaseStatementHandler
//最简单的情况,就是执行sql了。
      statement.execute(sql);
      rows = statement.getUpdateCount();

2、解析--MybatisXMLConfigBuilder

        MybatisSqlSessionFactoryBuilder 中包括从xml解析出config,再进行build()的方法。所以主要环节有xml解析,解析出xml文件中很多类,多数放在MybatisConfiguration(扩展configuration)类中,因为都是配置出来的相关工具。核心看处理xml的mappers节点:mapperElement(root.evalNode("mappers"));。里面用XMLMapperBuilder 进一步进行Parse。主要是:bindMapperForNamespace(); 对mapper目录中的xml与接口文件,分别进行解析。最后是 MybatisConfiguration(扩展configuration) 中的 MybatisMapperRegistry 中,按类型,放入mapper解析的结果,就是产生MappedStatement并放在配置类中。configuration.addMappedStatement(statement);

解析的重点-injector

        如果是plus的继承Mapper,就会有一些injector,为这个类型注入一些基本的操作方法。DefaultSqlInjector中,可以给mapper类提供默认的几十个操作类AbstractMethod的实现。这里可以作为未来增强通用方法时扩展改造。

//DefaultSqlInjector中,内置了一些方法,这些方法与特定的类组合,可以拼出一个具体表的sql出来,也就是产生 MappedStatement.
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        return Stream.of(
            new Insert(),
            new Delete(),
            new DeleteByMap(),
            ...
            
//AbstractSqlInjector
if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {
    GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
}
​
//为getMethodList中的几十个操作类,进行注入,产生MappedStatement
methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
​
//每个操作类,比如Delete(),它继承AbstractMethod,核心方法inject在每个类中具体实现的方法是injectMappedStatement:
    public void inject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        this.configuration = builderAssistant.getConfiguration();
        this.builderAssistant = builderAssistant;
        this.languageDriver = configuration.getDefaultScriptingLanguageInstance();
        /* 注入自定义方法 */
        injectMappedStatement(mapperClass, modelClass, tableInfo);
    }
    
   
//比如Insert的injectMappedStatement,就是String.format中,把表插到SQL模板中,最后产生MappedStatement,记录下来。
   ublic class Insert extends AbstractMethod {
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        KeyGenerator keyGenerator = new NoKeyGenerator();
  ...
        String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addInsertMappedStatement(mapperClass, modelClass, getMethod(sqlMethod), sqlSource, keyGenerator, keyProperty, keyColumn);
    }
}
​
//SqlMethod中的枚举方法中,有SQL模板,要替换其它的%s,比如对应的表。不过这只是产生一个sql,后面具体执行时,还要拼参数才是具体的SQL。
DELETE_BY_ID("deleteById", "根据ID 删除一条数据", "<script>\nDELETE FROM %s WHERE %s=#{%s}\n</script>"),

        所以想扩展公共的方法,不管用什么方式写,最后都是产生各个类型下的各个操作方法相关的MappedStatement。比如解析type时,用自己加的注解,可以拼到特定的SQL中。

3、DefaultSqlSession

        前面初始化的MybatisSqlSessionFactory工厂,解析了半天,最后的目的就是生成一个DefaultSqlSession(SqlSession),用它就可以得到一个mapper对象了

interface SqlSession:
* Through this interface you can execute commands, get mappers and manage transactions.
@Override
public <T> T getMapper(Class<T> type) {
  return configuration.getMapper(type, this);
}

我们知道mapper类是一个interface,真正要执行的话,要有一个实现这个接口的类,常见的办法就是动态代理。而动态代理中实现代理就要看产生代理对象时传入的InvocationHandler,即MybatisMapperProxy。

它的invoke方法中,最主要就是这里:

final MybatisMapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);

MybatisMapperMethod中,真正的执行还是 return executor.update(ms, wrapCollection(parameter));

3、执行-MybatisSimpleExecutor

执行器内部用一个new的handler处理数据库的query/update等操作。 StatementHandler就是用statement来执行sql。而sql可以用对应的MappedStatement与查询参数得到。

StatementHandler有一个重要的功能点是,加入了 interceptorChain,这样给扩展提供了便利。

statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);

包装原来的target时,传入增强的interceptor,又会用到动态代理。

public static Object wrap(Object target, Interceptor interceptor) {
  Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
  Class<?> type = target.getClass();
  Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
  if (interfaces.length > 0) {
    return Proxy.newProxyInstance(
        type.getClassLoader(),
        interfaces,
        new Plugin(target, interceptor, signatureMap));
  }
  return target;
}

        调用到Plugin的invoke()代理方法里,就传入实际的【执行对象/方法/参数】作为参数,在实现上,可以切入自己的逻辑。类似于AOP中的joinpoint。

        另外 pluginAll 是一个个interceptor进行增强的。与filterChain实现原理不一样。Plugin就是动态代理的invocationHandler,它的核心invoke方法,还是调用interceptor来执行。

      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
        return interceptor.intercept(new Invocation(target, method, args));
      }

二、扩展plus的思考

        自己实现 Interceptor,可以扩展执行SQL前的一些操作。比如日志什么的。就是mybatis的原理中的。

        考虑到mapper上的方法,是按名字找到对应的AbstractMethod实现类,这样就可以产生对应方法的MappedStatement。所以需要操作对象类上的特定字段(自己注解)与放入一些mapper特定的查询方法中时,可以继承AbstractMethod

        在实现其实抽象方法 injectMappedStatement时,可以查到注解的特定字段,拼接产生sql,最后产生新的MappedStatement。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值