Mybatis—SqlSource与BoundSql

  SqlSource用于描述通过@Select、@Insert、@Delete、@Update、@SelectProvider、@InsertProvider、@DeleteProvider和@UpdateProvider等注解配置的SQL信息,或者通过XML配置文件配置的SQL信息。SqlSource接口又定义了BoundSql getBoundSql(Object parameterObject)方法来获取对SQL信息进行封装的BoundSql实例。

SqlSource

  SqlSource接口中只定义了一个getBoundSql()方法,该方法返回一个BoundSql实例,如下源代码所示:

public interface SqlSource {
  BoundSql getBoundSql(Object parameterObject);
}

  Mybatis针对SqlSource接口一共提供DynamicSqlSource、ProviderSqlSource、RawSqlSource和StaticSqlSource四种实现,

  • DynamicSqlSource,描述XML配置文件和@Select、@Update等注解配置的SQL信息,这些SQL通常包含动态SQL配置或者参数占位符,需要在Mapper调用时才能确定最终的SQL语句。
  • ProviderSqlSource,描述通过@SelectProvider、@InsertProvider、@DeleteProvider和@UpdateProvider注解配置的SQL信息。
  • RawSqlSource,描述XML配置文件中的SQL信息,与DynamicSqlSource唯一的区别在于,其只描述不包含参数占位符且在解析XML配置时就能确定的SQL信息。
  • StaticSqlSource,描述经过ProviderSqlSource、DynamicSqlSource或者RawSqlSource解析后得到的静态SQL信息。

ProviderSqlSource

  ProviderSqlSource用于描述通过@SelectProvider、@InsertProvider、@DeleteProvider和@UpdateProvider注解配置的SQL信息,如下述代码块所示,通过@SelectProvider注解指定SqlGenerator类中的selectIds方法来构造SQL语句。

@SelectProvider(type = SqlGenerator.class, method = "selectIds")
List<Integer> selectIds();

  ProviderSqlSource是通过其构造函数来完成构造当前指定的SQL信息,如下源代码逻辑,ProviderSqlSource构造函数的逻辑可分为两大类,一类是正常的给属性复制,一类则是处理通过注解type和method属性指定构造SQL的信息,详情如下:

  1. 根据构造函数入参对configuration、mapperMethod和languageDriver属性进行赋值;
  2. 通过注解的type或者value属性来确定构造SQL的类;
  3. 通过注解的method属性来确定构造SQL具体的方法名称;
  4. 如果没有找到执行method且当前provider注解与ProviderMethodResolver是父子关系,就将确定的构造SQL的类的构造函数当做构造SQL具体的方法名称;
  5. 如果有明确指定构造SQL具体的方法,就从指定构造SQL的类中寻找指定的方法;
  6. 成功找到构造SQL的类和方法之后,就将所有值赋值给指定的属性。
public ProviderSqlSource(Configuration configuration, Annotation provider,
                         Class<?> mapperType, Method mapperMethod) {
    String candidateProviderMethodName;
    Method candidateProviderMethod = null;
    try {
      // 省略当前configuration、mapperMethod和languageDriver属性的赋值
      // 获取当前provider注解指定的SQL构造类,如代码块中的type = SqlGenerator.class
      this.providerType = getProviderType(configuration, provider, mapperMethod);
      // 获取当前provider注解指定的方法,如代码块中的method = "selectIds"  
      candidateProviderMethodName = (String) provider.annotationType()
                                     .getMethod("method").invoke(provider);

      if (candidateProviderMethodName.length() == 0 
          		&& ProviderMethodResolver.class.isAssignableFrom(this.providerType)) {
          // 如果没有执行method且当前provider注解与ProviderMethodResolver是父子关系
          candidateProviderMethod = ((ProviderMethodResolver) this.providerType.getDeclaredConstructor().newInstance())
            .resolveMethod(new ProviderContext(mapperType, mapperMethod, configuration.getDatabaseId()));
      }
      if (candidateProviderMethod == null) {
        // 确认用于构造SQL的方法  
        candidateProviderMethodName = candidateProviderMethodName.length() == 0 
            		? "provideSql" : candidateProviderMethodName;
        // 从指定构造类中找到构造SQL的方法的Method
        for (Method m : this.providerType.getMethods()) {
          if (candidateProviderMethodName.equals(m.getName()) 
              	&& CharSequence.class.isAssignableFrom(m.getReturnType())) {
            if (candidateProviderMethod != null) {
              throw new BuilderException("");
            }
            candidateProviderMethod = m;
          }
        }
      }
    } 
    // 省略异常处理和部分赋值代码
  }

DynamicSqlSource

  DynamicSqlSource用于描述XML配置文件和@Select、@Update等注解,通过参数占位符或者动态标签配置的动态SQL信息,其也是通过构造函数来完成对SQL信息的描述。其构造函数相对简单,就只是对属性值进行赋值,如下源代码所示。

public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    this.configuration = configuration;
    this.rootSqlNode = rootSqlNode;
}

RawSqlSource

  RawSqlSource用于描述XML配置文件和@Select、@Update等注解,不包含参数占位符和动态标签的SQL配置信息。RawSqlSource构造函数除了正常给属性赋值以外,还多了一步解析SQL的操作。

public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, 
                    Class<?> parameterType) {
    this(configuration, getSql(configuration, rootSqlNode), parameterType);
}

public RawSqlSource(Configuration configuration, String sql,
                    Class<?> parameterType) {
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
}

private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
    DynamicContext context = new DynamicContext(configuration, null);
    rootSqlNode.apply(context);
    return context.getSql();
}

BoundSql

  BoundSql是对SQL语句及参数信息的封装,是SqlSource解析后的结果。Executor接口所定义的方法上可以清晰地看见BoundSql是作为入参传入的,所以不难推导出Executor是将BoundSql作为SQL信息来执行SQL语句的。
  BoundSql源代码就是只是封装了SQL语句、参数映射、Mapper调用时传入的参数对象和一个反射工具类,其这几个属性对应的值都是在构造BoundSql实例时通过构造函数传入的。

public class BoundSql {

  private final String sql;
  private final List<ParameterMapping> parameterMappings;
  private final Object parameterObject;
  private final Map<String, Object> additionalParameters;
  private final MetaObject metaParameters;

  public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
    this.sql = sql;
    this.parameterMappings = parameterMappings;
    this.parameterObject = parameterObject;
    this.additionalParameters = new HashMap<>();
    this.metaParameters = configuration.newMetaObject(additionalParameters);
  }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
MyBatis提供了拦截器(Interceptor)的机制,可以在SQL执行前、后或者代替原有SQL执行。通过自定义拦截器,我们可以实现对SQL的修改。 自定义拦截器需要实现Interceptor接口,其中最重要的方法是intercept()方法,该方法的参数Invocation代表一个拦截的方法调用。在该方法中,我们可以通过Invocation对象获取到原有的SQL以及其它相关信息,然后对其进行修改。 下面是一个简单的示例,演示如何在SQL执行前拦截并修改SQL: ```java public class MyInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 获取原始的SQL语句 MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; Object parameter = invocation.getArgs()[1]; BoundSql boundSql = mappedStatement.getBoundSql(parameter); String originalSql = boundSql.getSql(); // 修改SQL语句 String newSql = originalSql + " limit 10"; // 将修改后的SQL语句设置回去 BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), newSql, boundSql.getParameterMappings(), boundSql.getParameterObject()); MappedStatement newMappedStatement = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql)); invocation.getArgs()[0] = newMappedStatement; // 执行SQL return invocation.proceed(); } // 复制MappedStatement对象,将新的BoundSqlSqlSource设置进去 private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) { MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType()); builder.resource(ms.getResource()); builder.fetchSize(ms.getFetchSize()); builder.statementType(ms.getStatementType()); builder.keyGenerator(ms.getKeyGenerator()); builder.keyProperty(StringUtils.join(ms.getKeyProperties(), ",")); builder.timeout(ms.getTimeout()); builder.parameterMap(ms.getParameterMap()); builder.resultMaps(ms.getResultMaps()); builder.cache(ms.getCache()); builder.useCache(ms.isUseCache()); return builder.build(); } // 定义一个内部类,实现SqlSource接口 private class BoundSqlSqlSource implements SqlSource { BoundSql boundSql; public BoundSqlSqlSource(BoundSql boundSql) { this.boundSql = boundSql; } @Override public BoundSql getBoundSql(Object parameterObject) { return boundSql; } } } ``` 上面的示例是在原有SQL语句后面加了一个limit 10的限制,这只是一个简单的修改示例。实际应用中,我们可以根据需要对SQL语句进行各种操作,甚至可以完全替换原有SQL语句。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乐只乐之

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值