Mybatis知识Part05(插件拦截对象,设置参数)

MyBatis允许拦截四大对象中的任意一个对象,而通过plugin源码,我们知道需要先注册签名才能使用插件,因此首先要确定需要拦截的对象,才能进一步确定需要配置的签名,进而完成拦截的方法逻辑
1、确定需要拦截的对象
根据功能来确定
	Executor是执行SQL的全过程,包括组装参数、组装结果集返回和执行SQL过程,都可以拦截,较为广泛,一般用的不多。根据是否启动缓存参数,决定是否用CachingExecutor进行封装,
	StatementHandler是执行SQL的过程,可以重写执行SQL的过程,是最常用的拦截对象。
	ParameterHandler主要拦截执行SQL的参数组装,可以重写组装参数规则
	ResultSetHandler用于拦截执行结果的组装,可以重写组装结果的规则
要拦截的是StatementHandler对象,应该在预编译SQL之前修改SQL,使得结果返回数量被限制。

2.拦截方法和参数
	当确定了需要拦截什么对象,接下来就确定需要拦截什么方法及方法的参数,这些都是在理解了MyBatis四大对象的基础上才确定的。

	查询的过程是通过Executor调度StatementHandler来完成的。调度StatementHandler的prepare方法法预编译SQL,于是要拦截的方法便是prepare方法,在此之前完成SQL的重新编写
StatementHandler接口的定义
	package org.apache.ibatis.executor.statement;
/**
 * @author Clinton Begin
 */
public interface StatementHandler {

  Statement prepare(Connection connection)
      throws SQLException;

  void parameterize(Statement statement)
      throws SQLException;

  void batch(Statement statement)
      throws SQLException;

  int update(Statement statement)
      throws SQLException;

   List query(Statement statement, ResultHandler resultHandler)
      throws SQLException;

  BoundSql getBoundSql();

  ParameterHandler getParameterHandler();

}
以上任何方法都可以拦截。从接口定义而言,prepare方法有一个参数Connection对象,
	定义插件的签名
	@Intercepts({@Signature(
        type = StatementHandler.class,
        method = "prepare",
        args = {Connection.class, Integer})})
public class ExamplePlugin implements Interceptor {
	...
}
其中 @Intercepts说明是一个拦截器,@Signature是注册拦截器签名的地方,只有签名满足条件才能拦截,type可以是四大对象中的一个,这里是StatementHandler。method代表要拦截四大都系的某一种接口方法,而args则不表示该方法的参数,要根据拦截对象的方法参数进行设置。

实现拦截方法

@Intercepts({@Signature(
        type = StatementHandler.class,
        method = "prepare",
        args = {Connection.class, Integer.class})})
public class ExamplePlugin implements Interceptor {

    private Logger log = Logger.getLogger(ExamplePlugin.class);
    private Properties props = null;

    /**
     * 插件方法,将替代StatementHandler的prepare方法
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); //被代理对象
        MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
        // 进行绑定
        // 分离代理对象链(由于目标类可能被多个插件拦截,从而形成多次代理,通过循环可以分离出最原始的目标类)
        while (metaStatementHandler.hasGetter("h")) {
            Object object = metaStatementHandler.getValue("h");
            metaStatementHandler = SystemMetaObject.forObject(object);
        }

        // 获取当前调用的SQl
        String sql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
        Long parameterObject = (Long) metaStatementHandler.getValue("delegate.boundSql.parameterObject");
        log.info("执行的SQL:"+ sql);
        log.info("参数:"+ parameterObject);
        log.info("before...");
        //如果当前代理的是一个非代理对象,那么它就回调用真实拦截对象的方法
        //如果不是,那么它会调度下个擦火箭代理对象的invoke方法
        Object proceed = invocation.proceed();
        log.info("after...");
        return proceed;
    }

    /**
     * 生成代理对象
     * @param target 被拦截对象
     * @return 代理对象
     */
    @Override
    public Object plugin(Object target) {
        // 采用系统默认的Plugin.wrap方法生成
        return Plugin.wrap(target, this);
    }

    /**
     * 设置参数,MyBatis初始化时,就会生成插件实例,并且调用这个方法
     * @param properties 配置参数
     */
    @Override
    public void setProperties(Properties properties) {
        this.props = properties;
        log.info("dbType:"+ this.props.get("dbType"));
    }
}

这个插件首先分离代理对象,然后通过MetaObject获取了执行的SQL和参数,并且在反射方法之前和之后分别打印了before和after,意味着可以在方法前或方法后执行特殊的代码来满足要求。

配置和运行

需要注意plugins元素的配置顺序,在sqlMapConfig.xml文件中配置
    
        
            
        
    
 任意一个sql执行后可以在日志中发现,setProperties方法在MyBatis系统初始化时就已经开始执行了【dbType会在最早打印出来】,sql元素和参数也可以从中获取【在before之前出现执行的SQl、参数等】,并且在编译SQL之前和之后打印出了before和after
定义分页参数POJO
public class PageParams{
	//当前页码
 	private int pageNum;
	 //每页限制条数
    private int pageSize;
    //是否启动插件,如果不启动,则不作分页
    private Boolean useFlag;
    //是否检测页码的有效性,如果为true,则页码大于最大页数,则抛出异常
     private Boolean checkFlag;
     //是否清除最后order by 后面的语句
    private Boolean cleanOrderBy;
    // 总条数,插件会回填这个值
    private int total;
    // 总页数,插件会回填这个值
    private int totalPage;
}
在MyBatis中传递参数可以是单个参数,也可以是多个,或者使用map。有了规则,为了使用方便,只要满足下面的条件之一,就可以启用分页参数(PageParams)
	传递单个PageParams或者其子对象
	map中存在一个值为PageParams或者其子对象的参数
	在MyBatis中传递多个参数,但其中之一为PageParams或者其子对象
	传递单个POJO参数,这个POJO有一个属性为PageParams或者其子对象,且提供了setter和getter方法
在BoundSql的描述中,有对参数规则的描述,通过这些规则可以分离出参数
为了在编译SQL之前修改SQL,需增加分页参数并计算出查询总条数。依据MyBatis原理,选择拦截StatementHandler的prepare方法,拦截方法签名
	@Intercepts({@Signature(
        type = StatementHandler.class,
        method = "prepare",
        args = {Connection.class, Integer.class})})
	在插件中有3个方法需要自己完成,其中plugin方法中,使用Plugin类的静态方法wrap生产代理对象。当PageParams的useFlag属性设置为false时,就没有必要生成代理对象了,使用代理对象会造成性能降低。对于setProperties方法而言,一方面它给予PageParams属性定义默认值,另一方面可以接受来自MyBatis配置文件的plugins标签的配置参数,以满足不同项目的需要。
	 @Override
	    public void setProperties(Properties properties) {
	        String strDefaultPage = properties.getProperty("default.page", "1");
	        String strDefaultPageSize = properties.getProperty("default.pageSize", "10");
	        String strDefaultUseFlag = properties.getProperty("default.useFlag", "false");
	        String strDefaultCheckFlag = properties.getProperty("default.checkFlag", "false");
	        String strDefaultCleanOrderBy = properties.getProperty("default.cleanOrderBy", "false");
	        this.defaultPage = Integer.parseInt(strDefaultPage);
	        this.defaultPageSize = Integer.parseInt(strDefaultPageSize);
	        this.defaultUseFlag = Boolean.parseBoolean(strDefaultUseFlag);
	        this.defaultCheckFlag = Boolean.parseBoolean(strDefaultCheckFlag);
	        this.defaultCleanOrderBy = Boolean.parseBoolean(strDefaultCleanOrderBy);
	//        this.props = properties;
	//        log.info("dbType:"+ this.props.get("dbType"));
	    }
plugin方法使用了MyBatis提供生成代理对象的方法来生成,只要符合签名的规则,StatementHandler在运行时就会进入到intercept方法里。setProperties方法用于给插件设置默认值,这些默认值可以通过配置文件去改变它。

你可能感兴趣的:(知识)