乐趣区

关于java:Mybatis系列全解五全网最全详解Mybatis的Mapper映射文件

封面:洛小汐
作者:潘潘

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

前言

上节咱们介绍了《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,备注「 加群」咱们群里欢聊吧 ~

退出移动版