背景:
维护公司内部系统的一个叫获取sql的接口时做的一些改进,该接口的功能是获取报表查询列表的的sql,可以用于直接在数据库里面查询出跟页面一样的数据。
公司原本的代码是每个获取sql接口都是各种StringBuilder拼接硬编码,再配上mybatis里面使用了动态的sql的话,也用java代码各种判断各种判断代替。这些代码的缺点:
- 代码十分臃肿,一个接口方法几百上千行的拼接代码。
- 可读性十分差,刚开始我开发新页面时,也按照这个方法拼接,过一段时间连自己写的代码都看不太懂逻辑。
- 很难维护,如果sql发生变化的话,就要改写该接口的代码,而臃肿加可读性差导致修改也比较困难(这就是我花时间改进的动力,修改太难了)。
- 刚好写了两三个接口之后把mybatis源码看了个大概,就尝试着修改一下,但是也可能有更好的办法,不过我改进后的方法也挺好用的了。就记录一下。不过我只敢改自己写的接口,历史的还是让他留着吧。
- 也可以用于查询拦截前面作日志输出。
修改:
private static final Field additionalParametersField = getAdditionalParametersField();
private static Field getAdditionalParametersField() {
try {
Field additionalParametersField = BoundSql.class.getDeclaredField("additionalParameters");
additionalParametersField.setAccessible(true);
return additionalParametersField;
} catch (NoSuchFieldException var1) {
return null;
}
}
/**
* @param methodId 这个参数是mapper接口方法的全限定名
* @param paramName 这个是参数名的集合 跟mapper.xml文件中sql语句使用的参数名相同
* @param paramValue 这个是参数值得集合 必须与上面的参数名位置一一对应
* @return
*/
protected String baseGetSql(String methodId,String[] paramName,Object[] paramValue) {
//将参数转换为Map,因为要获取BoundSql需要一个Map的参数。
Map<String, Object> paramMap = convertParamToMap(paramName, paramValue);
//获取boundsql
BoundSql boundSql = getBoundSql(methodId, paramMap);
//获取参数映射
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
Map additionalParameters = null;
try {
additionalParameters = (Map)additionalParametersField.get(boundSql);
} catch (IllegalAccessException e) {
;
}
StringBuilder stringBuilder = new StringBuilder(boundSql.getSql());
int paramMappingSize = parameterMappings.size();
//这段是取自MybatisDefaultParameterHandler类的setParameters方法的一部分,用于参数的绑定,我这里是预编译?号替换。
if (parameterMappings != null) {
//获取Mapper接口的参数
Object parameterObject = boundSql.getParameterObject();
//获取管理类型转换的容器
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
for (int i = 0; i < paramMappingSize; ++i) {
ParameterMapping parameterMapping = (ParameterMapping) parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
String propertyName = parameterMapping.getProperty();
Object value;
if (boundSql.hasAdditionalParameter(propertyName)) {
//如果是动态参数近这里
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
//如果参数为空value就为空
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
//如果参数有专门的类型转换就进入这里
value = parameterObject;
} else {
//普通赋值,MetaObject是参数的元信息,里面有参数值和参数名对应
MetaObject metaObject = this.configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
if (value == null && MapUtils.isNotEmpty(additionalParameters)) {
value = additionalParameters.get(propertyName);
}
}
String stringValue = getParameterValue(value);
//获取第一个?号的偏移量,然后用于替换,因为替换了的话,这个问号就消失了,所以可以一直获取第一个问号的偏移量
int index = stringBuilder.indexOf("?");
if (index > -1)
stringBuilder.replace(index,index+1,stringValue);
}
}
}
return stringBuilder.toString();
}
/**
* @description 将多个传到mybatis的参数转换成一个map,paramName与 paramValue 的顺序必须一一对应 并且参数名与mybatis的参数名必须一样 缺一不可
* @param paramName 参数名数组
* @param paramValue 参数值数组
* @return
*/
private Map<String,Object> convertParamToMap(String[] paramName,Object[] paramValue){
Map<String,Object> paramMap = new HashMap<>();
if (CommonsUtils.isEmptyArray(paramName) || CommonsUtils.isEmptyArray(paramValue))
return paramMap;
if (paramName.length != paramValue.length)
throw new RuntimeException("参数名与参数值的数量必须相等并且要一一对应");
int len = paramName.length;
for (int i = 0;i<len;i++){
paramMap.put(paramName[i],paramValue[i]);
}
return paramMap;
}
/**
* @description 获取boundsql
* @param id
* @param paramMap
* @return
*/
private BoundSql getBoundSql(String id,Map<String,Object> paramMap){
return configuration.getMappedStatement(id).getBoundSql(paramMap);
}
/**
* 对参数进行转换成String类型
* @param obj
* @return
*/
private String getParameterValue(Object obj) {
String value = null;
if (obj instanceof String) {
//如果参数是String类型,则直接添加单引号在两边
value = "'" + obj.toString() + "'";
} else if (obj instanceof Date) {
//如果参数是日期,则转换再加单引号。
DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
value = "'" + formatter.format(new Date()) + "'";
} else {
if (obj != null) {
value = obj.toString();
} else {
value = "";
}
}
return value;
}
public void test(){
//这个是mapper接口参数名,与mybatis的@param里面的值一致。
String[] paramNameArray = {"params1","params2"};
//这个是参数名对应的值,一定要一一对应。并且跟mybatis接口一样便可。
Object[] paramValueArray = {params,params};
return baseGetSql("mapper接口全限定名",paramNameArray,paramValueArray);
//这样不用各种拼接,只要传参与mapper接口的一致便可准确地获得sql。方便了不少。
}
1万+





