myBatis【常规使用】

本贴最后更新于 1362 天前,其中的信息可能已经时过境迁

myBatis

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

mybatis 和 spring 的整合,以及关于的 mybatis 配置数据源就不在这个给介绍了,本文主要是介绍 mybatis 在工作日常中一些使用。

mybatis 的接口写法

  • 使用注解,在接口的方法上面添加 @Select@Update 等注解,里面写上对应的 SQL 语句进行 SQL 语句的绑定。
  • 通过映射文件 xml 方式进行绑定,指定 xml 映射文件中的 namespace 对应的接口的全路径名

但是现在一般的项目都是整合了 mybatis_plus,所以使用使用第一种方式很少,因为如果需要写的 sql 过于复杂,一般都是使用 mapper.xml 进行 sql 写入。

动态 SQL

if

日常 sql 都要使使用 where 语句的判断,在使用 if 的过程中会需要注意的是对于 where 语句写法

第一种写法
<select id="findmybatis" resultType="user">
  SELECT * FROM user 
 <where>
  <if test="user_name!= null">
     AND user_name = #{user_name}
  </if>
  <if test="user != null and user.userId!= null">
    AND user_id = #{user.userId}
  </if>
 </where> 
</select>

第二种写法

<select id="findmybatis" resultType="user">
  SELECT * FROM user 
<trim prefix="WHERE" prefixOverrides="AND |OR ">
  <if test="user_name!= null">
     AND user_name = #{user_name}
  </if>
  <if test="user != null and user.userId!= null">
    AND user_id = #{user.userId}
  </if>
</trim>
</select>

set,trim,where

第一种使用 where 语句,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

第二种使用属于自定义类型:

  • prefix=添加前缀
  • suffix=添加后缀
  • prefixOverrides=去掉前缀
  • suffixOverrides=去掉后缀

suffixOverrides 可以用于在使用动态的更新数据,与他功能类似的是 set 语法,set 会去除最后不为空值‘,’

suffixOverrides 去除最后一个‘,’,也可以用于在其他的操作上。

第一种写法

<update id="updateuser">
  update user
    <set>
      <if test="user_name != null">user_name=#{username},</if>
      <if test="pass_word != null">pass_word=#{password},</if>
      <if test="user_email != null">user_email=#{email}</if>
    </set>
  where user_id=#{id}
</update>

第二种写法
<update id="updateuser">
  update user
    <trim prefix="set" suffixoverride="," suffix="where user_id=#{id}">
      <if test="user_name != null">user_name=#{username},</if>
      <if test="pass_word != null">pass_word=#{password},</if>
      <if test="user_email != null">user_email=#{email}</if>
    </trim>
  
</update>

foreach

使用 sql 中的 in 语句,使用 list 集合遍历查询,在使用过程中如果传递的参数是一个 map,就直接将 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.put('userids',ids)
//=========
IPage<user> queryByCondition(Page page, @Param("parameter") Parameter parameter,@Param('map') Map><String,Object> map);
第一种
<select id="finduser" resultType="user">
  SELECT *
  FROM user 
  WHERE id in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

第二种
<select id="finduser" resultType="user">
  SELECT *
  FROM user 
  WHERE id in
  <foreach item="userids" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

choose, when, otherwise

类似于 java 代码中的 switch 语句

下面的语句使用中需要注意 2 个点,在参数传递中,如果涉及到在 test 中进行字符串的比较的时候,需要注意将“”改成‘’ 里面使用“”来标识字符串,否则是失效的,使用大于,小于号的时候,使用 <![CDATA[标识 ]]> 格式处理,防止出现编译错误!

<select id="finduser"
     resultType="user">

  <bind name="pattern" value="_parameter.getUsersex()" />

  SELECT * FROM user WHERE state = ‘ACTIVE’
  <choose>
    <when test='pattern == "1"'>
      AND user_age <![CDATA[>]]> #{user_age}
    </when>
    <when test="'pattern == "2"'>
      AND user_age <![CDATA[<]]> #{user_age}
    </when>
    <otherwise>
      AND user_age = #{user_age}
    </otherwise>
  </choose>
</select>

bind

bind 元素可以从 OGNL 表达式中创建一个变量并将其绑定到上下文。比如:

<select id="selectBlogsLike" resultType="Blog">
  <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
  SELECT * FROM BLOG
  WHERE title LIKE #{pattern}
</select>

MyBatis 中#{}和 ${}区别

#{} 是预编译处理,像传进来的数据会加个" "(#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号)

${}是指就是字符串替换。直接替换掉占位符,一般使用的话,之传入表名。

使用 ${}会导致 sql 注入风险,举一个栗子:

select * from user where id = ${value}

value 应该是一个数值吧。然后如果对方传过来的是 '123' and user_name = 'hax'。直接会多个条件改动了原有的 sql 语句。如果是攻击性的语句呢?‘123;drop table user,直接威胁了数据库数据安全,所有尽量建议不使用

模糊查询的写法

记得上一次写代码的过程,我写了这样的一段代码 and username LIKE '%${username}%',有一个同事过和说,你这个代码写的生效吗?虽然说我这个写法不安全,但是至少是正确的。

我但是就直接怼回去了,我说你可能只会写 AND name LIKE CONCAT(CONCAT('%',#{name},'%')),我觉得在工作中,质疑他人前一定要有正确的结论,都是搞技术的谁都不愿意被质疑。

5 种写法,个人推荐使用第三种写法,我一般使用,至于上次为什么使用第一种,em.....,主要是想体验一下新鲜感 😂

方式 1:$ 这种方式,简单,但是无法防止 SQL 注入,所以不推荐使用

LIKE '%${username}%'

方式 2:

LIKE "%"#{username}"%" 方式 3:字符串拼接

AND username LIKE CONCAT(CONCAT('%',#{username},'%'))

方式 4:bind 标签

<select id="finduser" resultType="com.hax.entity.user"
  parameterType="com.hax.entity.user">
  <bind name="pattern1" value="'%' + _parameter.name + '%'" />
  <bind name="pattern2" value="'%' + _parameter.address + '%'" />
  SELECT * FROM user
  <where>
   <if test="name != null and name != ''">
    AND user_name LIKE #{pattern1}
   </if>
   <if test="address != null and address != ''">
    AND user_address LIKE #{pattern2}
   </if>
  </where>
 </select>

方式 5:java 代码里写

param.setUsername("%username%");

在 java 代码中传参的时候直接写上

<if test="username!=null"> AND username LIKE #{username}</if>

mybatis 如何获取自增的主建 id

insert 方法总是返回一个 int 值 ,这个值代表的是插入的行数。如果采用自增长策略,自动生成的键值在 insert 方法执行完后可以被设置到对象对应的属性中

示例:mysql 中加入 usegeneratedkeys="true" keyproperty="id"


<insert id="insertname" usegeneratedkeys="true" keyproperty="
id">
insert into names (name) values (#{name})
</insert>

java代码
user user = new user();
user.setname("username");
int rows = mapper.insertname(user);
system.out.println("user-id"+ rows.getId());

orcal 在这里不介绍,自行百度

MyBatis 传递多个参数

  • 方法一:使用 map 接口传递参数
public List<user> finduserBymap(Map<String, Object> parameterMap);

<select id="findRolesByMap" parameterType="map" resultType="role">
   select *from user where user_id =#{id}
</select>

在 mapper 文件中直接使用 map 在 put 的值 栗子:map.put('id',id); 使用就是#{id}

  • 方法二:使用注解传递多个参数

MyBatis 为开发者提供了一个注解 @Param(org.apache.ibatis.annotations.Param),可以通过它去定义映射器的参数名称,使用它可以得到更好的可读性

public List<user> finduserByAnnotation(@Param("username") String username, @Param("sex") String sex);

<select id="finduserByAnnotation" resultType="role">
 select *from user where user_name like CONCAT('%'+#{username}+'%') and user_sex = #{sex}
</select

  • 方法三:通过 Java Bean 传递多个参数,其实就是自己定义个包装类进行封装即可。

总体的来说,方法一,使用起来简洁,但是可读性不强,你不能很直观看到自己传入的参数类型,方法二的就可读性最强,但是当需要传入参数过多的时候,在写代码的时候,也是不是很直观,会显得繁琐建议参数小于 5 个,方法三,一般使用包装类的话,只能说明参数传入比较特殊,必然复用性不好。

参数多推荐 一或者三

参数少推荐 二

mybatis 的缓存机制

官方网站写的太好了。我怕我的语言组织误导大家,所以直接 copy 过来了 😭 地址

缓存机制减轻数据库压力,提高数据库性能

mybatis 的缓存分为两级:一级缓存、二级缓存

  • 一级缓存:

一级缓存为 sqlsesson 缓存,缓存的数据只在 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 保存的
  • 二级缓存:

二级缓存是 <span> </span>mapper 级别的缓存,也就是同一个 namespace 的 mapper.xml ,当多个 SqlSession 使用同一个 Mapper 操作数据库的时候,得到的数据会缓存在同一个二级缓存区域

二级缓存默认是没有开启的。需要在 setting 全局参数中配置开启二级缓存

开启二级缓存步骤:

  1. conf.xml 配置全局变量开启二级缓存
<settings>
    <setting name="cacheEnabled" value="true"/>默认是false:关闭二级缓存
<settings>
  1. <span> </span>userMapper.xml<span> </span> 中配置
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>当前mapper下所有语句开启二级缓存

基本上就是这样。这个简单语句的效果如下:

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。可用的清除策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

默认的清除策略是 LRU。

flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。

这里配置了一个 LRU 缓存,并每隔 60 秒刷新,最大存储 512 个对象,而返回的对象是只读的

若想禁用当前 select 语句的二级缓存,添加 useCache="false" 修改如下:

<select id="getCountByName" parameterType="java.util.Map" resultType="INTEGER" statementType="CALLABLE" useCache="false">

具体流程:

1.当一个 <span> </span>sqlseesion<span> </span> 执行了一次 <span> </span>select 后,在关闭此 <span> </span>session 的时候,会将查询结果缓存到二级缓存

2.当另一个 <span> </span>sqlsession<span> </span> 执行 <span> </span>select 时,首先会在他自己的一级缓存中找,如果没找到,就回去二级缓存中找,找到了就返回,就不用去数据库了,从而减少了数据库压力提高了性能

注意:

  1. 如果 SqlSession 执行了 DML 操作 (insert、update、delete),并 commit 了,那么 mybatis 就会清空当前 <span> </span>mapper 缓存中的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现差异
  2. <span> </span>mybatis 的缓存是基于 <span> </span>[namespace:sql语句:参数]<span> </span> 来进行缓存的,意思就是,SqlSessionHashMap 存储缓存数据时,是使用 [namespace:sql:参数]<span> </span> 作为 key ,查询返回的语句作为 value 保存的。

mybatis 的缓存机制用的很少,包括于它的一级缓存,使用的人都很少,主要原因是因为对于缓存的机制不够好很容易出现脏读的情况,使用一级缓存的时候,因为缓存不能跨会话共享,不同的会话之间对于相同的数据可能有不一样的缓存。在有多个会话或者分布式环境下,会存在脏数据的问题。如果要解决这个问题,就要用到二级缓存。MyBatis 一级缓存(MyBaits 称其为 Local Cache)无法关闭,但是有两种级别可选:

  • session 级别的缓存,在同一个 sqlSession 内,对同样的查询将不再查询数据库,直接从缓存中。
  • statement 级别的缓存,避坑: 为了避免这个问题,可以将一级缓存的级别设为 statement 级别的,这样每次查询结束都会清掉一级缓存。

注意这里的 session 是 sqlsession 会话,不是用户的的 session 会话,MyBatis 一级缓存的生命周期和 SqlSession 一致,MyBatis 的一级缓存最大范围是 SqlSession 内部,有多个 SqlSession 或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为 Statement,分布式的开发环境中,直接使用 Redis、Memcached 等分布式缓存可能成本更低,安全性也更高。

  • MyBatis

    MyBatis 本是 Apache 软件基金会 的一个开源项目 iBatis,2010 年这个项目由 Apache 软件基金会迁移到了 google code,并且改名为 MyBatis ,2013 年 11 月再次迁移到了 GitHub。

    170 引用 • 414 回帖 • 387 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...