最近项目中出现了至少两次因为Mybatis的动态where条件不满足导致实际sql语句的where条件为空,进而查询全表,当数据量比较大的时候,导致OOM的情况.
如何禁止这种情况,个人觉得三种措施:
- 1.在逻辑层面加充分的参数有效性检查;
- 2.在where条件中如果索引条件都不满足,加上1=2这种必然失败的条件;
1
2
3
4
5
6
7
8
9
10
11
| <where>
<choose>
<when test="id != null">
...
</when>
...
<otherwise>
and 1=2
</otherwise>
</choose>
</where>
|
前两种措施都是依赖人,从这个层面讲,是不靠谱的,即一个策略不是强制的,就是不靠谱的.相对而言,第三种是不依赖程序员的自觉性,是最靠谱的.乘周六有时间,实现一个简单的Mybatis拦截器来拦截where条件为空的SQL语句.
1.实现
先上代码,这里拦截了SqlCommandType为select,update,delete这三种类型.出现情况目前只是打日志.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
| /**
* Mybatis拦截器, 拦截:
* select语句where条件为空
* update语句where条件为空
* delete语句where条件为空
*
* 处理: 目前只是打warning日志,因为项目中存在数据量很少的表读全表的情况(比如加载数据进缓存)
* TODO:后续考虑设置拦截器的白名单,不在白名单的触发where条件为空的直接抛出异常.
*
* @author: xiaobaoqiu Date: 15-8-22 Time: 上午10:26
*/
@Intercepts({ @Signature(
type = StatementHandler.class,
method = "prepare",
args = { Connection.class }) })
public class EmptyWhereInterceptor implements Interceptor {
private static final Logger logger = LoggerFactory.getLogger(EmptyWhereInterceptor.class);
/**
* 拦截的 COMMAND 类型
*/
private static final Set<String> INTERCEPTOR_COMMAND = Sets.newHashSet("select", "update", "delete");
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler handler = (StatementHandler) invocation.getTarget();
//获取实际的StatementHandler
if (handler instanceof RoutingStatementHandler) {
handler = (BaseStatementHandler) ReflectUtil.getFieldValue(handler, "delegate");
}
//获取SqlCommandType
String commandType = getCommandType(handler);
if (INTERCEPTOR_COMMAND.contains(commandType)) {
String originSql = handler.getBoundSql().getSql().toLowerCase(); //获取sql
if (!originSql.contains("where")) {
logger.warn("禁止使用不带where条件的SQL语句.原始SQL={}", originSql);
}
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
/**
* 获取Command类型,小写化返回
*
* @param handler
* @return
*/
private String getCommandType(StatementHandler handler) {
MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(handler, "mappedStatement");
return mappedStatement.getSqlCommandType().toString().toLowerCase();
}
}
|
包括一个简单反射工具的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
| /**
* 反射工具
*
* @author: xiaobaoqiu Date: 15-8-22 Time: 上午11:52
*/
public class ReflectUtil {
public ReflectUtil() {
}
/**
* 改变 Accessible,便于访问private等属性
* @param field
*/
private static void makeAccessible(Field field) {
if(!Modifier.isPublic(field.getModifiers())) {
field.setAccessible(true);
}
}
/**
* 获取 object 的字段,字段名称为filedName,获取不到返回null
* @param object
* @param filedName
* @return
*/
private static Field getDeclaredField(Object object, String filedName) {
Class superClass = object.getClass();
while(superClass != Object.class) {
try {
return superClass.getDeclaredField(filedName);
} catch (NoSuchFieldException var4) {
superClass = superClass.getSuperclass();
}
}
return null;
}
/**
* 获取object字段fieldName的值,如果字段不存在直接抛异常
*
* @param object
* @param fieldName
* @return
*/
public static Object getFieldValue(Object object, String fieldName) {
Field field = getDeclaredField(object, fieldName);
if(field == null) {
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]");
} else {
makeAccessible(field);
Object result = null;
try {
result = field.get(object);
} catch (IllegalAccessException var5) {
var5.printStackTrace();
}
return result;
}
}
/**
* 设置object字段fieldName的值,如果字段不存在直接抛异常
*
* @param object
* @param fieldName
* @param value
*/
public static void setFieldValue(Object object, String fieldName, Object value) {
Field field = getDeclaredField(object, fieldName);
if(field == null) {
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]");
} else {
makeAccessible(field);
try {
field.set(object, value);
} catch (IllegalAccessException var5) {
var5.printStackTrace();
}
}
}
}
|
2.Mybatis的Interceptor原理
这篇文章很好的解释了Mybatis的Interceptor机制.
mybatis读取配置再xml文件中Interceptor,通过反射构造其实例,将所有的Interceptor保存到InterceptorChain中。
mybatis的拦截器只能代理指定的四个类:ParameterHandler、ResultSetHandler、StatementHandler以及Executor。
参考:
http://www.tuicool.com/articles/RbyUfu
http://blog.csdn.net/hupanfeng/article/details/9247379