mybatis(4)—自定义拦截器(下)对象详解

mybatis自定义拦截器(一)基本使用
mybatis自定义拦截器(二)对象详解

mybatis若想实现自定义拦截器,需要实现Interceptor接口,对象首先会执行plugin(Object target)方法,根据类上的@Intercepts注解决定是否拦截。若需要拦截,则调用intercept(Invocation invocation)方法。

1. 准备工作

需要拦截的sql:

  <select id="selectByNameAndGroup" parameterType="java.lang.String" resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List" />
    from sys_quartz_job_config
    where job_Name = #{jobName,jdbcType=VARCHAR} AND
    job_Group =#{jobGroup,jdbcType=VARCHAR}
  </select>

需要拦截的Mapper对象:

SysQuartzJobConfig selectByNameAndGroup(@Param("jobName") String jobName,@Param("jobGroup") String jobGroup);

invocation对象:

invocation对象

可以看到invocation中的args参数,就是@Intercepts中的args参数。

@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class})})

2. mappedStatement对象

1. invocation对象如何获取mappedStatement对象:

一个mappedStatement对象对应Mapper配置文件中的一个select/update/insert/delete节点,主要描述的是一条sql语句。

//获取参数1:MappedStatement对象
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
MappedStatement.png

2. MappedStatement对象详解:

public final class MappedStatement {
  //该Mapper.xml的绝对路径
  private String resource;
  //mybatis所有的配置
  private Configuration configuration;
  //sql的ID(命令空间+key)
  private String id;
  //尝试影响驱动程序每次批量返回的结果行数和这个设置值相等
  private Integer fetchSize;
  //SQL超时时间
  private Integer timeout;
  //Statement的类型,STATEMENT/PREPARE/CALLABLE
  private StatementType statementType;
  //结果集类型,FORWARD_ONLY/SCROLL_SENSITIVE/SCROLL_INSENSITIVE 
  private ResultSetType resultSetType;
  //表示解析出来的SQL
  private SqlSource sqlSource;
  //缓存
  private Cache cache;
  //已废弃
  private ParameterMap parameterMap;
  //对应的ResultMap
  private List<ResultMap> resultMaps;
  private boolean flushCacheRequired;
  private boolean useCache;
  private boolean resultOrdered;
  //SQL类型,INSERT/SELECT/DELETE
  private SqlCommandType sqlCommandType;
  //和SELECTKEY标签有关
  private KeyGenerator keyGenerator;
  private String[] keyProperties;
  private String[] keyColumns;
  private boolean hasNestedResultMaps;
  //数据库ID,用来区分不同环境
  private String databaseId;
  private Log statementLog;
  private LanguageDriver lang;
  //多结果集时
  private String[] resultSets;

  MappedStatement() {
    // constructor disabled
  }
  ...
  }

其中真正表示SQL的字段是SqlSource这个对象。
而SqlSource接口很简单,只有一个getBoundsql方法。

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

sqlSource有很多实现,需要我们重点关注的是StaticSqlSource,RawSqlSource和DynamicSqlSource。而通过上述的实现,便可以将mybatis特有的sql格式转化成可供PrepareStatement直接执行的sql。

mappedStatement —— BoundSql对象:

 BoundSql boundSql = mappedStatement.getBoundSql(parameter);

上述方法主要是对动态标签的解析,获取完全可执行的sql。对#{ }字符解析,将其替换成?,最后均包装成域表达式供PrepareStatement调用。

  • 解析后的sql保存在sql对象中;
  • 请求参数保存在parameterObject对象中;
  • #{ }key属性以及相对应的参数映射,比如javaType,jdbcType等信息均保存至BoundSql的parameterMapping属性中。供最后的域表达式对象PrepareStatement赋值使用。
BoundSql .png

BoundSql—解析后的sql对象:

BoundSql对象中的sql对象是对动态标签解析后的完全可执行的sql。

select
     
    ID, JOB_NAME, JOB_GROUP, ENABLE, CLASS_NAME, CRON, CONCURRENT, CREATE_TIME, MODIFY_TIME
   
    from sys_quartz_job_config
    where job_Name = ? AND
    job_Group =?

BoundSql—ParameterObject对象:

该对象为sql执行的参数。也就是我们传入的参数。因为使用了@param注解,故有两种方式可以获取到value的值。

//获取parameterObject对象
Object parameter = null;
if (invocation.getArgs().length > 1) {
     parameter = invocation.getArgs()[1];
}
parameterObject.png

注意:ParameterObject是一个Object对象,上传不同的参数时,该对象的类型不同。

  • 若上传一个参数对象时:为该参数的类型;
  • 若上传多个参数对象时:为ParamMap对象,由于我们使用了@Param注解,故可以使用注解的key,也可以使用param1取出变量。
  • 若上传的为Criteria对象时(如下图):也是为该对象的类型,但是获取对象的方法又不相同。
Criteria对象.png

注意:如果真实传递进来的参数在TypeHandlerRegistry对象中声明的话,则使用真实传递进来的参数作为真实的变量名。

换句话说,我们在Mapper接口里面写delete(Integer id),而在Mapper.xml中定义的变量#{var}可以随便命名,都可以被mybatis正确处理。

BoundSql—ParameterMapping对象:

采用#{var}的形式来引用变量时,其中的变量会在解析Mapper.xml文件中的语句时,就被替换成占位符“?”,同时通过ParameterMapping类记录对应的变量信息。在真正执行对应语句的时候回传递真实的参数。根据parameterMapping信息给ParameterStatement设置参数。

Criteria对象的ParameterMapping对象.png
普通对象的ParameterMapping对象.png

需要注意的是,property参数就是#{var}中的var。

BoundSql—additionalParameters对象

若是使用Criteria对象时的sql,那么在additionalParameters中便有值,我们可以使用:

//获取请求参数
//获取#{var}中的key
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
      Object obj = boundSql.getAdditionalParameter(propertyName);
}
Criteria对象的additionalParameters参数.png

BoundSql—MetaObject对象

MetaObject类相当于一个工具类,Mybatis在sql参数设置和结果集映射里经常使用到这个对象。

 //原始的对象
  private Object originalObject;
  //对原始对象的一个包装
  private ObjectWrapper objectWrapper;
  
  //这两个属性基本不用,因为在Mybatis中都找不到ObjectWrapperFactory的有效实现类
  private ObjectFactory objectFactory;
  private ObjectWrapperFactory objectWrapperFactory;
MetaObject.png
//通过MetaObject完成参数的设置
//获取key
 String propertyName = parameterMapping.getProperty();
 if (metaObject.hasGetter(propertyName)) {
     Object obj = metaObject.getValue(propertyName);
}

MappedStatement——Configuration对象

mybatis会在启动时读取所有的配置文件,然后加载到内存中,Configuration对象就是承载整个配置的类。

Configuration configuration = mappedStatement.getConfiguration();
public class Configuration {
  /**
   * MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中,
   * 比如设置不同的开发、测试、线上配置,在每个配置中可以配置事务管理器和数据源对象.
   */
  protected Environment environment;
 
  //允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为false。
  protected boolean safeRowBoundsEnabled = false; 
  //允许在嵌套语句中使用分页(ResultHandler)。如果允许使用则设置为false
  protected boolean safeResultHandlerEnabled = true;
  //是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。
  protected boolean mapUnderscoreToCamelCase = false; 
  //当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载(参考lazyLoadTriggerMethods).
  protected boolean aggressiveLazyLoading = true;
  //是否允许单一语句返回多结果集(需要兼容驱动)
  protected boolean multipleResultSetsEnabled = true; 
  //允许 JDBC 支持自动生成主键,需要驱动兼容。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。
  protected boolean useGeneratedKeys = false;
  //使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。
  protected boolean useColumnLabel = true;
  //配置全局性的cache开关
  protected boolean cacheEnabled = true;
  /*指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。
    注意基本类型(int、boolean等)是不能设置成 null 的。*/
  protected boolean callSettersOnNulls = false;
  //指定 MyBatis 增加到日志名称的前缀。
  protected String logPrefix;
  //指定 MyBatis 所用日志的具体实现,未指定时将自动查找
  protected Class <? extends Log> logImpl;
  /*MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 
    默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 
    若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。*/
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  /*当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 
    某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。*/
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
  //指定哪个对象的方法触发一次延迟加载。
  protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
  //设置超时时间,它决定驱动等待数据库响应的秒数。

Configuration — TypeHandlerRegistry对象

类型处理器注册对象。在构建TypeHandlerRegistry对象的时候,便将类型注册了进去。

类型处理器TypeHandlerRegistry简单点就是用于处理javaType与jdbcType之间的类型转换用的处理器,Mybatis针对诸多Java类型与数据库类型进行了匹配处理。

//获取到类型处理器
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();

类型处理器的格式:

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

推荐阅读更多精彩内容