逗号分隔值文件-这下够清楚了吧!详解Mybatis的Mapper映射文件

生活百科1年前 (2023)发布 aixure
89 0 0

前言

上节我们介绍了 《 Mybatis系列全解(四):全网最全!Mybatis配置文件 XML 全貌详解 》,内容很详细( 也很枯燥),由于篇幅实在过于冗长逗号分隔值文件,我预计大家想看完得花上两段上班地铁公交车的时间 。。。

逗号分隔值文件-这下够清楚了吧!详解Mybatis的Mapper映射文件

不过应该有让大家了解到 Mybatis 的核心配置文件 config.xml 全貌,其中的 元素即是我们本节准备登场介绍的 SQL 映射器,上节有介绍了三种引入 SQL 映射器的方式,本节我们就主要聊聊它的几个顶级元素用法。

Mybatis 真正强大就在于它的语句映射,这是它的魔力所在,也是基石。由于它异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码( 95% 是Mybatis 官网的说法 ,我也就引入一下 ),MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。

一、mapper 映射器顶级元素全貌

与其它 ORM 框架如 Hibernate 不同,Mybatis 的框架思想希望开发者能够直接操作数据库编写 SQL,而不是隐藏起来,让开发者独自面对 Java 对象,为此 Mybatis 设计了 SQL 映射器,任你五招十二式。

逗号分隔值文件-这下够清楚了吧!详解Mybatis的Mapper映射文件

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

其中,增删改查操作拼接 SQL 时使用到的 动态SQL( if、where、foreach啥的),以及封装结果集时使用到的 复杂映射 (1对1 ,1对多,多对多啥的),这两部分我们后面单立文章再详细介绍,本文中我们简单点过。

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

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

无论你有多么复杂的 SQL 操作,最根本的思路都逃不出以上 4 部分。

二、namespace 命名空间

一个完整的 Mapper 映射文件,需要有约束头 xml 与 !DOCTYPE ,其次才是 mapper 根元素,最后再是顶级元素,而其中,namespace 属性作为 mapper 的唯一标识,试回忆:

逗号分隔值文件-这下够清楚了吧!详解Mybatis的Mapper映射文件

每一段 SQL 语句都是唯一定义的,我们在 Mybatis 中用「 命名空间标识 + 语句块 ID 」作为唯一的标识,组合之后在 Mybatis 二级缓存中可以作为本地 map 集合 缓存 的唯一Key ,也可以用于 Dao 接口的 映射 绑定,还能作为唯一 代理 标识。总之,我们希望避免命名冲突和重复定义,所以,拥有这么一个唯一标识 ,它就至少有一亿个利好。

三、select 查询

select 查询语句,几乎是我们最高频的使用元素,所以 Mybatis 在这块没少下功夫,目的就是通过提供尽可能多的便利,让我们的查询操作变得简单。 一个查询用户 User 的查询语句可以这么编写:


  select * from t_user where id = #{id}

当然如果你不希望通过 hashmap 来接收查询结果,允许你自由指定返回类型。Mybatis 是支持自动绑定 JavaBean 的,我们只要让查询返回的字段名和 JavaBean 的属性名保持一致(或者采用驼峰式命名),便可以自动映射结果集,例如你创建一个 Java 类 User.java ,包含两个属性 id 和 name , 那么结果集可以指定为 com.vo.User ,就完成了。


  select * from t_user where id = #{id}

注意参数符号:

#{id}

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

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

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

逗号分隔值文件-这下够清楚了吧!详解Mybatis的Mapper映射文件

我们希望完成类似 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 * from t_user

下面详细介绍一下,略微冗长,一口气看完吧:

我们知道 JDBC 通过 ResultSet 来对查询结果进行封装,ResultSet 对象本身包含了一个由查询语句返回的一个结果集合。例如你经常在 JDBC 见过的结果集读取:

// 允许滚动游标索引结果集
while( rs.next() ){
    rs.getString("name");
}
// 当然也支持游标定位到最后一个位置
rs.last();
// 向后滚动
rs.previous();

四、insert / update / delete 增删改

数据变更语句 insert,update 和 delete 的实现非常接近,而且相对于 select 元素而言要简单许多。



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

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


  insert into t_user (id,name) 
  values (#{id},#{name})


  update t_user set name = #{name} where id = #{id}


  delete from t_user where id = #{id}

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

首先,如果你的数据库支持 自动生成主键 的字段(比如 MySQL 和 SQL Server),那么你可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置为目标属性就 OK 了。例如,如果上面的 t_user 表已经在 id 列上使用了自动生成,那么语句可以修改为:


  insert into t_user (name) values (#{name})

如果你的数据库还支持多行插入, 你也可以传入一个 User 数组或集合,并返回自动生成的主键。


  
  insert into t_user (name) values  
    
  
    (#{item.name})
  

对于不支持自动生成主键列的数据库和可能不支持自动生成主键的 JDBC 驱动,MyBatis 有另外一种方法来生成主键。

这里有一个简单(也很傻)的示例,它可以生成一个随机 ID(不建议实际使用,这里只是为了展示 MyBatis 处理问题的灵活性和宽容度):


 
  
    select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
  
    
  insert into t_user (id, name)
  values  (#{id}, #{name})

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

selectKey 元素描述如下:

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

五、cache 缓存

缓存对于互联网系统来说特别常见,其特点就是将数据保存在内存中。MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。

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

 
 
     

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

 

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

缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。

这些属性可以通过 cache 元素的属性来修改。比如:

上面表示了一套更高级的缓存配置,首先创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,然后返回的对象被设置成只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

缓存可用的清除策略有:

默认的清除策略是 LRU

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

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

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

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

逗号分隔值文件-这下够清楚了吧!详解Mybatis的Mapper映射文件

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

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

六、cache-ref 引用缓存

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

七、sql 语句块

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

逗号分隔值文件-这下够清楚了吧!详解Mybatis的Mapper映射文件

 
    ${alias}.id,${alias}.name 

这个 SQL 片段可以在其它语句中使用,例如:


  select
    
        
    ,
    
        
    
  from t_user t1 cross join t_user t2

也可以在 include 元素的 refid 属性或多层内部语句中使用属性值,例如:


  ${prefix}_user


  from
    


  select
    id, name
  
    
    
  

八、parameterMap 参数映射

parameterMap 元素官方已经不建议使用,并且再后续版本会退出舞台。首先对于我们 Java 来说,特别不希望在代码中通过传递 map 来传参,这样对于后续维护或者参数查找都是极不负责任的,我们推荐使用 JavaBean 来传值参数,这是 parameterMap 被抛弃的其中一个原因;另外也由于 parameterType 属性的诞生就能很好的代替 parameterMap ,并且还能自定义 JavaBean 类型的传参,所以 parameterMap 退出舞台,实属正常。

逗号分隔值文件-这下够清楚了吧!详解Mybatis的Mapper映射文件

九、总结

我一直来都希望自己只输出观点,而不是输出字典,但其中有些知识点又是极其冗杂,知识输出真是个难搞的差事,如何既能把知识脉络梳理的完整,又能讲得浅显易懂逗号分隔值文件,言简意赅,确实是后续文章分解输出的研究方向。

限时特惠:本站每日持续更新海量各大内部网赚创业教程,会员可以下载全站资源点击查看详情
站长微信:

© 版权声明

相关文章

暂无评论

暂无评论...