mybatismysqlrownum_Mybatis的这些坑,把我坑惨了!
专注于Java领域优质技术,欢迎关注
⼤多数开发者应该都使⽤过Hibernate或者Mybatis的框架,或多或少都踩过⼀些坑!
如在MyBatis/Ibatis中#和$的区别,#⽅式能够很⼤程度防⽌sql注⼊,$⽅式⽆法防⽌Sql注⼊。所以,⽼司机 对新⼿说,最好⽤#。简单的说#{}是经过预编译的,是安全的,⽽是未经过预编译的,仅仅是取变量的值,是⾮安全的,存在sql注⼊。有些特例是需要关注的,有的时候需要⽤ 解决解决⼀些实际问题。
如在执⾏sql语句时你有时并不希望让变量进⾏处理,⽽是直接赋值执⾏,这时就要⽤到(${a})了,在使⽤时还要这样赋值
@Param(value="a") String a
如⽇期问题:
可能会遇到⽇期格式的时间段问题,当数据库的时间为DATE类型时,MyBatis的jdbcType应该使⽤DATE
jdbcType=DATE,
⽽不是使⽤
jdbcType=TIMESTAMP
如在使⽤resultMap的时候,要把ID写在第⼀⾏,否则的话,就会报错。
⼜如最近在做的项⽬,遇到myBatis的⼤坑,Mybatis⼀直报异常:
Java.lang.ArrayIndexOutOfBoundsException,
于是开始代码查错,代码中有存储过程,然后开发使⽤ROOT⽤户执⾏SQL跑出来的数据结果集是正常的,在测试环境程序运⾏也正常,但是在正式环境就其他⽤户不⾏,最后发现是因为数据库没有给该⽤户授权出了问题。
案例⼀:
作为新⼿,在此记下刚踩的⼀个坑,(踩踩更健康= =踩过痛过才不会再次错),写了⼀个sql语句⽤到两张表,两张表中有两个字段名字是⼀样的都是Time和Content,然后要查询这两张表的这两个字段都要查出来放到⼀个dto中,dto如下图所⽰,
sql语句如下,
然⽽运⾏后却发现后⼏个在数据库表⾥同名的字段取出来都是null,
但是放到数据库那边执⾏是没有取出空数据的,
真是苦恼= =,后来经⼤神指点,sql语句查询出来的这个字段名必须和dto的参数名⼀致,改成这样就通过了,
数据都取出来了。。。。。。。。。。还记得在hibernate⾥⽤hql时放到dto⾥,select new dto名()参数顺序和类型⼀致就可以取出来。。。这应该算⼀个不同点吧,,感觉还是hql⽤起来舒服,,,求⼤神科普两者的差别优缺点
当实体类中的属性名和表中的字段名不⼀致时,使⽤MyBatis进⾏查询操作时⽆法查询出相应的结果的问题,当时上⽹查了很多才知道,看到的⼀个解决⽅法分享给⼤家,通过来映射字段名和实体类属性名的⼀⼀对应关系。这种⽅式是使⽤MyBatis提供的解决⽅式来解决字段名和属性名的映射关系的!
案例⼆:
数据库表使⽤了联合主键,逆向⽣成的时候⽣成了两个实体类。看起来别扭。但还是可以⽤。后来就先取消主键,⽣成完后再将主键加上。还有就是,tinyint本来以为⽤来表⽰⽐较⼩的整数,结果⽣成了布尔型的属性。后来就表⽰是和否才⽤tinyint了。逆向⽣成的sql语句绝对不能⼈为改动,否则再次⽣成的时候会重复⽣成。但是,尽管踩过坑,我还是觉得mybatis超级好⽤,⽐hibernate好多了。虽然hibernate 我只试过⼀点之后就完全转向了mybatis了。
案例三:
sum()和count()使⽤场景不对导致出错:
count()、count(1)、count(0)就是指绝对的⾏数,哪怕某⾏所有字段全部为null也会计算在内。count(1)和count()相⽐,innodb来说count(*)效率低。
如果count(列名)查询出来的结果就是查出列名中不为null的⾏数;
sum(列名)对指定列名进⾏求和
MyBatis把int类型的0处理成空串’’和mysql处理空串’’为0的问题,在Mybatis的Mapper中整数类型条件该如何判断?
当数据库字段类型是整数,如果参数变量为空字符串或者NULL,Mybatis会⾃动将参数赋值0,所以如果要判断整数参数的多种状态在传递数值到Mapper之前就要判断是否为空字符串和NULL并将相应的状态数值赋值给该参数,否则参数值等于空字符串、NULL和0得到的结果是⼀样的。
⼀般情况下,涉及到int类型的操作的时候,在Service中会统⼀把数字类型先变成字符串类型,然后再传递到Mapper中操作。
时间戳的使⽤
在创建新记录的时候把这个字段设置为当前时间,但以后修改时,不再刷新它(可以给createtime使⽤这个):
TIMESTAMP DEFAULT CURRENT_TIMESTAMP
在创建新记录和修改现有记录的时候都对这个数据列刷新(可以给update使⽤这个):
TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
在使⽤resultMap的时候,要把ID写在第⼀⾏,否则的话,就会报错。
案例四:
XML转义字符,如果直接写就会报错,需要⽤左边⼀列的转义字符
< < ⼩于号 > > ⼤于号
& & 和
' ' 单引号
" " 双引号
案例五:
前⼏天在项⽬中碰到,来说下吧。⼤神可绕道。在使⽤selectOne查询个数时,
如果你写了resultType为Integer,然后在业务代码中很⾃然的⽤⼀个变量int去接当前这个⽅法的返回,如果按照你传⼊的条件在数据库中没有到相关的值,此时selectOne⽅法的返回值会是⼀个null,当你使⽤Java的⾃动拆箱机制的时候会报出⼀个⽆情的NPE。
原因:Java在⾃动拆箱的时候会调⽤Integer类中的intValue⽅法,如果当前对象为null,则抛出NPE。
因此,在接受的时候要判空,否则可能异常。
案例六:
多参数的使⽤
MyBatis的查询或者更新中,如果需要多个参数有如下⼏种办法:
对象映射,建⽴⼀个Java对象,并作为接⼝的参数,对象的属性可以直接使⽤#{属性名}的⽅式访问;
Map, 参数为⼀个Map, key对于属性名,value对于参数值,这个⽅法就是传参数是需要建⽴⼀个Map的临时对象;
@param参数注解,在接⼝⽅法参数前加⼊参数名称注解,这样就可以直接在Mapper中通过参数名访问;
通过序号访问,第⼀个参数#{0}或#{param1}, 第⼆个参数#{1}, #{param2}
MyBatis中时间字段的使⽤–返回;
时间字段的返回⽬前笔者采⽤放回字符串的⽅式:
date_format(update_time, ‘%Y-%c-%d %H:%i:%s’) updatetime
采⽤MySQL的时间格式化⽅法。
或者放回Timestamp类型的数据,要求放回对象属性参数为Timestamp.
MyBatis中时间字段的使⽤–参数
如果需要查询⼀段时间范围的数据时,可以通过以下动态SQL的⽅式查询数据:
and lbr.update_time > #{startTime}
and lbr.update_time < #{endTime, javaType=Date, jdbcType=TIMESTAMP}
对于的接⼝⽅法名称如下:
… Date startTime, Date endTime…
我想这个⽅法会⽐通过格式转换的效率要⾼⼀些
MyBatis中时间字段的使⽤–写⼊
写⼊可是直接写⼊Timestamp的数据,需要描述⼀些写⼊的jdbcType,如下:
{installTime, jdbcType=TIMESTAMP}
Mapper层参数为Map,由Service层负责重载。
Mapper由于机制的问题,不能重载,参数⼀般设置成Map,但这样会使参数变得模糊,如果想要使代码变得清晰,可以通过service层来实现重载的⽬的,对外提供的Service层是重载的,但这些重载的Service⽅法其实是调同⼀个Mapper,只不过相应的参数并不⼀致。
也许有⼈会想,为什么不在Service层也设置成Map呢?我个⼈是不推荐这么做的,虽然为了⽅便,我在之前的项⽬中也⼤量采⽤了这种⽅式,但很明显会给⽇后的维护⼯作带来⿇烦。因为这么做会使你整个MVC都依赖于Map模型,这个模型其实是很不错的,⽅便搭框架,但存在⼀个问题:仅仅看⽅法签名,你不清楚Map中所拥有的参数个数、类型、每个参数代表的含义。
试想,你只对Service层变更,或者DAO层变更,你需要清楚整个流程中Map传递过来的参数,除⾮你注释或者⽂档良好,否则必须把每⼀层的代码都了解清楚,你才知道传递了哪些参数。针对于简单MVC,那倒也还好,但如果层次复杂之后,代码会变得异常复杂,⽽且如果我增加⼀个参数,需要把每⼀个层的注释都添加上。相对于注释,使⽤⽅法签名来保证这种代码可控性会来得更可⾏⼀些,因为注释有可能是过时的,但⽅法签名⼀般不太可能是陈旧的。
尽量少⽤if choose等语句,降低维护的难度。
Mybatis的配置SQL时,尽量少⽤if choose 等标签,能⽤SQL实现判断的尽量⽤SQL来判断(CASE WHEN ,DECODE等),以便后期维护。否则,⼀旦SQL膨胀,超级恶⼼,如果需要调试Mybatis中的SQL,需要去除⼤量的判断语句,⾮常⿇烦。另⼀⽅⾯,⼤量的if判断,会使⽣成的SQL中包含⼤量的空格,增加⽹络传输的时间,也不可取。thinkphprce漏洞利用
⽽且⼤量的if choose语句,不可避免地,每次⽣成的SQL会不太⼀致,会导致ORACLE⼤量的硬解析,
inventor如何装配
也不可取。
我们来看看这样的SQL:
python一打开就闪退
这样的if判断,其实是完全没有必要的,我们可以很简单的采⽤DECODE来解决默认值问题:
当然有⼈会想,引⼊CASE WHEN,DECODE会导致需要ORACLE函数解析,会拖慢SQL执⾏时间,有兴趣的同学可以回去做⼀下测试,看看是否会有⼤的影响。就个⼈经验⽽⾔,在我的开发过程,没有发现因为函数解析导致SQL变慢的情形。影响SQL执⾏效率的⼀般情况下是JOIN、ORDER BY、DISTINCT、PARTITATION BY等这些操作,这些操作⼀般与表结构设计有很⼤的关联。相对于这些的效率影响程度,函数解析对于SQL执⾏速度影响应该是可以忽略不计的。
另外⼀点,对于⼀些默认值的赋值,像上⾯那条SQL,默认成当前⽇期什么的,其实可以完全提到Service层或Controller层做处理,在Mybatis中应该要少⽤这些判断。因为,这样的话,很难做缓存处
理。如果startdate为空,在SQL上使⽤动态的SYSDATE,就⽆法确定缓存startdate⽇期的key应该是什么了。所以参数最好在传递⾄Mybatis之前都处理好,这样Mybatis层也能减少部分if choose语句,同时也⽅便做缓存处理。
当然不使⽤if choose也并不是绝对的,有时候为了优化SQL,不得不使⽤if来解决,⽐如说LIKE语句,当然⼀般不推荐使⽤LIKE,但如果存在使⽤的场景,尽可能在不需要使⽤时候去除LIKE,⽐如查询⽂章标题,以提⾼查询效率。最好的⽅式是使⽤lucence等搜索引擎来解决这种全⽂索引的问题。
总的来说,if与choose判断分⽀是不可能完全去除的,但是推荐使⽤SQL原⽣的⽅式来解决⼀些动态问题,⽽不应该完全依赖Mybatis来完成动态分⽀的判断,因为判断分⽀过于复杂,⽽且难以维护。
⽤XML注释取代SQL注释。
Mybatis中原SQL的注释尽量不要保留,注释会引发⼀些问题,如果需要使⽤注释,可以在XML中⽤来注释,保证在⽣成的SQL中不会存在SQL注释,从⽽降低问题出现的可能性。这样做还有⼀个好处,就是在IDE中可以很清楚的区分注释与SQL。
现在来谈谈注释引发的问题,我做的⼀个项⽬中,分页组件是基于Mybatis的,它会在你写的SQL脚本外⾯再套⼀层SELECT COUNT(*) ROWNUM_ FROM (….) 计算总记录数,同时有另⼀个嵌套SELEC
T * FROM(…) WHERE ROWNUM > 10 AND RONNUM < 10 * 2这种⽅式⽣成分页信息,如果你的脚本中最后⼀⾏出现了注释,则添加的部分会成为注释的⼀部分,执⾏就会报错。除此之外,某些情况下也可能导致部分条件被忽略,如下⾯的情况:
SELECT * FROM TEST
WHERE COL1 > 1 -- 这⾥是注释AND COL2 = #{a}
即使传⼊的参数中存在对应的参数,实际也不会产⽣效果,因为后⾯的内容实际上是被完全注释了。这种错误,如果不经过严格的测试,是很难发现的。⼀般情况下,XML注释完全可以替代SQL注释,因此这种⾏为应该可以禁⽌掉。
尽可能使⽤`#{}`,⽽不是`${}`.
关于${},另⼀个误⽤的地⽅就是LIKE,我这边还有个案例:⽐如⼀些树型菜单,节点会设计成'01','0101',⽤两位节点来区分层级,这时候,如果需要查询01节点下所有的节点,最简单的SQL便是:SELECT * FROM TREE WHERE ID LIKE '01%',这种SQL其实⽆可厚⾮,因为它也能⽤到索引,所以不需要特别的处理,直接使⽤就⾏了。但如果是⽂章标题,则需要额外注意了:SELECT * FROM
T_NEWS_TEXT WHERE TITLE LIKE '%OSC%',这是怎么也不会⽤到索引的,上⾯说了,最好采⽤全⽂检索。但如果离不开LIKE,就需要注意使⽤的⽅式:ID LIKE #{ID} || '%'⽽不是ID LIKE '
有⼈觉得使⽤||会增加ORACLE处理的时间,我觉得不要把ORACLE看得太傻,虽然有时候确实⾮常傻,有空可以再总结ORACLE傻不垃圾的地⽅,但是稍加测试便知:这种串联⽅式,对于整个SQL的解析执⾏,应该是微乎其微的。
当然还有⼀些特殊情况是没有办法处理的,⽐如说动态注⼊列名、表名等。对于这些情况,则⽐较棘⼿,没有到⽐较⽅便的⼿段。由于这种情况出现的可能性会⽐较少,所以使⽤ID有⼈觉得使⽤∣∣会增加ORACLE处理的时间,我觉得不要把ORACLE看得太傻,虽然有时候确实⾮常傻,有空可以再总结ORACLE傻不垃圾的地⽅,但是稍加测试便知:这种串联⽅式,对于整个SQL的解析执⾏,应该是微乎其微的。当然还有⼀些特殊情况是没有办法处理的,⽐如说动态注⼊列名、表名等。对于这些情况,则⽐较棘⼿,没有到⽐较⽅便的⼿段。由于这种情况出现的可能性会⽐较少,所以使⽤{}倒也不⾄于有什么太⼤的影响。当然你如果有代码洁癖的话,可以使⽤ORACLE的动态执⾏SQL的机制Execute immediate,这样就可以完全避免${}出现的可能性了。这样会引⼊⽐较复杂的模型,这个时候,你就需要取舍了。
针对于以上动态SQL所导致的问题,最激进的⽅式是全部采⽤存储过程,⽤数据库原⽣的⽅式来解决,
⽅便开发调试,当然也会带来问题:对开发⼈员会有更⾼的要求、存储过程的管理等等,我这边项⽬没有采⽤过这种⽅式,这⾥不做更多的展开。
简单使⽤Mybatis。
Mybatis的功能相对⽽⾔还是⽐较弱的,缺少了好多必要的辅助库,字符串处理等等,扩展也⽐较困难,⼀般也就可能对返回值进⾏⼀些处理。因此最好仅仅把它作为单纯的SQL配置⽂件,以及简单的ORM框架。不要尝试在Mybatis中做过多的动态SQL,否则会导致后续的维护⾮常恶⼼。
⼏点技巧总结;
1、查询很多字段时可以提出来再引⼊到sql语句
toolstripstatuslable时间实时更新
提取:
id, type, shopCouId, Path, fromDate, toDate, insDate, insUserId, updDate, updUserId, delFlg
引⼊:
select
from adinfo
where id = #{id,jdbcType=INTEGER}mysql语句的执行顺序
2、如果sql语句中需要使⽤, "" 符号时,需要使⽤< > " 或者
CDATA内部所有东西都会被解析器忽略
select type, shopCouId, Path
flink开发手册from adinfo
WHERE delFlg ='0'
and fromDate < #{date} and toDate >= #{date}
3、缓存使⽤
在增删查改时,可以使⽤缓存属性控制数据缓存
4、可以判断传进来的参数,再进⾏操作

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。