学习Mybatis(5):编写插件

Mybatis的插件用来对SQL语句进行修饰,或者在执行SQL语句前后进行一些工作

在Mybatis的配置文件中,<plugins>标签就是来配置自定义插件的

例如:

<plugins>
  <plugin interceptor="org.mybatis.example.ExamplePlugin">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>

自定义插件需要有如下注解:

@Intercepts({@Signature(
  type= Executor.class,
  method = "update",
  args = {MappedStatement.class,Object.class})})
  • type:拦截的组件类型,有Executor、StatementHandler、ResultSetHandler、ParameterHandler等,从名字可以看出来,插件可以作用于SQL执行器、SQL语句、SQL参数、结果集等
  • method:四大组件的方法,如下:
    • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
    • ParameterHandler (getParameterObject, setParameters)
    • ResultSetHandler (handleResultSets, handleOutputParameters)
    • StatementHandler (prepare, parameterize, batch, update, query)
  • args:方法参数,如StatementHandler的prepare方法,参数为Connection和Integer类型,就写{Connection.class,Integer.class}

编写插件需要实现Interceptor接口,该接口定义如下:

import java.util.Properties;

public interface Interceptor {
    Object intercept(Invocation var1) throws Throwable;

    Object plugin(Object var1);

    void setProperties(Properties var1);
}

setProperties用来为实例变量赋值

plugin一般只要调用Plugin.wrap(var1,this)即可

重点是intercept方法,用来实现拦截逻辑,不过一般最后都要调用var1.proceed();

还有一个重要类——MetaObject,用来获取四大组件内部的实例变量(以及这些变量内部的变量),例如 StatementHandler 类,实际上框架调用的是 RoutingStatementHandler ,该类有一个StatementHandler类型的delegate变量,其内部有一个名为boundSql的对象,现在要获取该对象,可以这么写:

MetaObject stmtHandlerMeta=SystemMetaObject.forObject(statementHandler);
BoundSql boundSql= (BoundSql) stmtHandlerMeta.getValue("delegate.boundSql");

实际编写

以分页查询为例(结合Spring Boot):

1)编写Page类:

public class Page {
    private Integer page;
    private Integer pageSize;

    public Integer getPage() {
        return page;
    }

    public void setPage(int page) {
        this.page = page;
    }

    public Integer getPageSize() {
        return pageSize;
    }

    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }
}

很简单的一个POJO类,包含两个参数:页码和页容量,及其getter、setter

2)编写PagePlugin类:

import Page;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;

//拦截StatementHandler的prepare方法
@Intercepts({
        @Signature(type = StatementHandler.class,method = "prepare",args = {Connection.class,Integer.class})
})
public class PagePlugin implements Interceptor {
    private Integer defaultPage;
    private Integer defaultPageSize;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler= (StatementHandler) invocation.getTarget();
        MetaObject statementHandlerMetaObject= SystemMetaObject.forObject(statementHandler);
        Object o=null;
        //插件使用责任链模式重重代理,这里是用来还原到原始类
        while(statementHandlerMetaObject.hasGetter("h")){
            o=statementHandlerMetaObject.getValue("h");
            statementHandlerMetaObject=SystemMetaObject.forObject(o);
        }
        if(o!=null)
            statementHandler= (StatementHandler) o;
        MetaObject stmtHandlerMeta=SystemMetaObject.forObject(statementHandler);
        String sql= (String) stmtHandlerMeta.getValue("delegate.boundSql.sql");
        //只有select方法才会拦截
        if(sql.trim().toLowerCase().startsWith("select")!=0)
            return invocation.proceed();
        BoundSql boundSql= (BoundSql) stmtHandlerMeta.getValue("delegate.boundSql");
        Object paramObject=boundSql.getParameterObject();
        Page page=getPage(paramObject);
        //没有带页面参数就直接反射
        if (page==null)
            return invocation.proceed();
        int pageNum=page.getPage()==null?defaultPage:page.getPage();
        int pageSize=page.getPageSize()==null?defaultPageSize:page.getPageSize();
        int total=getTotal(invocation,stmtHandlerMeta,boundSql);
        int totalPage=total/pageSize==0?total/pageSize:total/pageSize+1;
        //页码过大(超过数据总数)就直接反射
        if(!checkPage(pageNum,totalPage))
            return invocation.proceed();
        return changeSQL(invocation,stmtHandlerMeta,boundSql,pageNum,pageSize);
    }

    private Object changeSQL(Invocation invocation, MetaObject stmtHandlerMeta, BoundSql boundSql, int pageNum, int pageSize) throws InvocationTargetException, IllegalAccessException, SQLException {
        String sql= (String) stmtHandlerMeta.getValue("delegate.boundSql.sql");
        String newSql=sql+" limit ?,?";
        stmtHandlerMeta.setValue("delegate.boundSql.sql",newSql);
        PreparedStatement ps= (PreparedStatement) invocation.proceed();
        int count=ps.getParameterMetaData().getParameterCount();
        ps.setInt(count-1,(pageNum-1)*pageSize);
        ps.setInt(count,pageSize);
        return ps;
    }

    private boolean checkPage(int pageNum, Integer totalPage) throws Exception {
        if (pageNum>totalPage)
            return false;
        return true;
    }

    private int getTotal(Invocation invocation, MetaObject stmtHandlerMeta, BoundSql boundSql) throws Throwable {
        MappedStatement mappedStatement= (MappedStatement) stmtHandlerMeta.getValue("delegate.mappedStatement");
        Configuration configuration=mappedStatement.getConfiguration();
        String sql= (String) stmtHandlerMeta.getValue("delegate.boundSql.sql");
        String countSql="select count(*) as total from ("+sql+") $_paging";
        Connection conn= (Connection) invocation.getArgs()[0];
        PreparedStatement ps=null;
        int total=0;
        try {
            ps=conn.prepareStatement(countSql);
            BoundSql bs=new BoundSql(configuration,countSql,boundSql.getParameterMappings(),boundSql.getParameterObject());
            ParameterHandler ph=new DefaultParameterHandler(mappedStatement,boundSql.getParameterObject(),bs);
            ph.setParameters(ps);
            ResultSet rs=ps.executeQuery();
            while (rs.next())
                total=rs.getInt("total");
        }finally {
            if(ps!=null)
                ps.close();
        }
        return total;
    }

    private Page getPage(Object paramObject) {
        if(paramObject==null)
            return null;
        Page page=null;
        if(paramObject instanceof Map){
            Map<String,Object> paramMap= (Map<String, Object>) paramObject;
            for(Map.Entry<String,Object> entry:paramMap.entrySet()){
                if(entry.getValue() instanceof Page) {
                    return (Page) entry.getValue();
                }
            }
        }else if(paramObject instanceof Page){
            return (Page) paramObject;
        }
        return page;
    }

    @Override
    public Object plugin(Object o) {
        //Plugin工具类的wrap方法用来包装插件
        return Plugin.wrap(o,this);
    }

    @Override
    public void setProperties(Properties properties) {
        defaultPage=1;
        defaultPageSize=5;
    }
}

3)配置:

首先将其注入为Bean:

    @Bean
    public PagePlugin pagePlugin(){
        return new PagePlugin();
    }

然后修改Mapper、Service、ServiceImpl,增加 getAllUsers(@Param Page page) 方法

然后写SQL语句(UserMapper.xml):

<select id="getAllUsers" resultMap="BaseResultMap">
    select * from user
</select>

然后在Controller添加相应方法:

    @GetMapping("/getAllUsers")
    public List<User> getAll(@RequestParam int page,@RequestParam int pageSize){
        Page page=new Page();
        page.setPage(page);
        page.setPageSize(pageSize);
        return mapper.getAllUser(page);
    }

4)测试:(共6条测试数据)

GET /getAllUsers?page=1&pageSize=4

[
    {
        "id": 1,
        "userName": "zhangsan",
        "age": 1
    },
    {
        "id": 2,
        "userName": "lisi",
        "age": 2
    },
    {
        "id": 3,
        "userName": "wangwu",
        "age": 3
    },
    {
        "id": 4,
        "userName": "zhaoliu",
        "age": 4
    }
]

GET /getAllUsers?page=2&pageSize=4

[
    {
        "id": 5,
        "userName": "zhouqi",
        "age": 5
    },
    {
        "id": 6,
        "userName": "wangba",
        "age": 6
    }
]

GET /getAllUsers?page=3&pageSize=4

[
    {
        "id": 1,
        "userName": "zhangsan",
        "age": 1
    },
    {
        "id": 2,
        "userName": "lisi",
        "age": 2
    },
    {
        "id": 3,
        "userName": "wangwu",
        "age": 3
    },
    {
        "id": 4,
        "userName": "zhaoliu",
        "age": 4
    },
    {
        "id": 5,
        "userName": "zhouqi",
        "age": 5
    },
    {
        "id": 6,
        "userName": "wangba",
        "age": 6
    }
]

猜你喜欢

转载自blog.csdn.net/u010670411/article/details/84592406