封面:洛小汐
作者:潘潘

若不是生存所迫,谁违心背负一身才华。

前言

上节咱们介绍了 《 Mybatis系列全解(四):全网最全!Mybatis配置文件 XML 全貌详解 》,内容很具体( 也很干燥),因为篇幅切实过于简短,我预计大家想看完得花上两段下班地铁公交车的工夫 。。。

不过应该有让大家理解到 Mybatis 的外围配置文件 config.xml 全貌,其中的 <mappers></mappers> 元素即是咱们本节筹备退场介绍的 SQL 映射器,上节有介绍了三种引入 SQL 映射器的形式,本节咱们就次要聊聊它的几个顶级元素用法。

Mybatis 真正弱小就在于它的语句映射,这是它的魔力所在,也是基石。因为它异样弱小,映射器的 XML 文件就显得绝对简略。如果拿它跟具备雷同性能的 JDBC 代码进行比照,你会立刻发现省掉了将近 95% 的代码( 95% 是Mybatis 官网的说法 ,我也就引入一下 ),MyBatis 致力于缩小应用老本,让用户能更专一于 SQL 代码。

Mybatis系列全解脑图分享,继续更新中

Mybaits系列全解 (继续更新)

  • Mybatis系列全解(一):手写一套长久层框架
  • Mybatis系列全解(二):Mybatis简介与环境搭建
  • Mybatis系列全解(三):Mybatis简略CRUD应用介绍
  • Mybatis系列全解(四):全网最全!Mybatis配置文件XML全貌详解
  • Mybatis系列全解(五):全网最全!详解Mybatis的Mapper映射文件
  • Mybatis系列全解(六):Mybatis最硬核的API你晓得几个?
  • Mybatis系列全解(七):全息视角看Dao层两种实现形式之传统形式与代理形式
  • Mybatis系列全解(八):Mybatis的动静SQL
  • Mybatis系列全解(九):Mybatis的简单映射
  • Mybatis系列全解(十):Mybatis注解开发
  • Mybatis系列全解(十一):Mybatis缓存全解
  • Mybatis系列全解(十二):Mybatis插件开发
  • Mybatis系列全解(十三):Mybatis代码生成器
  • Mybatis系列全解(十四):Spring集成Mybatis
  • Mybatis系列全解(十五):SpringBoot集成Mybatis
  • Mybatis系列全解(十六):Mybatis源码分析

目录

1、mapper 映射器顶级元素全貌
2、namespace 命名空间
3、select 查问
4、insert / update / delete 增删改
5、cache 缓存
6、cache-ref 缓存援用
7、sql 语句块
8、parameterMap 参数映射
9、总结

mapper 映射器顶级元素全貌

与其它 ORM 框架如 Hibernate 不同,Mybatis 的框架思维心愿开发者可能间接操作数据库编写 SQL,而不是暗藏起来,让开发者单独面对 Java 对象,为此 Mybatis 设计了 SQL 映射器,任你五招十二式。

映射器有九大顶级元素 ,基本技能介绍

  • select : 用于查问,反对传参,返回指定后果集;
  • insert : 用于新增,反对传参,返回指定后果集;
  • update : 用于更新,反对传参,返回指定后果集;
  • delete : 用于删除,反对传参,返回指定后果集;
  • sql : 被其它语句援用的 可复用 语句块;
  • cache : 以后命名空间缓存配置;
  • cache-ref : 援用其它命名空间的缓存配置;
  • parameterMap : 参数映射,已弃用,是它不够好;
  • resultMap : 后果集映射,它就很好;
其中,增删改查操作拼接 SQL 时应用到的 动静SQL( if、where、foreach啥的),以及封装后果集时应用到的 简单映射 (1对1 ,1对多,多对多啥的),这两局部咱们前面单立文章再具体介绍,本文中咱们简略点过。

九大顶级元素 ,性能归类:

其中顶一元素 parameterMap 已倡议弃用了 。

无论你有如许简单的 SQL 操作,最基本的思路都逃不出以上 4 局部。

namespace 命名空间

一个残缺的 Mapper 映射文件,须要有束缚头 xml 与 !DOCTYPE ,其次才是 mapper 根元素,最初再是顶级元素,而其中,namespace 属性作为 mapper 的惟一标识,试回顾:

  • 上学时,6年级一班23号,能代表惟一的你。
  • 编写 Java 类时,包名 + 类名,能代表惟一的类。
  • 而现在,咱们在 Mybatis 中写的每一段 SQL 语句,同样有惟一的代表形式,那就是「 命名空间标识 + 语句id 」,无论是为了辨别业务也好,还是为了拆分服务也好,反正 Mybatis 让每一个 mapper.xml 装备一个惟一命名空间标识。

每一段 SQL 语句都是惟一定义的,咱们在 Mybatis 中用「 命名空间标识 + 语句块 ID 」作为惟一的标识,组合之后在 Mybatis 二级缓存中能够作为本地 map 汇合 缓存 的惟一Key ,也能够用于 Dao 接口的 映射 绑定,还能作为惟一 代理 标识。总之,咱们心愿防止命名抵触和反复定义,所以,领有这么一个惟一标识 ,它就至多有一亿个利好。

select 查问

select 查问语句,简直是咱们最高频的应用元素,所以 Mybatis 在这块没少下功夫,目标就是通过提供尽可能多的便当,让咱们的查问操作变得简略。 一个查问用户 User 的查问语句能够这么编写:

<select id="selectUser" parameterType="int" resultType="hashmap">  select * from t_user where id = #{id}</select>
  • id属性:在以后 mapper.xml 命名空间下,它的 id 值是惟一的( 不过如果在不同的 mapper.xml 命名空间下,则容许有雷同的的 id 值 )
  • parameterType 属性:代表传入的参数类型,这里是 int (或 Integer)类型
  • resultType属性:代表返回后果类型,这里指定返回一个 hashMap 类型的对象,mybatis 会把查问进去的数据表记录对应的 ' 字段列名 - 字段值 ',主动映射为 map 汇合的 key - value
当然如果你不心愿通过 hashmap 来接管查问后果,容许你自在指定返回类型。Mybatis 是反对主动绑定 JavaBean 的,咱们只有让查问返回的字段名和 JavaBean 的属性名保持一致(或者采纳驼峰式命名),便能够主动映射后果集,例如你创立一个 Java 类 User.java ,蕴含两个属性 id 和 name , 那么后果集能够指定为 com.vo.User ,就实现了。
<select id="selectUser"     parameterType="int" resultType="com.vo.User">  select * from t_user where id = #{id}</select>

留神参数符号:

#{id}

{} 通知 MyBatis 创立一个预编译语句(PreparedStatement)参数,在 JDBC 中,这样的一个参数在 SQL 中会由一个 “ ? ” 来标识,并被传递到一个新的预编译语句中,就像这样:

// 近似的 JDBC 代码,非 MyBatis 代码...String selectUser = " select * from t_user where id = ? ";PreparedStatement ps = conn.prepareStatement(selectUser);ps.setInt(1,id);

{} 作为占位符,${} 作为替换符,两者没有孰轻孰重,只不过利用场景不同,适当取舍即可。

咱们心愿实现相似 JDBC 中的 PrepareStatement 预编译解决 ,能够应用 #{} ,它会在替换占位符时首尾增加上单引号 '' ,能无效避免 SQL 注入 危险。

例如应用 ${} 操作删除 ( 就很有问题!)

// 1、应用 ${} 有注入危险delete from t_user where id = ${id}// 2、失常传值,id 传入 1  delete from t_user where id = 1// 后果删除了id=1 的记录    // 3、注入危险,id 传入 1 or 1=1 delete from t_user where id = 1 or 1=1// 全表删除了

再看看 #{} 是如何躲避 SQL 注入 的:

// 1、应用 #{} 无效避免注入危险delete from t_user where id = #{id}// 2、失常传值,id 传入 1   delete from t_user where id = '1'// 后果删除了id=1 的记录    // 3、注入危险,id 传入 1 or 1=1 delete from t_user where id = '1 or 1=1'// SQL 语句报错,表数据安全

尽管在避免 SQL 注入方面,${} 的确无能为力,不过咱们 ${} 在其它方面可不容小觑,例如它容许你灵便地进行 动静表和动静列名的替换 操作,例如:

// 1、灵便查问指定表数据select * from ${tableName} // 传入 tableName参数 = t_user , 后果select * from t_user  // 2、灵便查问不同列条件数据select * from t_user where ${colunmName} = ${value}// 传入 colunmName参数 = name , value参数 = '潘潘', 后果select * from t_user where name = '潘潘'// 传入 colunmName参数 = id , value参数 = 1, 后果select * from t_user where id = 1
以上的 ${} 替换列名与表名的形式非常灵活,不过的确存在 SQL 注入危险,所以在思考应用 #{} 或 ${} 前,须要评估危险,防止危险,容许的状况下,我倡议应用 #{} 。

当然,select 元素容许你配置很多属性来配置每条语句的行为细节。

<select       id="selectUser"   parameterType="int"  parameterMap="deprecated"  resultType="hashmap"  resultMap="personResultMap"  flushCache="false"  useCache="true"  timeout="10"  fetchSize="256"  statementType="PREPARED"  resultSetType="FORWARD_ONLY"  databaseId="mysql"  resultOrdered="false"  resultSets="rs1,rs2,rs3">  select * from t_user</select>

上面具体介绍一下,稍微简短,一口气看完吧:

  • id 必填项,在命名空间下的惟一标识,可被 Mybatis 援用,如果存在雷同的 “ 命名空间 + 语句id ” 组合,Mybatis 将抛出异样;
  • parameterType 可选项,传入语句的参数的类全限定名或别名,能够是根本类型、map 或 JavaBean 等简单的参数类型传递给 SQL;
  • parameterMap 用于援用内部 parameterMap 的属性块,目前已被废除。当前请应用行内参数映射和 parameterType 属性。
  • resultType 可选项,定义类的全门路,在容许主动匹配的状况下,后果集将通过 Javaben 的标准映射,或定义为 int 、double、float 等参数;也能够应用别名,然而要合乎别名标准和定义。 resultType 和 resultMap 之间只能同时应用一个。(日常中,比方咱们统计后果总条数的时候能够设置为 int );
  • resultMap 可选项,对外部 resultMap 的命名援用。后果映射是 MyBatis 最弱小的个性,如果你对其了解透彻,许多简单的映射问题都能迎刃而解,前面一对一、一对多、多对多咱们会有一篇文章独自解说。 resultType 和 resultMap 之间只能同时应用一个。
  • flushCache 可选项,清空缓存,将其设置为 true 后,只有语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。
  • useCache 可选项,应用缓存,将其设置为 true 后,将会导致本条语句的后果被二级缓存缓存起来,默认值:对 select 元素为 true。
  • timeout 可选项,这个设置是在抛出异样之前,驱动程序期待数据库返回申请后果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
  • fetchSize 可选项,获取记录的总条数设定。这是一个给驱动的倡议值,尝试让驱动程序每次批量返回的后果行数等于这个设置值。 默认值为未设置(unset)(依赖驱动)。因为性能问题,倡议在 sql 做分页解决。
  • statementType 可选项,可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 别离应用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
  • resultSetType 可选项,FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。

    • FORWARD_ONLY,只容许游标向前拜访;
    • SCROLL_SENSITIVE,容许游标双向滚动,但不会及时更新数据,也就是说如果数据库中的数据被批改过,并不会在resultSet中及时更新进去;
    • SCROLL_INSENSITIVE ,容许游标双向滚动,如果数据库中的数据被批改过,会及时更新到resultSet;
咱们晓得 JDBC 通过 ResultSet 来对查问后果进行封装,ResultSet 对象自身蕴含了一个由查问语句返回的一个后果汇合。例如你常常在 JDBC 见过的后果集读取:
// 容许滚动游标索引后果集while( rs.next() ){    rs.getString("name");}// 当然也反对游标定位到最初一个地位rs.last();// 向后滚动rs.previous();
  • databaseId 可选项,如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配以后 databaseId 的语句;如果带和不带的语句都有,则不带的会被疏忽。
  • resultOrdered 可选项,这个设置仅针对嵌套后果 select 语句:如果为 true,将会假如蕴含了嵌套后果集或是分组,当返回一个主后果行时,就不会产生对后面后果集的援用。 这就使得在获取嵌套后果集的时候不至于内存不够用。默认值:false。
  • resultSets 这个设置仅实用于多后果集的状况。它将列出语句执行后返回的后果集并赋予每个后果集一个名称,多个名称之间以逗号分隔。

insert / update / delete 增删改

数据变更语句 insert,update 和 delete 的实现十分靠近,而且绝对于 select 元素而言要简略许多。

<insert  id="insertUser"  parameterType="domain.vo.User"  flushCache="true"  statementType="PREPARED"  keyProperty=""  keyColumn=""  useGeneratedKeys=""  timeout="20"><update  id="updateUser"  parameterType="domain.vo.User"  flushCache="true"  statementType="PREPARED"  timeout="20"><delete  id="deleteUser"  parameterType="domain.vo.User"  flushCache="true"  statementType="PREPARED"  timeout="20">

其中大部分属性和 select 元素雷同,咱们介绍 3 个不同的属性:

  • useGeneratedKeys : (仅实用于 insert 和 update)这会令 MyBatis 应用 JDBC 的 getGeneratedKeys 办法来取出由数据库外部生成的主键(比方:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的主动递增字段),默认值:false。
  • keyProperty : (仅实用于 insert 和 update)指定可能惟一辨认对象的属性,MyBatis 会应用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,能够用逗号分隔多个属性名称。
  • keyColumn : (仅实用于 insert 和 update)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,能够用逗号分隔多个属性名称。

咱们先看看 insert,update 和 delete 语句的示例:

<insert id="insertUser">  insert into t_user (id,name)   values (#{id},#{name})</insert><update id="updateUser">  update t_user set name = #{name} where id = #{id}</update><delete id="deleteUser">  delete from t_user where id = #{id}</delete>

如前所述,插入语句的配置规定更加丰盛,在插入语句外面有一些额定的属性和子元素用来解决主键的生成,并且提供了多种生成形式。

首先,如果你的数据库反对 主动生成主键 的字段(比方 MySQL 和 SQL Server),那么你能够设置 useGeneratedKeys=”true”,而后再把 keyProperty 设置为指标属性就 OK 了。例如,如果下面的 t_user 表曾经在 id 列上应用了主动生成,那么语句能够批改为:

<insert id="insertUser" useGeneratedKeys="true"    keyProperty="id">  insert into t_user (name) values (#{name})</insert>

如果你的数据库还反对多行插入, 你也能够传入一个 User 数组或汇合,并返回主动生成的主键。

<insert id="insertUser" useGeneratedKeys="true"    keyProperty="id">    insert into t_user (name) values        <foreach item="item" collection="list" separator=",">    (#{item.name})  </foreach></insert>

对于不反对主动生成主键列的数据库和可能不反对主动生成主键的 JDBC 驱动,MyBatis 有另外一种办法来生成主键

这里有一个简略(也很傻)的示例,它能够生成一个随机 ID(不倡议理论应用,这里只是为了展现 MyBatis 解决问题的灵活性和宽容度):

<insert id="insertUser">   <selectKey keyProperty="id" resultType="int" order="BEFORE">    select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1  </selectKey>      insert into t_user (id, name)  values  (#{id}, #{name})</insert>

在下面的示例中,首先会运行 selectKey 元素中的语句,并设置 User 的 id,而后才会调用插入语句。这样就实现了数据库主动生成主键相似的行为,同时放弃了 Java 代码的简洁。

selectKey 元素形容如下:

<selectKey  keyProperty="id"  resultType="int"  order="BEFORE"  statementType="PREPARED">

selectKey 中的 order 属性有2个抉择:BEFORE 和 AFTER 。

  • BEFORE:示意先执行selectKey的语句,而后将查问到的值设置到 JavaBean 对应属性上,而后再执行 insert 语句。
  • AFTER:示意先执行 AFTER 语句,而后再执行 selectKey 语句,并将 selectKey 失去的值设置到 JavaBean 中的属性。下面示例中如果改成 AFTER,那么插入的 id 就会是空值,然而返回的 JavaBean 属性内会有值。

cache 缓存

缓存对于互联网零碎来说特地常见,其特点就是将数据保留在内存中。MyBatis 内置了一个弱小的事务性查问缓存机制,它能够十分不便地配置和定制。 为了使它更加弱小而且易于配置,咱们对 MyBatis 3 中的缓存实现进行了许多改良。

默认状况下,只启用了本地的会话缓存(即一级缓存,sqlSession级别 ),它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,首先在全局配置文件config.xml文件中退出如下代码:

<!--开启二级缓存--> <settings>     <setting name="cacheEnabled" value="true"/> </settings>

其次在UserMapper.xml文件中开启缓存:

<!--开启二级缓存--> <cache></cache>

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

  • 映射语句文件中的所有 select 语句的后果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会应用最近起码应用算法(LRU, Least Recently Used)算法来革除不须要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新距离)。
  • 缓存会保留列表或对象(无论查询方法返回哪种)的 1024 个援用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,能够平安地被调用者批改,而不烦扰其余调用者或线程所做的潜在批改。
缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合应用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你须要应用 @CacheNamespaceRef 注解指定缓存作用域。

这些属性能够通过 cache 元素的属性来批改。比方:

<cache  eviction="FIFO"  flushInterval="60000"  size="512"  readOnly="true"/>

下面示意了一套更高级的缓存配置,首先创立了一个 FIFO 缓存,每隔 60 秒刷新,最多能够存储后果对象或列表的 512 个援用,而后返回的对象被设置成只读的,因而对它们进行批改可能会在不同线程中的调用者产生抵触。

缓存可用的革除策略有:

  • LRU – 最近起码应用:移除最长工夫不被应用的对象。
  • FIFO – 先进先出:按对象进入缓存的程序来移除它们。
  • SOFT – 软援用:基于垃圾回收器状态和软援用规定移除对象。
  • WEAK – 弱援用:更踊跃地基于垃圾收集器状态和弱援用规定移除对象。
默认的革除策略是 LRU

flushInterval(刷新距离)属性能够被设置为任意的正整数,设置的值应该是一个以毫秒为单位的正当工夫量。 默认状况是不设置,也就是没有刷新距离,缓存仅仅会在调用语句时刷新。

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

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

二级缓存是事务性的。这意味着,当 SqlSession 实现并提交 ( commit ) 时,或是实现并回滚 ( close ) 时,二级缓存都会被刷新。不论是否配置了 flushCache=true 。

Mybatis 的缓存包含一级缓存(sqlSession 级别)和二级缓存(mapper 级别),所以 mapper 映射器中配置的是二级缓存,咱们先大略晓得有这个概念,因为后续咱们会针对这两种缓存进行具体介绍,而且还会解说如何自定义缓存,因为 Mybatis 的缓存默认都是以 map 的数据结构存储在本地,所以自定义缓存能够把存储介质拓展到磁盘或数据库redis等;而且一级缓存是默认开启的,二级缓存须要咱们手工开启,这些后续都会具体解说,提前预报。

缓存获取程序:二级缓存 > 一级缓存 > 数据库

cache-ref 援用缓存

回忆一下 cache 的内容,对某一命名空间的语句,只会应用该命名空间的缓存进行缓存或刷新。 但你可能会想要在多个命名空间中共享雷同的缓存配置和实例。要实现这种需要,你能够应用 cache-ref 元素来援用另一个缓存。

<cache-ref namespace="com.vo.UserMapper"/>

sql 语句块

这个元素能够用来定义可重用的 SQL 代码片段,以便在其它语句中应用。 参数能够动态地(在加载的时候)确定下来,并且能够在不同的 include 元素中定义不同的参数值。比方:

<sql id="userColumns">     ${alias}.id,${alias}.name </sql>

这个 SQL 片段能够在其它语句中应用,例如:

<select id="selectUsers" resultType="map">  select    <include refid="userColumns">        <property name="alias" value="t1"/>    </include>,    <include refid="userColumns">        <property name="alias" value="t2"/>    </include>  from t_user t1 cross join t_user t2</select>

也能够在 include 元素的 refid 属性或多层外部语句中应用属性值,例如:

<sql id="sql1">  ${prefix}_user</sql><sql id="sql2">  from    <include refid="${include_target}"/></sql><select id="select" resultType="map">  select    id, name  <include refid="sql2">    <property name="prefix" value="t"/>    <property name="include_target" value="sql1"/>  </include></select>

parameterMap 参数映射

parameterMap 元素官网曾经不倡议应用,并且再后续版本会退出舞台。首先对于咱们 Java 来说,特地不心愿在代码中通过传递 map 来传参,这样对于后续保护或者参数查找都是极不负责任的,咱们举荐应用 JavaBean 来传值参数,这是 parameterMap 被摈弃的其中一个起因;另外也因为 parameterType 属性的诞生就能很好的代替 parameterMap ,并且还能自定义 JavaBean 类型的传参,所以 parameterMap 退出舞台,实属失常。

总结

我始终来都心愿本人只输入观点,而不是输入字典,但其中有些知识点又是极其繁杂,常识输入真是个难搞的差事,如何既能把常识脉络梳理的残缺,又能讲得浅显易懂,长篇累牍,的确是后续文章合成输入的钻研方向。

本篇完,本系列下一篇咱们讲《 Mybatis系列全解(六):Mybatis最硬核的API你晓得几个? 》。

BIU ~ 文章继续更新,微信搜寻「潘潘和他的敌人们」第一工夫浏览,随时有惊喜。本文会在 GitHub https://github.com/JavaWorld 收录,热腾腾的技术、框架、面经、解决方案,咱们都会以最美的姿态第一工夫送达,欢送 Star ~ 咱们将来 不止文章!想进读者群的搭档欢送撩我集体号:panshenlian,备注「加群」咱们群里欢聊吧 ~