MyBatis常见问题

Posted yaomagician

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis常见问题相关的知识,希望对你有一定的参考价值。

Mybatis常见问题

1,大于号、小于号在sql语句中的转换

使用 mybatis 时 sql 语句是写在 xml 文件中,如果 sql 中有一些特殊的字符的话,比如< ,<=,>,>=等符号,会引起 xml 格式的错误,需要替换掉,或者不被转义。 有两种方法可以解决:转义字符和标记 CDATA 块。

方式一:转义字符

<select id="searchByPrice" parameterType="Map" resultType="Product">
    <!-- 方式1、转义字符 -->
    select * from Product where price &gt;= #minPrice and price &lt;= #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

 

  1. LRU- 最近最少使用的;移除最长时间不被使用的对象。

  2. FIFO- 先进先出;按对象进入缓存的顺序来移除它们。

  3. SOFT- 软引用;移除基于垃圾回收器状态和软引用规则的对象

  4. 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语句:参数]来进行缓存的,意思就是,SqlSessionHashMap 存储缓存数据时,是使用 [namespace:sql:参数]作为 key ,查询返回的语句作为 value 保存的。

是否应该使用二级缓存?

那么究竟应该不应该使用二级缓存呢?先来看一下二级缓存的注意事项:

  1. 缓存是以namespace为单位的,不同namespace下的操作互不影响。

  2. insert,update,delete操作会清空所在namespace下的全部缓存。

  3. 通常使用MyBatis Generator生成的代码中,都是各个表独立的,每个表都有自己的namespace

  4. 多表操作一定不要使用二级缓存,因为多表操作进行更新操作,一定会产生脏数据。

如果你遵守二级缓存的注意事项,那么你就可以使用二级缓存。

但是,如果不能使用多表操作,二级缓存不就可以用一级缓存来替换掉吗?而且二级缓存是表级缓存,开销大,没有一级缓存直接使用 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() 就有值了

MyBatis解析MyBatis传入参数的问题

一、单个参数:

复制代码
public List<XXBean> getXXBeanList(String xxCode);  

<select id="getXXXBeanList" parameterType="java.lang.String" resultType="XXBean">

  select t.* from tableName t where t.id= #{id}  

</select>  

其中方法名和ID一致,#{}中的参数名与方法中的参数名一直, 我这里采用的是XXXBean是采用的短名字,

select 后的字段列表要和bean中的属性名一致, 如果不一致的可以用 as 来补充。
复制代码

 

二、多参数:

复制代码
public List<XXXBean> getXXXBeanList(String xxId, String xxCode);  

<select id="getXXXBeanList" resultType="XXBean">

  select t.* from tableName where id = #{0} and name = #{1}  

</select>  

由于是多参数那么就不能使用parameterType, 改用#{index}是第几个就用第几个的索引,索引从0开始
复制代码

 

三、Map封装多参数:  

复制代码
public List<XXXBean> getXXXBeanList(HashMap map);  

<select id="getXXXBeanList" parameterType="hashmap" resultType="XXBean">

  select 字段... from XXX where id=#{xxId} code = #{xxCode}  

</select>  

其中hashmap是mybatis自己配置好的直接使用就行。map中key的名字是那个就在#{}使用那个,map如何封装就不用了我说了吧。 
复制代码

 

 四、List封装in:

复制代码
public List<XXXBean> getXXXBeanList(List<String> list);  

<select id="getXXXBeanList" resultType="XXBean">
  select 字段... from XXX where id in
  <foreach item="item" index="index" collection="list" open="(" separator="," close=")">  
    #{item}  
  </foreach>  
</select>  

foreach 最后的效果是select 字段... from XXX where id in (\'1\',\'2\',\'3\',\'4\') 
复制代码

 

五、多参数传递之注解方式示:    

 

复制代码
例子:
 
public AddrInfo getAddrInfo(@Param("corpId")int corpId, @Param("addrId")int addrId);
 
xml配置这样写:
 
<select id="getAddrInfo"  resultMap="com.xxx.xxx.AddrInfo">
       SELECT * FROM addr__info 
    where addr_id=#{addrId} and corp_id=#{corpId} </select> 以前在<select>语句中要带parameterType的,现在可以不要这样写。
复制代码

 

 

六、selectList()只能传递一个参数,但实际所需参数既要包含String类型,又要包含List类型时的处理方法:

将参数放入Map,再取出Map中的List遍历。如下:

复制代码
List<String> list_3 = new ArrayList<String>();
Map<String, Object> map2 = new HashMap<String, Object>();
list.add("1");
list.add("2");
map2.put("list", list); //网址id

map2.put("siteTag", "0");//网址类型
复制代码

 

public List<SysWeb> getSysInfo(Map<String, Object> map2) {
  return getSqlSession().selectList("sysweb.getSysInfo", map2);
}

 

复制代码
<select id="getSysInfo" parameterType="java.util.Map" resultType="SysWeb">
  select t.sysSiteId, t.siteName, t1.mzNum as siteTagNum, t1.mzName as siteTag, t.url, t.iconPath
   from TD_WEB_SYSSITE t
   left join TD_MZ_MZDY t1 on t1.mzNum = t.siteTag and t1.mzType = 10
   WHERE t.siteTag = #{siteTag } 
   and t.sysSiteId not in 
   <foreach collection="list" item="item" index="index" open="(" close=")" separator=",">
       #{item}
   </foreach>
 </select>
复制代码

 

 

原文连接:MyBatis插入多个参数的问题

以上是关于MyBatis常见问题的主要内容,如果未能解决你的问题,请参考以下文章

关于 mybatis的问题

MyBatis和Mybatis-plus以及mybatis-spring的冲突解决

mybatis中带下划线变量的映射问题

MyBatis和Mybatis-plus以及mybatis-spring的冲突解决

mybatis中resultmap用法问题

MyBatis知识点