美文网首页
mybatis动态查询框架

mybatis动态查询框架

作者: 骆宏 | 来源:发表于2018-08-23 19:03 被阅读99次

1.mybatis通用查询框架

1.1.该框架的设计背景

在使用数据库时,我们经常需要根据用户的输入条件来查询内容,所以代码中各个业务模块都有着相同的逻辑代码,仅仅是业务不一样,所以我们怎么抽象出变与不变,形成一个基础组件,使得各个业务模块通用呢?

通过抽象,我们可以观察到,上面的核心问题在于不变,比如一个系统有50个模块,每个模块仅仅是对应的业务不一样(表不一样),但是查询的过程是不变的。

  • 变,指的是业务模块
  • 不变,指的是根据用户的输入,去数据库查询的过程

1.2.常用的解决方案

  • 使用mybatis的dynamic sql
  • 在业务层捕获param,然后构建where sql

1.3.如何做到动态扩展查询条件

在使用mybatis的dynamic sql时,有一个很明显的缺点,那就是mapper文件的中的查询条件是固定的,如果要动态的添加查询条件时,需要同时更新mapper.xml以及parameterType的查询对象
假设我们为下面的sql添加一个新的查询条件,age > 25

select * from student
<where>
    <if test="name != null and name != ''">
        name = #{name}
    </if>
    <if test="tel != null and tel != ''">
        and tel = #{tel}
    </if>
</where>

那么我们需要将上面的sql修改为如下的样子

select * from student
<where>
    <if test="name != null and name != ''">
        name = #{name}
    </if>
    <if test="tel != null and tel != ''">
        and tel = #{tel}
    </if>
    <if test="age != null">
        and age > #{age}
    </if>
</where>

2.mybatis # $的区别

  • #{} 解析为一个 JDBC 预编译语句(prepared statement)的参数标记符
  • ${} 解析为一个存粹的字符串替换

比如下面的例子,我们假设name = 'luohong'

name = #{name},将会被解析为name = ?,然后jdbc PreparedStatement会动态的将luohong的值替换掉?,也就是name = 'luohong'
name = ${name},那么将会被直接解析为name = 'luohong'

2.1

既然mybatis的dynamic sql有两个占位符,那么我们该使用哪一个呢?

大部分的情况下,我们都是用#{},因为这种方式,可以避免一些sql注入的问题

3.利用mybatis的$特性来创建一个动态查询sql框架

还是上面的sql,假设我们将该sql修改为下面的格式

select * from student
<where>
    ${whereSql}
</where>

其中whereSql代表的是业务层已经处理好的查询条件sql,那么在mapper文件这一侧,我们就实现了动态增加、删除查询条件的特性了

  • 比如A模块:whereSql = "name = 'luohong'";
  • 比如B模块:whereSql = "name = 'luohong' or name = 'xiemeijiao'";
  • 比如C模块:whereSql = "name = 'luohong' and tel = '15013338888'";
  • 比如D模块:whereSql = "name = 'luohong' and age > 25";

4.设计思路与实现

sql常用的比较操作

  • 等于
  • 不等于
  • 大于
  • 大于等于
  • 小于
  • 小于等于
  • between ... and ...
  • LIKE
  • IN

支持数据类型,后续感兴趣的同学可以自行扩展,比如TimeStamp等

  • String
  • Date
  • Number

5.核心代码

先看一个类图


20180823191055.png

ICondition,抽象出一个条件表达式,比如:name = 'luohong', age > 15

package luohong;

/**
 * 抽象的查询条件
 * 这里面使用模板设计模式,目前支持Date,Number,String三种数据类型
 * @author luohong
 */
public interface ICondition {
    /**
     * 查询条件的名字
     * @return
     */
    String name();

    /**
     * 查询条件的比较类型
     * @return
     */
    OperationEnum operation();

    /**
     * 查询条件的值
     * @return
     */
    Object value();

    /**
     * 查询条件生成的sql
     * @return
     */
    String sql();
}

OperationEnum的抽象,这里面使用枚举

package luohong;

public enum OperationEnum {
    EQ("="),
    NEQ("!="),
    GTE(">="),
    GT(">"),
    LTE("<="),
    LT("<"),
    LIKE("LIKE"),
    IN("IN");

    private String value;

    /**
     * 比较类型
     * @param operation
     */
    OperationEnum(String operation){
        value = operation;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

DateCondition的实现

package luohong;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

public class DateCondition implements ICondition {
    private String name;
    private Date value;
    private List<Date> values;
    private OperationEnum operation;

    public DateCondition(String name, OperationEnum operation, Date value){
        if(operation == OperationEnum.IN){
            throw new RuntimeException("Not support IN operation for single value, please use new StringCondition(String name, List<String> values)");
        }

        if(operation == OperationEnum.LIKE){
            throw new RuntimeException("Not supper LIKE operation for date condition");
        }

        this.name = name;
        this.operation = operation;
        this.value = value;
    }

    public DateCondition(String name, List<Date> values){
        this.name = name;
        this.operation = OperationEnum.IN;
        this.values = values;
    }

    @Override
    public String name() {
        return name;
    }

    @Override
    public OperationEnum operation() {
        return operation;
    }

    @Override
    public Object value() {
        return value;
    }

    @Override
    public String sql() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

        switch (operation()){
            case EQ:
            case NEQ:
            case GT:
            case GTE:
            case LT:
            case LTE:
                return name + " " +  operation.getValue() +  " '" + sdf.format(value) + "'";
            case IN:
                StringBuilder sb = new StringBuilder();
                sb.append(name() + " " + operation().getValue() + " (");
                for(int i=0; i<values.size(); i++){
                    if(i == values.size() - 1){
                        sb.append("'" + sdf.format(values.get(i)) + "'");
                    }else{
                        sb.append("'" + sdf.format(values.get(i)) + "',");
                    }
                }
                sb.append(")");
                return sb.toString();
        }
        throw new RuntimeException(name() + operation() + value() + " generate sql fail");
    }

    public static void main(String[] args) {
        System.out.println(new DateCondition("name", OperationEnum.EQ, new Date()).sql());
        System.out.println(new DateCondition("name", OperationEnum.NEQ, new Date()).sql());
        System.out.println(new DateCondition("name", OperationEnum.LT, new Date()).sql());
        System.out.println(new DateCondition("name", OperationEnum.LTE, new Date()).sql());
        System.out.println(new DateCondition("name", OperationEnum.GT, new Date()).sql());
        System.out.println(new DateCondition("name", OperationEnum.GTE, new Date()).sql());
        System.out.println(new DateCondition("name", Arrays.asList(new Date(), new Date())).sql());
    }
}

NumberCondition的实现

package luohong;
import java.util.Arrays;
import java.util.List;

public class NumberCondition implements ICondition {
    private String name;
    private Number value;
    private List<Number> values;
    private OperationEnum operation;

    public NumberCondition(String name, OperationEnum operation, Number value){
        if(operation == OperationEnum.IN){
            throw new RuntimeException("Not support IN operation for single value, please use new StringCondition(String name, List<String> values)");
        }
        if(operation == OperationEnum.LIKE){
            throw new RuntimeException("Not support LIKE operation for number condition");
        }

        this.name = name;
        this.operation = operation;
        this.value = value;
    }

    public NumberCondition(String name, List<Number> values){
        this.name = name;
        this.operation = OperationEnum.IN;
        this.values = values;
    }

    @Override
    public String name() {
        return name;
    }

    @Override
    public OperationEnum operation() {
        return operation;
    }

    @Override
    public Object value() {
        return value;
    }

    @Override
    public String sql() {
        switch (operation()){
            case EQ:
            case NEQ:
            case GT:
            case GTE:
            case LT:
            case LTE:
                return name + " " +  operation.getValue() + " " + value;
            case IN:
                StringBuilder sb = new StringBuilder();
                sb.append(name() + " " + operation().getValue() + " (");
                for(int i=0; i<values.size(); i++){
                    if(i == values.size() - 1){
                        sb.append("" + values.get(i) + "");
                    }else{
                        sb.append("" + values.get(i) + ",");
                    }
                }
                sb.append(")");
                return sb.toString();
        }
        throw new RuntimeException(name() + operation() + value() + " generate sql fail");
    }

    public static void main(String[] args) {
        System.out.println(new NumberCondition("name", OperationEnum.EQ, 10).sql());
        System.out.println(new NumberCondition("name", OperationEnum.NEQ, 100L).sql());
        System.out.println(new NumberCondition("name", OperationEnum.LT, 1).sql());
        System.out.println(new NumberCondition("name", OperationEnum.LTE, 100.2).sql());
        System.out.println(new NumberCondition("name", OperationEnum.GT, 1000).sql());
        System.out.println(new NumberCondition("name", OperationEnum.GTE, 100F).sql());
        System.out.println(new NumberCondition("name", Arrays.asList(1, 2, 3, 4, 5)).sql());
    }
}

StringCondition的实现

package luohong;

import java.util.Arrays;
import java.util.List;

public class StringCondition implements ICondition {
    private String name;
    private String value;
    private List<String> values;
    private OperationEnum operation;

    public StringCondition(String name, OperationEnum operation, String value){
        if(operation == OperationEnum.IN){
            throw new RuntimeException("Not support IN operation for single value, please use new StringCondition(String name, List<String> values)");
        }

        this.name = name;
        this.operation = operation;
        this.value = value;
    }

    public StringCondition(String name, List<String> values){
        this.name = name;
        this.operation = OperationEnum.IN;
        this.values = values;
        this.value = values.toString();
    }

    @Override
    public String name() {
        return name;
    }

    @Override
    public OperationEnum operation() {
        return operation;
    }

    @Override
    public Object value() {
        return value;
    }

    @Override
    public String sql() {
        switch (operation()){
            case EQ:
            case NEQ:
            case GT:
            case GTE:
            case LT:
            case LTE:
                return name + " " +  operation.getValue() +  " '" + value + "'";
            case LIKE:
                return name + " " + operation.getValue() +  " '%" + value + "%'";
            case IN:
                StringBuilder sb = new StringBuilder();
                sb.append(name() + " " + operation().getValue() + " (");
                for(int i=0; i<values.size(); i++){
                    if(i == values.size() - 1){
                        sb.append("'" + values.get(i) + "'");
                    }else{
                        sb.append("'" + values.get(i) + "',");
                    }
                }
                sb.append(")");
                return sb.toString();
        }
        throw new RuntimeException(name() + operation() + value() + " generate sql fail");
    }

    public static void main(String[] args) {
        System.out.println(new StringCondition("name", OperationEnum.EQ, "luohong").sql());
        System.out.println(new StringCondition("name", OperationEnum.NEQ, "luohong").sql());
        System.out.println(new StringCondition("name", OperationEnum.LT, "luohong").sql());
        System.out.println(new StringCondition("name", OperationEnum.LTE, "luohong").sql());
        System.out.println(new StringCondition("name", OperationEnum.GT, "luohong").sql());
        System.out.println(new StringCondition("name", OperationEnum.GTE, "luohong").sql());
        System.out.println(new StringCondition("name", OperationEnum.LIKE, "luohong").sql());
        System.out.println(new StringCondition("name", Arrays.asList("luohong", "luoyan")).sql());
    }
}

为了简化调用,我们使用一个工厂类,ConditionFactory

package luohong;

import java.util.Date;
import java.util.List;

/**
 * 工厂模式
 * 用于创建condition
 */
public class ConditionFactory {
    public static ICondition newInstance(String name, OperationEnum operation, String value){
        return new StringCondition(name, operation, value);
    }

    public static ICondition newInstance(String name, OperationEnum operation, Number value){
        return new NumberCondition(name, operation, value);
    }

    public static ICondition newInstance(String name, OperationEnum operation, Date value){
        return new DateCondition(name, operation, value);
    }

    public static ICondition newInstanceDates(String name, List<Date> values){
        return new DateCondition(name, values);
    }

    public static ICondition newInstanceStrings(String name, List<String> values){
        return new StringCondition(name, values);
    }

    public static ICondition newInstanceNumbers(String name, List<Number> values){
        return new NumberCondition(name, values);
    }
}

最后,我们提供一个聚合类,并且提供用户自定义扩展sql的方式,比如用户可以直接添加age = 15这种扩展,无需了解condition的设计,这个特性是开闭原则的应用点

package luohong;

import java.util.*;

/**
 * 用于解析condition,然后生成whereSql
 */
public class ConditionHandler {
    private Set<ICondition> conditions = new LinkedHashSet<>();
    /**
     * 用户自定义的sql片段,提供一个扩展点,由用户注入动态的condtion条件
     */
    private Set<String> conditionSqls = new LinkedHashSet<>();

    public Set<ICondition> getConditions(){
        return conditions;
    }

    public void addCondition(ICondition condition){
        this.conditions.add(condition);
    }

    public void addConditionSql(String sql){
        this.conditionSqls.add(sql);
    }

    /**
     * 便利所有的condition,然后生成所有一个sql片段
     * @return
     */
    public String genWhereSql(){
        StringBuilder sb = new StringBuilder();
        for(ICondition condition: conditions){
            sb.append(condition.sql());
            sb.append(" AND ");
        }

        for(String conditionSql: conditionSqls){
            sb.append(conditionSql);
            sb.append(" AND ");
        }

        String whereSql = sb.toString();
        if(whereSql != null && !whereSql.equals("")){
            whereSql = whereSql.substring(0, whereSql.length() - 4);
        }

        return whereSql;
    }

    public static void main(String[] args) {
        ConditionHandler requestParamSqlHandler = new ConditionHandler();
        requestParamSqlHandler.addConditionSql("age > 15");
        requestParamSqlHandler.addConditionSql("name = 'luohong'");
        requestParamSqlHandler.addConditionSql("tel = '15013336884'");
        requestParamSqlHandler.addCondition(ConditionFactory.newInstance("name", OperationEnum.EQ, "luohong"));
        requestParamSqlHandler.addCondition(ConditionFactory.newInstance("name", OperationEnum.NEQ, "luohong"));
        requestParamSqlHandler.addCondition(ConditionFactory.newInstance("name", OperationEnum.LT, "luohong"));
        requestParamSqlHandler.addCondition(ConditionFactory.newInstance("name", OperationEnum.LTE, "luohong"));
        requestParamSqlHandler.addCondition(ConditionFactory.newInstance("name", OperationEnum.GT, "luohong"));
        requestParamSqlHandler.addCondition(ConditionFactory.newInstance("name", OperationEnum.GTE, "luohong"));
        requestParamSqlHandler.addCondition(ConditionFactory.newInstance("name", OperationEnum.LIKE, "luohong"));

        requestParamSqlHandler.addCondition(ConditionFactory.newInstance("name", OperationEnum.EQ, 100));
        requestParamSqlHandler.addCondition(ConditionFactory.newInstance("name", OperationEnum.LT, 100));
        requestParamSqlHandler.addCondition(ConditionFactory.newInstance("name", OperationEnum.LTE, 100));
        requestParamSqlHandler.addCondition(ConditionFactory.newInstance("name", OperationEnum.GTE, 100));
        requestParamSqlHandler.addCondition(ConditionFactory.newInstance("name", OperationEnum.NEQ, 100));

        requestParamSqlHandler.addCondition(ConditionFactory.newInstance("name", OperationEnum.EQ, new Date()));
        requestParamSqlHandler.addCondition(ConditionFactory.newInstance("name", OperationEnum.NEQ, new Date()));
        requestParamSqlHandler.addCondition(ConditionFactory.newInstance("name", OperationEnum.GT, new Date()));
        requestParamSqlHandler.addCondition(ConditionFactory.newInstance("name", OperationEnum.GTE, new Date()));
        requestParamSqlHandler.addCondition(ConditionFactory.newInstance("name", OperationEnum.LT, new Date()));
        requestParamSqlHandler.addCondition(ConditionFactory.newInstance("name", OperationEnum.LTE, 100));

        requestParamSqlHandler.addCondition(ConditionFactory.newInstanceDates("name", Arrays.asList(new Date())));
        requestParamSqlHandler.addCondition(ConditionFactory.newInstanceStrings("name", Arrays.asList("luohong", "xiemeijiao")));
        requestParamSqlHandler.addCondition(ConditionFactory.newInstanceNumbers("name", Arrays.asList(1, 2, 3, 4)));
        System.out.println(requestParamSqlHandler.genWhereSql());
    }
}

程序输出结果

name = 'luohong' 
AND name != 'luohong' 
AND name < 'luohong' 
AND name <= 'luohong' 
AND name > 'luohong' 
AND name >= 'luohong' 
AND name LIKE '%luohong%' 
AND name = 100 
AND name < 100 
AND name <= 100 
AND name >= 100 
AND name != 100 
AND name = '2018-08-23' 
AND name != '2018-08-23' 
AND name > '2018-08-23' 
AND name >= '2018-08-23' 
AND name < '2018-08-23' 
AND name <= 100 
AND name IN ('2018-08-23') 
AND name IN ('luohong','xiemeijiao') 
AND name IN (1,2,3,4) 
AND age > 15 
AND name = 'luohong' 
AND tel = '15013336884'

6.总结

上面的代码虽然简单,但是却使用了<b>策略设计模式</b>,<b>工厂设计模式</b>,以及应用了开闭原则使得扩展性大大提高。

7.扩展性

由于上面的demo仅仅是花了一小点时间制作,还有些不完善的地方,大家有兴趣的可以进行这些方面的思考

  • 支持更多的数据类型扩展,比如Timestame,boolean等
  • 比较类型的扩展,比如between ... and ...
  • AND OR扩展,上面默认都是AND规则,这里面可以继续加入OR的规则
  • sql注入的规避
  • ...

相关文章

  • mybatis动态查询框架

    1.mybatis通用查询框架 1.1.该框架的设计背景 在使用数据库时,我们经常需要根据用户的输入条件来查询内容...

  • MyBatis框架动态查询Sql

    今天写一个接口,查询监测数据,传感器有100多个监测因子,也就是数据库中一条数据对应100多个字段,前端勾选需要查...

  • 大连滕泰科技学习笔记2020-05-21

    1,mybatis框架的CRUD 1,1 mybatis框架的查询一个参数的查询没有参数的参数多个参数的查询使用m...

  • 第六章 初识Mybatis

    什么是Mybatis Mybatis是一个支持普通SQL查询、存储过程、高级映射的持久层框架 Mybatis框架也...

  • Mybatis

    Mybatis 介绍MyBatis是支持普通 SQL 查询,存储过程和高级映射的优秀持久 层框架。MyBatis ...

  • mybatis动态查询

    mybatis 的动态sql语句是基于OGNL表达式的。可以方便的在 sql 语句中实现某些逻辑. 总体说来myb...

  • SSM面试

    Mybatis 简介 mybatis支持普通sql查询,存储过程和高级映射的优秀持久层框架,Mybatis消除了几...

  • 使用MyBatis动态SQL表达式时遇到的“坑”

    现有一项目,ORM框架使用MyBatis,在进行列表查询时,选择一状态(值为0)通过动态SQL拼接where条件但...

  • MyBatis 动态 SQL 之通用 where

    使用MyBatis过程中无可避免的要用到动态SQL做查询;有关MyBatis动态SQL的基础不在此记录,需要的同学...

  • 【mybatis xml】数据层框架应用--Mybatis 基于

    使用MyBatis框架进行持久层开发 MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持久层框架。 M...

网友评论

      本文标题:mybatis动态查询框架

      本文链接:https://www.haomeiwen.com/subject/akwmiftx.html