基本大多数的项目,条件查询模块都是每个领域对象都有一个条件查询的controller,多个界面中甚至有很多重复的条件,例如编号、名称等属性。那么我们能不能只写一个通用查询接口,来适用与大多数的条件查询呢?在service层中的逻辑处理,我们封装为一个方法返回sql语句,这样即便有特殊情况使我们的通用查询不能处理时,也能及时的更改sql语句。
sql语句大家都很熟悉,例如
select userNo, name from tbUser where id = '3d96926a-0124-4c1f-ae62-704ad3ae5df3
'
显而易见,我们要解决的是如何获取sql中需要的信息,例如表名、where后面的条件。
首先,表名肯定是不能由前端来传递过来,这样并不符合我们的代码规范,那我们该怎么拿到我们的表名呢?我们可以将我们的请求url中添加领域对象的名称,例如下图controller
通用查询只能适用于后端,对前端来讲还是每个对象多要调用一次后台。前台将{entity}中填入想要查询对象的名称。
后端获取前端所传的领域对象名,根据代码规范,我们的领域对象都应在同一包下,加上所在包路径,通过反射来获取领域对象类的信息。
接下来我们要通过领域对象类来获取对应数据库的表名。
首先我们要自定义一个注解,功能很简单,根据实体类来获取对应的数据库表名。自定义注解代码如下
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface TableName {
public String name();
}
该注解的使用如下
我们首先通过前端所传的领域对象名称获取到该类,,然后根据我们的自定义注解获取表名
这个接受参数是我自己封装的一个类,很简单
public class SearchParam {
/**
* 字段名称
*/
private String fieldName;
/**
* 操作符 默认为 =
*/
private String op;
/**
* 字段值
*/
private String fieldValue;
}
现在表名有了,查询条件有了,我们可以开始拼装sql语句了
StringBuilder sb = new StringBuilder();
if (searchParams.size() > 0) {
sb.append(" where ");
for (SearchParam searchParam : searchParams) {
if (searchParam.getFieldName().contains(".")) {
sb.append(searchParam.getFieldName());
} else {
sb.append(alias).append(".").append(searchParam.getFieldName());
}
if (StringUtils.isBlank(searchParam.getOp())) {
sb.append(" = ");
} else {
sb.append(" ").append(searchParam.getOp()).append(" ");
}
try {
Class classzz = Class.forName(className);
Field field = classzz.getDeclaredField(searchParam.getFieldName());
String simpleName = field.getType().getSimpleName();
if ("String".equals(simpleName)) {
if ("like".equals(searchParam.getOp())) {
sb.append("'%").append(filter(searchParam.getFieldValue())).append("%'");
} else {
sb.append("'").append(filter(searchParam.getFieldValue())).append("'");
}
} else if ("Integer".equals(simpleName) || "BigDecimal".equals(simpleName) || "LocalDateTime".equals(simpleName)) {
sb.append(filter(searchParam.getFieldValue()));
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
sb.append(" and ");
}
sb.delete(sb.length() - 4, sb.length());
}
return String.format("select * from %s %s %s", tableName, alias, ObjectUtils.isEmpty(sb) ? "" : sb);
这个方法直接返回拼装好的sql语句,这样我们就只能使用mybatis的$符了,但是$无法防sql注入,所以我们要在程序中防止sql注入,具体代码如下
/**
* 防止sql注入
*
* @param param 过滤前字符串
* @return 过滤后字符串
*/
public static String filter(String param) {
String regex = "'|%|--|and|or|not|use|insert|delete|update|select|count|group|union|" +
"|create|drop|truncate|alter|grant|execute|xp_cmdshell|call|declare|source|sql";
if (param == null) {
return param;
}
return param.replaceAll("(?i)" + regex, "");
}
方法返回sql语句,可有特殊需求可根据实际情况进行更改。
仅提供思路供借鉴,以上代码为娱乐时所写,可运行及正常使用,但不建议。