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的插件可以实现在SQL语句执行前、后、返回结果前等多个阶段对SQL语句进行拦截和修改,可以实现动态修改SQL语句的功能。 具体实现方法是通过实现MyBatis的Interceptor接口来实现插件,Interceptor接口有三个方法: - intercept:拦截目标对象的方法,可以在该方法中对SQL语句进行修改。 - plugin:用来包装目标对象,返回一个代理对象,可以通过代理对象来访问目标对象的方法。 - setProperties:用来设置插件的属性。 在intercept方法中,可以获取到当前执行的SQL语句,然后进行修改并返回新的SQL语句。例如,下面的代码可以将SQL语句中的所有表名都添加上前缀: ``` public class AddPrefixInterceptor implements Interceptor { private String prefix; public Object intercept(Invocation invocation) throws Throwable { MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; Object parameter = invocation.getArgs()[1]; BoundSql boundSql = mappedStatement.getBoundSql(parameter); String sql = boundSql.getSql(); sql = sql.replaceAll("from", "from " + prefix); BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), sql, boundSql.getParameterMappings(), boundSql.getParameterObject()); MappedStatement newMappedStatement = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql)); invocation.getArgs()[0] = newMappedStatement; return invocation.proceed(); } public Object plugin(Object target) { return Plugin.wrap(target, this); } public void setProperties(Properties properties) { this.prefix = properties.getProperty("prefix"); } 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(); } private static class BoundSqlSqlSource implements SqlSource { private BoundSql boundSql; public BoundSqlSqlSource(BoundSql boundSql) { this.boundSql = boundSql; } public BoundSql getBoundSql(Object parameterObject) { return boundSql; } } } ``` 在这个插件中,我们将所有的表名前都添加了一个前缀,可以通过在配置文件中配置插件来启用该功能,例如: ``` <plugins> <plugin interceptor="com.example.AddPrefixInterceptor"> <property name="prefix" value="test_" /> </plugin> </plugins> ``` 这样,所有的SQL语句中的表名都会被添加上test_前缀。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

乐只乐之

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

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

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

打赏作者

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

抵扣说明:

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

余额充值