MyBatis常见问题分析(mybatis 异常处理)
ztj100 2024-11-02 14:29 24 浏览 0 评论
1,大于号、小于号在sql语句中的转换
使用 mybatis 时 sql 语句是写在 xml 文件中,如果 sql 中有一些特殊的字符的话,比如< ,<=,>,>=等符号,会引起 xml 格式的错误,需要替换掉,或者不被转义。 有两种方法可以解决:转义字符和标记 CDATA 块。
方式一:转义字符
<select id="searchByPrice" parameterType="Map" resultType="Product"> <!-- 方式1、转义字符 --> select * from Product where price >= #{minPrice} and price <= #{maxPrice} </select>
方式二:标记CDATA
<select id="searchByPrice" parameterType="Map" resultType="Product"> <!-- 方式2、CDATA --> <![CDATA[select * from Product where price >= #{minPrice} and price <= #{maxPrice} ]]> </select>
转义字符表
2.传入参数时参数为0查询条件失效
场景案例
场景是这样的,需要做一个对账单查询,可以按金额范围进行查询,页面参数写完之后进行条件,输入0测试了无数次均失效。
原因解析
当页面参数为0,传入到mybatis的xml中后,如果不是字符串,需指定数据类型,否则会被误认为null
<if test="data.tatalAmount != null and data.totalAmount !='' ">and total_Amount=#{data.totalAmount}</if>
这种情况如果totalAmount为0时将被误认为是null,里面的条件不会被执行。
解决方案
1,添加0判断
<if test="data.tatalAmount != null and data.totalAmount !='' or tatalAmount==0 ">and total_Amount=#{data.totalAmount}</if>
2,规定传入参数的类型
<if test="data.tatalAmount != null and data.totalAmount !='' ">and total_Amount=#{data.totalAmount,jdbc.Type=DECIMAL}</if>
3,Mybatis中#{}和${}区别
#{}是预编译处理,像传进来的数据会加个" "(#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号)
${}就是字符串拼接。直接替换掉占位符。$方式一般用于传入数据库对象,例如传入表名。
使用${}的话会导致sql注入。什么是sql注入呢?比如select * from user where id = #{value}
value本应该是一个数值。然后如果对方传过来的是 001 and name = tom.这样不就相当于多加了一条sql语句进去。
把SQL语句直接写进来了。如果是攻击性的语句呢?001;drop table user,直接把表给删了
所以为了防止 SQL 注入,能用 #{} 的不要去用 ${}
如果非要用 ${} 的话,那要注意防止 SQL 注入问题,可以手动判定传入的变量,进行过滤,一般 SQL 注入会输入很长的一条 SQL 语句
4,Mybatis动态sql语句(OGNL语法)
1、if
解决当要查询的多个条件有一个为空而导致的查询结果为空的情况
<select id="select" resultType="Blog"> SELECT * FROM BLOG WHERE state = ‘ACTIVE’ <if test="title != null"> AND title like #{title} </if> <if test="name!= null"> AND name like #{title} </if></select>
2、where
像上面的那种情况,如果where后面没有条件,然后需要直接写if判断(开头如果是 and / or 的话,会去除掉)
<select id="select" resultType="Blog"> SELECT * FROM BLOG <where> <if test="title != null"> AND title like #{title} </if> <if test="name!= null"> AND name like #{title} </if> <where></select>
3、choose(when、otherwise)
choose 相当于 java 里面的 switch 语句。otherwise(其他情况)
<select id="findActiveBlogLike" resultType="Blog"> SELECT * FROM BLOG WHERE state = ‘ACTIVE’ <choose> <when test="title != null"> AND title like #{title} </when> <when test="author != null and author.name != null"> AND author_name like #{author.name} </when> <otherwise> AND featured = 1 </otherwise> </choose></select>
4、trim
prefix:前缀prefixoverride:去掉第一个and或者是or
select * from test<trim prefix="WHERE" prefixoverride="AND丨OR"> <if test="a!=null and a!=' '">AND a=#{a}<if> <if test="b!=null and b!=' '">AND a=#{a}<if></trim>
5、set
set 元素主要是用在更新操作的时候,如果包含的语句是以逗号结束的话将会把该逗号忽略,如果set包含的内容为空的话则会出错。
<update id="dynamicSetTest" parameterType="Blog"> update t_blog <set> <if test="title != null"> title = #{title}, </if> <if test="content != null"> content = #{content}, </if> <if test="owner != null"> owner = #{owner} </if> </set> where id = #{id} </update>
6、foreach
foreach主要用在构建in条件中
<select id="dynamicForeachTest" resultType="Blog"> select * from t_blog where id in <foreach collection="list" index="index" item="item" open="(" separator="," close=")"> #{item} </foreach> </select>
open separator close
相当于是in (?,?,?)
如果是个map怎么办
<select id="dynamicForeach3Test" resultType="Blog"> select * from t_blog where title like "%"#{title}"%" and id in <foreach collection="ids" index="index" item="item" open="(" separator="," close=")"> #{item} </foreach> </select>
collection对应map的键,像这样
List<Integer> ids = new ArrayList<Integer>(); ids.add(1); ids.add(2); ids.add(3); ids.add(6); ids.add(7); ids.add(9); Map<String, Object> params = new HashMap<String, Object>(); params.put("ids", ids);
5,Like模糊查询
方式一:
$ 这种方式,简单,但是无法防止SQL注入,所以不推荐使用
LIKE '%${name}%'
方式二:
LIKE "%"#{name}"%"
方式三:字符串拼接
AND name LIKE CONCAT(CONCAT('%',#{name},'%'))
方式四:bind标签
<select id="searchStudents" resultType="com.example.entity.StudentEntity" parameterType="com.example.entity.StudentEntity"> <bind name="pattern1" value="'%' + _parameter.name + '%'" /> <bind name="pattern2" value="'%' + _parameter.address + '%'" /> SELECT * FROM test_student <where> <if test="age != null and age != '' and compare != null and compare != ''"> age ${compare} #{age} </if> <if test="name != null and name != ''"> AND name LIKE #{pattern1} </if> <if test="address != null and address != ''"> AND address LIKE #{pattern2} </if> </where> ORDER BY id </select>
6,传递多个参数
方法一:使用map接口传递参数
严格来说,map适用几乎所有场景,但是我们用得不多。原因有两个:首先,map是一个键值对应的集合,使用者要通过阅读它的键,才能明了其作用;其次,使用map不能限定其传递的数据类型,因此业务性质不强,可读性差,使用者要读懂代码才能知道需要传递什么参数给它,所以不推荐用这种方式传递多个参数。
public List<Role> findRolesByMap(Map<String, Object> parameterMap);<select id="findRolesByMap" parameterType="map" resultType="role"> select id, role_name as roleName, note from t_role where role_name like concat('%', #{roleName}, '%') and note like concat('%', #{note}, '%')</select>
方法二:使用注解传递多个参数
MyBatis为开发者提供了一个注解@Param(org.apache.ibatis.annotations.Param),可以通过它去定义映射器的参数名称,使用它可以得到更好的可读性 这个时候需要修改映射文件的代码,此时并不需要给出parameterType属性,让MyBatis自动探索便可以了 使可读性大大提高,使用者也方便了,但是这会带来一个麻烦。如果SQL很复杂,拥有大于10个参数,那么接口方法的参数个数就多了,使用起来就很不容易,不过不必担心,MyBatis还提供传递Java Bean的形式。
public List<Role> findRolesByAnnotation(@Param("roleName") String rolename, @Param("note") String note);<select id="findRolesByAnnotation" resultType="role"> select id, role_name as roleName, note from t_role where role_name like concat('%', #{roleName}, '%') and note like concat('%', #{note}, '%')</select>
方法三:通过Java Bean传递多个参数
public List<Role> findRolesByBean(RoleParams roleParam);<select id="findRolesByBean" parameterType="com.xc.pojo.RoleParams" resultType="role"> select id, role_name as roleName, note from t_role where role_name like concat('%', #{roleName}, '%') and note like concat('%', #{note}, '%')</select>
方法四:混合使用
在某些情况下可能需要混合使用几种方法来传递参数。举个例子,查询一个角色,可以通过角色名称和备注进行查询,与此同时还需要支持分页
public List<Role> findByMix(@Param("params") RoleParams roleParams, @Param("page") PageParam PageParam);<select id="findByMix" resultType="role"> select id, role_name as roleName, note from t_role where role_name like concat('%', #{params.roleName}, '%') and note like concat('%', #{params.note}, '%') limit #{page.start}, #{page.limit}</select>
总结:
描述了4种传递多个参数的方法,对各种方法加以点评和总结,以利于我们在实际操作中的应用。
?使用 map 传递参数导致了业务可读性的丧失,导致后续扩展和维护的困难,在实际的应用中要果断废弃这种方式。
?使用 @Param 注解传递多个参数,受到参数个数(n)的影响。当 n≤5 时,这是最佳的传参方式,它比用 Java Bean 更好,因为它更加直观;当 n>5 时,多个参数将给调用带来困难,此时不推荐使用它。
?当参数个数多于5个时,建议使用 Java Bean 方式。
?对于使用混合参数的,要明确参数的合理性。
7,MyBatis缓存机制
缓存机制减轻数据库压力,提高数据库性能
mybatis的缓存分为两级:一级缓存、二级缓存
一级缓存:
一级缓存为 SqlSession 缓存,缓存的数据只在 SqlSession 内有效。在操作数据库的时候需要先创建 SqlSession 会话对象,在对象中有一个 HashMap 用于存储缓存数据,此 HashMap 是当前会话对象私有的,别的 SqlSession 会话对象无法访问。
具体流程:
第一次执行 select 完毕会将查到的数据写入 SqlSession 内的 HashMap 中缓存起来
第二次执行 select 会从缓存中查数据,如果 select 同传参数一样,那么就能从缓存中返回数据,不用去数据库了,从而提高了效率
注意:
1、如果 SqlSession 执行了 DML 操作(insert、update、delete),并 commit 了,那么 mybatis 就会清空当前 SqlSession 缓存中的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现差异
2、当一个 SqlSession 结束后那么他里面的一级缓存也就不存在了, mybatis 默认是开启一级缓存,不需要配置
3、 mybatis 的缓存是基于 [namespace:sql语句:参数] 来进行缓存的,意思就是, SqlSession 的 HashMap 存储缓存数据时,是使用 [namespace:sql:参数] 作为 key ,查询返回的语句作为 value 保存的
二级缓存:
二级缓存是mapper 级别的缓存,也就是同一个 namespace 的 mapper.xml ,当多个 SqlSession 使用同一个 Mapper 操作数据库的时候,得到的数据会缓存在同一个二级缓存区域
二级缓存默认是没有开启的。需要在 setting 全局参数中配置开启二级缓存
开启二级缓存步骤:
1、conf.xml 配置全局变量开启二级缓存
<settings> <setting name="cacheEnabled" value="true"/>默认是false:关闭二级缓存<settings>
2、在userMapper.xml中配置
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>当前mapper下所有语句开启二级缓存
这里配置了一个 FIFO缓存,并每隔60秒刷新,最大存储512个对象,而返回的对象是只读的,因此在不同线程中的调用者之间修改它们会导致冲突。可用的收回策略有,默认的是LRU:
- LRU- 最近最少使用的;移除最长时间不被使用的对象。
- FIFO- 先进先出;按对象进入缓存的顺序来移除它们。
- SOFT- 软引用;移除基于垃圾回收器状态和软引用规则的对象
- WEAK- 弱引用;更积极地移除基干垃圾收集器状态和弱引用规则的对象
若想禁用当前select语句的二级缓存,添加 useCache="false"修改如下:
<select id="getCountByName" parameterType="java.util.Map" resultType="INTEGER" statementType="CALLABLE" useCache="false">
具体流程:
1.当一个sqlseesion执行了一次select 后,在关闭此session 的时候,会将查询结果缓存到二级缓存
2.当另一个sqlsession执行select 时,首先会在他自己的一级缓存中找,如果没找到,就回去二级缓存中找,找到了就返回,就不用去数据库了,从而减少了数据库压力提高了性能
注意:
1、如果 SqlSession 执行了 DML 操作(insert、update、delete),并 commit 了,那么 mybatis 就会清空当前mapper 缓存中的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现差异
2、mybatis 的一级缓存是基于[namespace:sql语句:参数]来进行缓存的,意思就是,SqlSession 的 HashMap 存储缓存数据时,是使用 [namespace:sql:参数]作为 key ,查询返回的语句作为 value 保存的。
是否应该使用二级缓存?
那么究竟应该不应该使用二级缓存呢?先来看一下二级缓存的注意事项:
- 缓存是以namespace为单位的,不同namespace下的操作互不影响。
- insert,update,delete操作会清空所在namespace下的全部缓存。
- 通常使用MyBatis Generator生成的代码中,都是各个表独立的,每个表都有自己的namespace。
- 多表操作一定不要使用二级缓存,因为多表操作进行更新操作,一定会产生脏数据。
如果你遵守二级缓存的注意事项,那么你就可以使用二级缓存。
但是,如果不能使用多表操作,二级缓存不就可以用一级缓存来替换掉吗?而且二级缓存是表级缓存,开销大,没有一级缓存直接使用 HashMap 来存储的效率更高,所以二级缓存并不推荐使用。
8,MyBatis时间timestamp做条件进行查询
首先要将条件 转换为 时间戳
long startTime = TimeUtil.parseTimestamp(start);long endTime = TimeUtil.parseTimestamp(end); /*对应工具类*/public static long parseTimestamp(String datetime){ try{ SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date date = dateformat.parse(datetime); return date.getTime()/1000; }catch(Exception e){ e.printStackTrace(); } return 0;}
然后Mapper.xml中 使用BETWEEN and 和 to_timestamp
<if test="startDate !=null and startDate !='' and endDate !=null and endDate !=''"> AND tdnm.create_time BETWEEN to_timestamp(#{startDate}) AND to_timestamp(#{endDate})</if>
9,mybatis 是否支持延迟加载?延迟加载的原理是什么?
1.MyBatis 支持延迟加载。
2.什么是延迟加载:延迟加载,也称为懒加载,是指在进行关联查询时,按照设置延迟规则推迟对关联对象的select查询。延迟加载可以有效的减少数据库压力。MyBatis的延迟加载只是对关联对象的查询有延迟设置,对于主加载对象都是直接执行查询语句的。
3.MyBatis 对关联对象的加载类型
(1)直接加载:执行完对主加载对象的select语句,马上执行对关联对象的select查询。
(2)侵入式延迟:执行对主加载对象的查询时,不会执行对关联对象的查询。但当要访问主加载对象的详情时,就会马上执行关联对象的select查询。(将关联对象的详情作为主加载对象的详情的一部分出现)
(3)深度延迟:执行对主加载对象的查询时,不会执行对关联对象的查询。访问主加载对象的详情时也不会执行关联对象的select查询。只有当真正访问关联对象的详情时,才会执行对关联对象的select查询。
4.延迟加载的原理:调用的时候触发加载,而不是在初始化的时候就加载信息
例如:调用 a. getB(). getName(),这个时候发现 a. getB() 的值为 null,此时会单独触发事先保存好的关联 B 对象的 SQL,先查询出来 B,然后再调用 a. setB(b),而这时候再调用 a. getB(). getName() 就有值了
相关推荐
- Vue 技术栈(全家桶)(vue technology)
-
Vue技术栈(全家桶)尚硅谷前端研究院第1章:Vue核心Vue简介官网英文官网:https://vuejs.org/中文官网:https://cn.vuejs.org/...
- vue 基础- nextTick 的使用场景(vue的nexttick这个方法有什么用)
-
前言《vue基础》系列是再次回炉vue记的笔记,除了官网那部分知识点外,还会加入自己的一些理解。(里面会有部分和官网相同的文案,有经验的同学择感兴趣的阅读)在开发时,是不是遇到过这样的场景,响应...
- vue3 组件初始化流程(vue组件初始化顺序)
-
学习完成响应式系统后,咋们来看看vue3组件的初始化流程既然是看vue组件的初始化流程,咋们先来创建基本的代码,跑跑流程(在app.vue中写入以下内容,来跑流程)...
- vue3优雅的设置element-plus的table自动滚动到底部
-
场景我是需要在table最后添加一行数据,然后把滚动条滚动到最后。查网上的解决方案都是读取html结构,暴力的去获取,虽能解决问题,但是不喜欢这种打补丁的解决方案,我想着官方应该有相关的定义,于是就去...
- Vue3为什么推荐使用ref而不是reactive
-
为什么推荐使用ref而不是reactivereactive本身具有很大局限性导致使用过程需要额外注意,如果忽视这些问题将对开发造成不小的麻烦;ref更像是vue2时代optionapi的data的替...
- 9、echarts 在 vue 中怎么引用?(必会)
-
首先我们初始化一个vue项目,执行vueinitwebpackechart,接着我们进入初始化的项目下。安装echarts,npminstallecharts-S//或...
- 无所不能,将 Vue 渲染到嵌入式液晶屏
-
该文章转载自公众号@前端时刻,https://mp.weixin.qq.com/s/WDHW36zhfNFVFVv4jO2vrA前言...
- vue-element-admin 增删改查(五)(vue-element-admin怎么用)
-
此篇幅比较长,涉及到的小知识点也比较多,一定要耐心看完,记住学东西没有耐心可不行!!!一、添加和修改注:添加和编辑用到了同一个组件,也就是此篇文章你能学会如何封装组件及引用组件;第二能学会async和...
- 最全的 Vue 面试题+详解答案(vue面试题知识点大全)
-
前言本文整理了...
- 基于 vue3.0 桌面端朋友圈/登录验证+60s倒计时
-
今天给大家分享的是Vue3聊天实例中的朋友圈的实现及登录验证和倒计时操作。先上效果图这个是最新开发的vue3.x网页端聊天项目中的朋友圈模块。用到了ElementPlus...
- 不来看看这些 VUE 的生命周期钩子函数?| 原力计划
-
作者|huangfuyk责编|王晓曼出品|CSDN博客VUE的生命周期钩子函数:就是指在一个组件从创建到销毁的过程自动执行的函数,包含组件的变化。可以分为:创建、挂载、更新、销毁四个模块...
- Vue3.5正式上线,父传子props用法更丝滑简洁
-
前言Vue3.5在2024-09-03正式上线,目前在Vue官网显最新版本已经是Vue3.5,其中主要包含了几个小改动,我留意到日常最常用的改动就是props了,肯定是用Vue3的人必用的,所以针对性...
- Vue 3 生命周期完整指南(vue生命周期及使用)
-
Vue2和Vue3中的生命周期钩子的工作方式非常相似,我们仍然可以访问相同的钩子,也希望将它们能用于相同的场景。...
- 救命!这 10 个 Vue3 技巧藏太深了!性能翻倍 + 摸鱼神器全揭秘
-
前端打工人集合!是不是经常遇到这些崩溃瞬间:Vue3项目越写越卡,组件通信像走迷宫,复杂逻辑写得脑壳疼?别慌!作为在一线摸爬滚打多年的老前端,今天直接甩出10个超实用的Vue3实战技巧,手把...
- 怎么在 vue 中使用 form 清除校验状态?
-
在Vue中使用表单验证时,经常需要清除表单的校验状态。下面我将介绍一些方法来清除表单的校验状态。1.使用this.$refs...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- Vue 技术栈(全家桶)(vue technology)
- vue 基础- nextTick 的使用场景(vue的nexttick这个方法有什么用)
- vue3 组件初始化流程(vue组件初始化顺序)
- vue3优雅的设置element-plus的table自动滚动到底部
- Vue3为什么推荐使用ref而不是reactive
- 9、echarts 在 vue 中怎么引用?(必会)
- 无所不能,将 Vue 渲染到嵌入式液晶屏
- vue-element-admin 增删改查(五)(vue-element-admin怎么用)
- 最全的 Vue 面试题+详解答案(vue面试题知识点大全)
- 基于 vue3.0 桌面端朋友圈/登录验证+60s倒计时
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- node卸载 (33)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- exceptionininitializererror (33)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)