书接上回,在mybatis查询之前可手动调用公共方法,将特殊字符进行转义。commons-beanutils和反射解决查询%和_数据问题
但是,这么做还是不够方便,因为每次我都要在查询前调用方法,如果我忘记调用了,那测试大神又得给版本不通过了,这不就完蛋了个锤子了吗!
之前想的是用切面去做,但是发现切面方式不好,原因在于在切入获取参数时,存在不确定性,并且较难获取到其入参的实体类。为了满足测试大神的要求,于是尝试使用mybatis拦截器,拦截select方法的入参,对入参进行转义,并且对存在入参为实体类时,通过注解+反射的方式获取需要转义的字符。其中@Param传参的话是不需要定义注解的,注解的目的是在入参为实体类时进行转义。
定义注解+反射工具类
定义注解@SpecChar
/**
* 特殊字符处理注解,主要用来解决测试大神提的%和_查询问题
* 希望我司测试大神能满意
* 希望我司测试大神增强自己的实力,不要只会体验
* 真正的测试比开发要牛
* 共勉!
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SpecChar {
}
引入commons-beanutils包,建议引入1.9.3
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>
定义工具类setSpecChar(),将要转义的实体类传入此方法!
/**
* 处理特殊字符‘%’、‘_’
* @param object
* @return
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws NoSuchMethodException
*/
public static void setSpecChar(Object object) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
if (object == null) {
return ;
}
for (Field field : getAllFields(object)) {
if (field.getAnnotation(SpecChar.class) != null) {
String name = field.getName();
Object value = PropertyUtils.getSimpleProperty(object, name);
if (value != null) {
String valueStr = (String) value;
if (valueStr.contains("%")) {
valueStr = valueStr.replaceAll("\\%","\\\\%");
}
if (valueStr.contains("_")) {
valueStr = valueStr.replaceAll("\\_","\\\\_");
}
PropertyUtils.setProperty(object, name, valueStr);
}
}
}
}
/**
* 获取类的所有属性,包括父类
*
* @param object
* @return
*/
public static Field[] getAllFields(Object object) {
Class<?> clazz = object.getClass();
List<Field> fieldList = new ArrayList<>();
while (clazz != null) {
fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
clazz = clazz.getSuperclass();
}
Field[] fields = new Field[fieldList.size()];
fieldList.toArray(fields);
return fields;
}
定义dealSpecChar,将要转义的字符串传入此方法!
public static String dealSpecChar(String valueStr) {
if (valueStr.contains("%")) {
valueStr = valueStr.replaceAll("\\%","\\\\%");
}
if (valueStr.contains("_")) {
valueStr = valueStr.replaceAll("\\_","\\\\_");
}
return valueStr;
}
定义MyBatis拦截器
定义拦截器,在查询方法类型为Select时,将带有特殊字符的参数,进行转义。本案例中,也引入了update和insert方法,用于增强更新和创建时间,亦可进行参考。
/**
* mybatis拦截获取自动创建时间,并且查询时对特殊字符%、_进行转义
* @author 椰子皮
*/
@Slf4j
@Component
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
@Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class })
})
public class MybatisInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
Object parameter = invocation.getArgs()[1];
if (SqlCommandType.INSERT == sqlCommandType || SqlCommandType.UPDATE == sqlCommandType) {
if (parameter == null) {
return invocation.proceed();
}
Field[] fields = ConvertUtils.getAllFields(parameter);
for (Field field : fields) {
try {
if ("updateTime".equals(field.getName()) || "createTime".equals(field.getName())) {
field.setAccessible(true);
Object local_createDate = field.get(parameter);
field.setAccessible(false);
if (local_createDate == null || local_createDate.equals("")) {
field.setAccessible(true);
//ConvertUtils.getNowTime() 为获取当前时间,此处省略其实现方式
field.set(parameter, ConvertUtils.getNowTime());
field.setAccessible(false);
}
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
//如果是查询
else if (SqlCommandType.SELECT == sqlCommandType){
Object[] args = invocation.getArgs();
if (parameter instanceof MapperMethod.ParamMap) {
Map<Object, Object> paramMap = (Map) parameter;
Iterator<Map.Entry<Object, Object>> it = paramMap.entrySet().iterator();
//遍历map,并且将string类型的参数进行转义
while (it.hasNext()) {
Map.Entry<Object, Object> entry = it.next();
Object objectKey = entry.getKey();
if (objectKey instanceof String && entry.getValue() instanceof String) {
if ((StringUtils.isNotBlank(sql) && sql.contains("LIKE")) ||
(StringUtils.isNotBlank(sql) && sql.contains("like"))) {
if (paramMap.containsKey("page") &&
sql.contains("COUNT")) {
String value = (String) entry.getValue();
entry.setValue(ConvertUtils.dealSpecChar(value));
}
}
} else if (objectKey instanceof String && ((String) objectKey).contains("param")){
if (paramMap.containsKey("page") && sql.contains("COUNT")) {
ConvertUtils.setSpecChar(entry.getValue());
} else if (!paramMap.containsKey("page")) {
ConvertUtils.setSpecChar(entry.getValue());
}
}
}
}
}
return invocation.proceed();
}
@Override
public void setProperties(Properties properties) {
System.out.println("...................");
}
验证
根据测试大神的要求,当前按照设计思路已经编码完成,但是!咱不清楚到底能不能实现啊。还是得验证一下,测试大神的软件体验还是很重要的!
实践是检验真理的唯一标准!Just do it!
定义实体类TblCollectCamera(在需要搜索的字段cameraName设置定义好的注解@SpecChar)
/**
*
* @TableName tbl_collect_camera
*/
@TableName(value ="tbl_collect_camera")
@Data
public class TblCollectCamera implements Serializable {
/**
* 主键
*/
@TableId(type = IdType.AUTO)
private String id;
/**
*
*/
private String collectId;
/**
*
*/
@SpecChar
private String cameraName;
/**
*
*/
private String resCode;
/**
*
*/
private String updateTime;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
编写DAO层TblCollectCameraMapper
/**
* @author 椰子皮
* @description 针对表【tbl_collect_camera】的数据库操作Mapper
* @createDate 2022-12-05 14:45:45
* @Entity generator.domain.TblCollectCamera
*/
public interface TblCollectCameraMapper extends BaseMapper<TblCollectCamera>{
List<TblCollectCamera> getByCollectId(@Param("id") String id, @Param("cameraName") String cameraName);
IPage<TblCollectCamera> getTblCollectCameraByPage(Page<TblCollectCamera> page, @Param("tblCollectCamera") TblCollectCamera tblCollectCamera);
}
可以看到此处两个方法,入参分别为实体类TblCollectCamera和字段cameraName,按照mybatis拦截器中的代码,cameraName字段如果不加注解其实也是可以被转义的,注解的目的是在入参为实体类时进行转义
调用方法:
其中入参cameraName为特殊字符%
打断点观察:
可以看到mybatis拦截器拦截到了入参为一实体类tblCollectCamera,入参为cameraName=%,根据我们的代码判断,此会进入 else判断,即会使用反射+注解方式将带有@SpecChar注解的字段的参数进行转义
if (objectKey instanceof String && entry.getValue() instanceof String) {
String value = (String) entry.getValue();
entry.setValue(ConvertUtils.dealSpecChar(value));
} else {
ConvertUtils.setSpecChar(entry.getValue());
}
继续断点往下走:
发现cameraName已经被转义了。查看查询结果:
发现不会查询到所有数据,验证OK。另外单个查询的话此处就不演示了,如果仅仅是@Param这种单个字段的查询模式,是不需要加注解就可以实现特殊字符串处理的。
最后
希望我司的测试能够会一些基本的sql和linux命令,别只当个体验官,那你离开了这个行业啥也不是。
最后感谢公司秦总的图片转发支持!