前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Mybatis中sql拦截增强-AOP+interceptor实现分页和排序

Mybatis中sql拦截增强-AOP+interceptor实现分页和排序

作者头像
路行的亚洲
发布2021-03-04 15:37:22
2.6K1
发布2021-03-04 15:37:22
举报
文章被收录于专栏:后端技术学习后端技术学习

mybatis的执行的大概过程:首先需要有sqlSessionFactroy,然后通过sqlSessionFactory拿到sqlSession,然后通过sqlSession调用getMapper拿到代理的接口,然后拿到代理的接口的信息mapperInterface,从而找到需要执行的具体的方法中的sql方法,,如果执行过,同时没有发生改变的话,则直接返回结果,否则会进行更新,同时如果执行过的话,会直接返回结果,此时会看到methodCache中有我们执行过的方法。

mybatis中,如果想进行sql的拦截,需要对其基于interceptor做拦截。因为mybatis的执行中,我们需要获取它的boundSql,而获取boundSql,需要获取MappedStatement,而MappedStatement可以在StatementHandler语句处理器中找到,因此可以在此基础上获取,通过反射的方式获取,此时可以用到插件模块中的invocation对象获取,然后对其进行增强。

基于interceptor可以实现sql的完整打印,除了实现打印之外。其实还可以实现分页和排序,下面的分页和排序基于aop+mybatis的interceptor实现。其本质还是对mappedStament的boundSql进行增强。

下面的项目来源于github,通过这个我们可以很好的学习mybatis中插件interceptor的使用。

首先定义分页元注解:

代码语言:javascript
复制
/**
 * 自定义分页注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Limit {

    /**
     * 当前页面
     * 
     * @return
     */
    int page() default 0;

    /**
     * 每页显示数量
     * 
     * @return
     */
    int pageSize() default 10;
}

定义排序元注解:

代码语言:javascript
复制
/**
 * 自定义排序注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OrderBy {


    /**
     * 表的别名
     */
    String tableAlias() default "";

    /**
     * 排序字段
     */
    String orderColumn() default "";


    /**
     * ASC/DESC 默认倒序
     * @return
     */
    boolean isAsc() default false;

}

定义基础切面处理类:

代码语言:javascript
复制
/**
 * @ClassName BaseAspectAbstract
 * @Description 基础切面处理类,每一个Spring的切面都需要去继承此抽象类
 * @Date
 **/
public abstract class BaseAspectAbstract {

    private static TreeMap<Integer, SQLLanguage> CONTAINERS = new TreeMap<>();

    // 放入sql 切点、sql类型、sql
    public void putSQL(JoinPoint point, SQLEnums sqlEnums, SQLLanguage sqlLanguage) {
        CONTAINERS.put(sqlEnums.getId(), sqlLanguage);
        // 获取方法里的参数
        Object parmas = point.getArgs()[0];
        Map map = (Map)parmas;
        map.put("SQL", getSQL());
    }

    public TreeMap<Integer, SQLLanguage> getSQL() {
        return CONTAINERS;
    }

}

进行分页切面:

代码语言:javascript
复制
@Component
@Aspect
@Order(3) //拼接sql时的顺序
public class LimitAspect extends BaseAspectAbstract {

    @Pointcut("@annotation(com.mybatis.interceptor.annotation.Limit)")
    public void limitCut() {}

    @Before("limitCut()")
    public void limit(JoinPoint point) {
        StringBuilder limitBuilder = new StringBuilder(" LIMIT ");
        MethodSignature methodSignature = (MethodSignature)point.getSignature();
        // 获得对应注解
        Limit limit = methodSignature.getMethod().getAnnotation(Limit.class);
        if (!StringUtils.isEmpty(limit)) {
            limitBuilder.append(limit.page()).append(",").append(limit.pageSize());
            putSQL(point, LIMIT, new SQLLanguage(limitBuilder.toString()));
        }

    }
}

进行排序切面:

代码语言:javascript
复制
@Component
@Aspect
@Order(2)
public class OrderByAspect extends BaseAspectAbstract {

    // 切点:对注解中的特定注解进行拦截,进行增强
    @Pointcut("@annotation(com.mybatis.interceptor.annotation.OrderBy)")
    public void orderByCut() {}

    // 执行切点操作,将其进行增强,放入排序
    @Before("orderByCut()")
    public void orderBy(JoinPoint point) {
        StringBuilder orderByBuilder = new StringBuilder(" ORDER BY ");
        MethodSignature methodSignature = (MethodSignature)point.getSignature();
        // 获得对应注解
        OrderBy orderBy = methodSignature.getMethod().getAnnotation(OrderBy.class);
        if (!StringUtils.isEmpty(orderBy)) {
            String sort = orderBy.isAsc() ? " asc " : " desc";
            orderByBuilder.append(orderBy.orderColumn()).append(sort);
            putSQL(point, ORDERBY, new SQLLanguage(orderByBuilder.toString()));
        }
    }
}

枚举sql顺序:

代码语言:javascript
复制
/**
 * sql类型枚举
 */
public enum SQLEnums {

    /**
     * 数字越靠前 则拼接SQL语句越靠前执行,目前拼接顺序为
     * SELECT * FROM table GROUP BY ORDER BY xxx LIMIT 0, 10
     */
    LIKE(1, "LIKE"), GROUPBY(2, "GROUP BY"), ORDERBY(3, "ORDER BY"), LIMIT(4, "LIMIT");

    private int id;

    private String condition;

    SQLEnums(int id, String condition) {
        this.id = id;
        this.condition = condition;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getCondition() {
        return condition;
    }

    public void setCondition(String condition) {
        this.condition = condition;
    }
}

执行sql增强的注解放置在serviceImpl里面:

代码语言:javascript
复制
@RequestMapping("/nba")
public class PlayController {

    @Autowired
    private PlayerService playerService;

    @RequestMapping("/player")
    public List<Player> getList(Map<String, Object> params) {
        List<Player> players = playerService.getList(params);
        return players;
    }
}

执行sql增强:

代码语言:javascript
复制
@Service
public class PlayerServiceImpl implements PlayerService {

    @Autowired
    private PlayerMapper playerMapper;

    // 加入自定义注解,方便切点进行增强
    @OrderBy(orderColumn = "height")
    @Limit()
    @Override
    public List<Player> getList(Map<String, Object> params) {
        return playerMapper.getList(params);
    }
}

执行到这里会执行动态代理,然后执行sql拦截。

执行sql拦截:

代码语言:javascript
复制
@Intercepts(@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}))
@Component
public class DataFilterInterceptor extends AbstractSqlParserHandler implements Interceptor {

    // 拦截器
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        // SQLLanguage 解析
        sqlParser(metaObject);
        // 非查询操作
        MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement");
        if (!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {
            return invocation.proceed();
        }

        // 取出原始SQL 取出参数
        BoundSql boundSql = (BoundSql)metaObject.getValue("delegate.boundSql");
        String dataSql = boundSql.getSql();
        Object paramObj = boundSql.getParameterObject();
        Map map = (Map)paramObj;
        String sqlLanguage = getSQLLanguage(map);
        String sql = dataSql + sqlLanguage;
        // 重写sql
        metaObject.setValue("delegate.boundSql.sql", sql);
        return invocation.proceed();
    }

    // 插件
    @Override
    public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    @Override
    public void setProperties(Properties properties) {

    }

    // 获取sql语句
    private String getSQLLanguage(Map<String, Object> map) {
        TreeMap<Integer, SQLLanguage> sqlMap = (TreeMap)map.get("SQL");
        StringBuilder sqlBuilder = new StringBuilder();
        for (Map.Entry treeMap : sqlMap.entrySet()) {
            SQLLanguage sql = (SQLLanguage)treeMap.getValue();
            if (null != sql) {
                sqlBuilder.append(sql);
            }
        }
        return sqlBuilder.toString();
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-02-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 后端技术学习 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档