Mybatis加载Mapper的原理以及自定义动态sql的几种思路

 

mybatis是目前非常流行的数据库框架。它的轻便灵活的特性使的我们更加方便的操作数据库。所以我们来扒一扒Mybatis的原理。

先扒一张百度上对mybatis结构的描述的图

Mybatis加载Mapper的原理以及自定义动态sql的几种思路_第1张图片

百度的图

从上可以看出mysql的几个组件Configiration,MappedStatements,Mapper,xml。

先写个demo试验一下。

mybatis版本是

 
                org.mybatis
                mybatis
                3.4.6
            

数据源的用的ali的druid。不过数据源不是关键

代码如下

DataSource dataSource = dataSource();
        TransactionFactory transactionFactory = new JdbcTransactionFactory();
        Environment environment = new Environment("development", transactionFactory, dataSource);

        Configuration configuration = new Configuration(environment);

        configuration.addMapper(ApiErrorLogMapper.class);
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(configuration);
 ApiErrorLogMapper apiErrorLogMapper = factory.openSession().getMapper(ApiErrorLogMapper.class);

        ApiErrorLog apiErrorLog = apiErrorLogMapper.getByPrimaryKey(1);

 

代码非常简单,关键代码是

configuration.addMapper(ApiErrorLogMapper.class);

sqlSession.getMapper(ApiErrorLogMapper.class);

执行一下,debug调试。得到的流程大致如下

 

Mybatis加载Mapper的原理以及自定义动态sql的几种思路_第2张图片

ApiErrorLogMapper实际是通过JDK动态代理获取的MapperProxy对象。所以ApiErrorLogMapper只能是接口,jdk只支持接口的动态代理。

MapperProxy中持有MapperMethod的饮用。MapperMethod又持有SqlSession。在SqlSession中

@Override
  public  List selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

可以看出,MappedStatement也是在Configuration中保存了 Mapper+Method和 MappedStatement的一个StrictMap。

整个Configuration加载Mapper的流程如下。

Mybatis加载Mapper的原理以及自定义动态sql的几种思路_第3张图片

由此看出,Mybatis核心逻辑就是解析MappedStatement。MappedStatement保存了查询所需要的Sql的来源SqlSource,保存了主键策略,和返回值的ResultMap需要的类型数据。

其核心方法为

org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse


org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parseStatement

 获取SqlSource的逻辑

private SqlSource getSqlSourceFromAnnotations(Method method, Class parameterType, LanguageDriver languageDriver) {
    try {
      Class sqlAnnotationType = getSqlAnnotationType(method);
      Class sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
      if (sqlAnnotationType != null) {
        if (sqlProviderAnnotationType != null) {
          throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
        }
        Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
        final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
        return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
      } else if (sqlProviderAnnotationType != null) {
        Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
        return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method);
      }
      return null;
    } catch (Exception e) {
      throw new BuilderException("Could not find value method on SQL annotation.  Cause: " + e, e);
    }
  }

核心的SqlSource为

DynamicSqlSource 处理解析xml中动态sql
RawSqlSource 也是处理Xml 这不过这个sql不处理带有${}替换符号的sql
ProviderSqlSource基于注解的动态SqlSql

综上可以看出mybatis的几种用法

1.基于mapper.xml编写sql。优点点是可以编写任何复杂逻辑,确点是维护sql比较麻烦,并且不友好。

2.基于注解生成sql,这种一般有两种方式,

    1).@Select,@Insert 等。这种一个方法一个sql,适用于sql比较简单,而且适用性窄的情况。

    2).@InsertProvider ,@SelectProvider 等,这些是动态获取sql的。在自定一的Provider中能获取到ProviderContext对象,对象封装了mapper的类型和method和具体的参数期生成sql的形式入下。这种适合比较复杂的sql。当时结构稍微会多一层。

 private SqlSource createSqlSource(Object parameterObject) {
    try {
      int bindParameterCount = providerMethodParameterTypes.length - (providerContext == null ? 0 : 1);
      String sql;
      if (providerMethodParameterTypes.length == 0) {
        sql = invokeProviderMethod();
      } else if (bindParameterCount == 0) {
        sql = invokeProviderMethod(providerContext);
      } else if (bindParameterCount == 1 &&
              (parameterObject == null || providerMethodParameterTypes[(providerContextIndex == null || providerContextIndex == 1) ? 0 : 1].isAssignableFrom(parameterObject.getClass()))) {
        sql = invokeProviderMethod(extractProviderMethodArguments(parameterObject));
      } else if (parameterObject instanceof Map) {
        @SuppressWarnings("unchecked")
        Map params = (Map) parameterObject;
        sql = invokeProviderMethod(extractProviderMethodArguments(params, providerMethodArgumentNames));
      } else {
        throw new BuilderException("Error invoking SqlProvider method ("
                + providerType.getName() + "." + providerMethod.getName()
                + "). Cannot invoke a method that holds "
                + (bindParameterCount == 1 ? "named argument(@Param)": "multiple arguments")
                + " using a specifying parameterObject. In this case, please specify a 'java.util.Map' object.");
      }
      Class parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
      return sqlSourceParser.parse(replacePlaceholder(sql), parameterType, new HashMap());
    } catch (BuilderException e) {
      throw e;
    } catch (Exception e) {
      throw new BuilderException("Error invoking SqlProvider method ("
          + providerType.getName() + "." + providerMethod.getName()
          + ").  Cause: " + e, e);
    }
  }

 

结合Mybatis的用法。如果需要对mybaits进行扩展,如单表操作这种频率很高,生成sql的方式比较相似。可以统一动态生成。类似与Hibernate jpa的工具可以有以下思路。

1.基于SqlProvider,如@SelectProvider这种。犹豫Provider可以获得调用接口,和参数。所以基本能够根据接口获得操作对象表信息和方法信息,进行sql的生成。但是这种没办法还是基于mybaits原生的MappedStatement。向主键策略的就不太容易。

2.修改addMapper的操作。在addMapper的时候加载自己逻辑的MappedStatement 和DynamicSqlSource。这样能够全面进行自定义。但是DynamicSqlSource的生成比较复杂。类似于tk.mapper的原理

3.先修改addMapper的操作。自定义SqlSource。通过自己逻辑创建MappedStatement。这种自定义程度高。而且SqlSourse不用按照DynamicSqlSource这种基于

你可能感兴趣的:(Mybatis)