最近在项目中使用到了mybatis 作为数据库连接,由于自己对于mybatis 不熟悉的原因导致使用mapper和Provider 生产SQL语句是,发生一些问题。
目的:
我又一个work类, 对应mysql 中的一张数据表 work,现在需要使用insert语句下个数据库插入数据。
实现方案:
前端接口使用一个 hashmap 承载 work中的数据(没有自定义数据结构),
Mapper中的代码如下
@InsertProvider(type= WorksProcider.class,method = "insertWork")
int insertWork( @Param("workInfo")Map<String, String>);
WorksProcider 中 生产sql语句,为了偷懒,map中的key和数据库中的列名一一对应,直接想遍历映射:
public String insertWork(Map<String, String> workInfo) {
String sql = "insert CARTOON_WORK (" + Joiner.on(",").join(workInfo.keySet()) + ") values (" + Joiner.on(",").join(workInfo.values())
+ ");";
return sql;
}
问题:
神奇的问题就来了 ,运行时,provider组装sql前 workInfo 是这样的
和我想的完全不一样,怎么会这么包装呢?
分析:
通过查询网上的其他资料,和debug跟随源码,找到问题所在
mybatis类中,ParamNameResolver 负责生成参数,有这么一段代码
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
上面的 if (!names.containsValue(genericParamName)) 这段代码就是产生上面结果的原因,mybatis 会将所有参数加入一份复制,方便后续处理。
正常使用maybatis 的方式应该是这种模式 使用#{work.uid} 这种模式
public String insertWorkInfo(@Param("work") Work work) {
return new SQL() {{
INSERT_INTO("Work");
VALUES("uid", "#{work.uid}");
VALUES("title", "#{work.title}");
VALUES("tel", "#{work.tel}");
VALUES("address", "#{work.address}");
}}.toString();
}
再最终执行sql语句之前,mybatis 会根据#{work.uid} 匹配,到参数取对应的值。
而我们的程序中为了偷懒,绕过了mybatis的这个过程,直接产生最终的sql语句,又使用了mybatis框架进行传参,导致 最终生成的sql语句错误。
总结:
问题产生的根源还是来自于自己对mybatis的不熟悉,然后又自作聪明,不按照正常的方式使用mybatis,导致的错误。
解决方案:
1、老老实实按照mybatis的常规使用方式,通过#{work.address} 将参数传入sql语句,使用mybatis框架自动处理
2、想偷懒也可以,知道上述过程之后, 只要将@Param(“work”) 注解去掉之后,就不会使用mybaitis带来的问题,直接能拿到自己想要的参数(十分不推荐,都这样了,还不如不使用框架)