封面:洛小汐
做者:潘潘java
若不是生活所迫,谁愿意背负一身才华。mysql
上节咱们介绍了 《 Mybatis系列全解(四):全网最全!Mybatis配置文件 XML 全貌详解 》,内容很详细( 也很枯燥),因为篇幅实在过于冗长,我预计你们想看完得花上两段上班地铁公交车的时间 。。。git
不过应该有让你们了解到 Mybatis 的核心配置文件 config.xml 全貌,其中的 <mappers></mappers> 元素便是咱们本节准备登场介绍的 SQL 映射器,上节有介绍了三种引入 SQL 映射器的方式,本节咱们就主要聊聊它的几个顶级元素用法。github
Mybatis 真正强大就在于它的语句映射,这是它的魔力所在,也是基石。因为它异常强大,映射器的 XML 文件就显得相对简单。若是拿它跟具备相同功能的 JDBC 代码进行对比,你会当即发现省掉了将近 95% 的代码( 95% 是Mybatis 官网的说法 ,我也就引入一下 ),MyBatis 致力于减小使用成本,让用户能更专一于 SQL 代码。redis
Mybatis系列全解脑图分享,持续更新中算法
一、mapper 映射器顶级元素全貌sql
二、namespace 命名空间数据库
三、select 查询数组
四、insert / update / delete 增删改缓存
五、cache 缓存
六、cache-ref 缓存引用
七、sql 语句块
八、parameterMap 参数映射
九、总结
与其它 ORM 框架如 Hibernate 不一样,Mybatis 的框架思想但愿开发者可以直接操做数据库编写 SQL,而不是隐藏起来,让开发者独自面对 Java 对象,为此 Mybatis 设计了 SQL 映射器,任你五招十二式。
映射器有九大顶级元素 ,基本技能介绍
其中,增删改查操做拼接 SQL 时使用到的 动态SQL( if、where、foreach啥的),以及封装结果集时使用到的 复杂映射 (1对1 ,1对多,多对多啥的),这两部分咱们后面单立文章再详细介绍,本文中咱们简单点过。
九大顶级元素 ,功能归类:
其中顶一元素 parameterMap 已建议弃用了 。
不管你有多么复杂的 SQL 操做,最根本的思路都逃不出以上 4 部分。
一个完整的 Mapper 映射文件,须要有约束头 xml 与 !DOCTYPE ,其次才是 mapper 根元素,最后再是顶级元素,而其中,namespace 属性做为 mapper 的惟一标识,试回忆:
每一段 SQL 语句都是惟必定义的,咱们在 Mybatis 中用「 命名空间标识 + 语句块 ID 」做为惟一的标识,组合以后在 Mybatis 二级缓存中能够做为本地 map 集合 缓存 的惟一Key ,也能够用于 Dao 接口的 映射 绑定,还能做为惟一 代理 标识。总之,咱们但愿避免命名冲突和重复定义,因此,拥有这么一个惟一标识 ,它就至少有一亿个利好。
select 查询语句,几乎是咱们最高频的使用元素,因此 Mybatis 在这块没少下功夫,目的就是经过提供尽量多的便利,让咱们的查询操做变得简单。 一个查询用户 User 的查询语句能够这么编写:
<select id="selectUser" parameterType="int" resultType="hashmap"> select * from t_user where id = #{id} </select>
固然若是你不但愿经过 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 注入 风险。
例如使用 ${} 操做删除 ( 就颇有问题!)
// 一、使用 ${} 有注入风险 delete from t_user where id = ${id} // 二、正常传值,id 传入 1 delete from t_user where id = 1 // 结果删除了id=1 的记录 // 三、注入风险,id 传入 1 or 1=1 delete from t_user where id = 1 or 1=1 // 全表删除了
再看看 #{} 是如何规避 SQL 注入 的:
// 一、使用 #{} 有效防止注入风险 delete from t_user where id = #{id} // 二、正常传值,id 传入 1 delete from t_user where id = '1' // 结果删除了id=1 的记录 // 三、注入风险,id 传入 1 or 1=1 delete from t_user where id = '1 or 1=1' // SQL 语句报错,表数据安全
虽然在防止 SQL 注入方面,${} 确实无能为力,不过咱们 ${} 在其它方面可不容小觑,例如它容许你灵活地进行 动态表和动态列名的替换 操做,例如:
// 一、灵活查询指定表数据 select * from ${tableName} // 传入 tableName参数 = t_user , 结果 select * from t_user // 二、灵活查询不一样列条件数据 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 最强大的特性,若是你对其理解透彻,许多复杂的映射问题都能迎刃而解,后面一对1、一对多、多对多咱们会有一篇文章单独讲解。 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 (依赖数据库驱动)。
咱们知道 JDBC 经过 ResultSet 来对查询结果进行封装,ResultSet 对象自己包含了一个由查询语句返回的一个结果集合。例如你常常在 JDBC 见过的结果集读取:
// 容许滚动游标索引结果集 while( rs.next() ){ rs.getString("name"); } // 固然也支持游标定位到最后一个位置 rs.last(); // 向后滚动 rs.previous();
databaseId 可选项,若是配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载全部不带 databaseId 或匹配当前 databaseId 的语句;若是带和不带的语句都有,则不带的会被忽略。
resultOrdered 可选项,这个设置仅针对嵌套结果 select 语句:若是为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false。
数据变动语句 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
)。若是生成列不止一个,能够用逗号分隔多个属性名称。
咱们先看看 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 。
缓存对于互联网系统来讲特别常见,其特色就是将数据保存在内存中。MyBatis 内置了一个强大的事务性查询缓存机制,它能够很是方便地配置和定制。 为了使它更增强大并且易于配置,咱们对 MyBatis 3 中的缓存实现进行了许多改进。
默认状况下,只启用了本地的会话缓存(即一级缓存,sqlSession级别 ),它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,首先在全局配置文件config.xml文件中加入以下代码:
<!--开启二级缓存--> <settings> <setting name="cacheEnabled" value="true"/> </settings>
其次在UserMapper.xml文件中开启缓存:
<!--开启二级缓存--> <cache></cache>
基本上就是这样。这个简单语句的效果以下:
缓存只做用于 cache 标签所在的映射文件中的语句。若是你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你须要使用 @CacheNamespaceRef 注解指定缓存做用域。
这些属性能够经过 cache 元素的属性来修改。好比:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
上面表示了一套更高级的缓存配置,首先建立了一个 FIFO 缓存,每隔 60 秒刷新,最多能够存储结果对象或列表的 512 个引用,而后返回的对象被设置成只读的,所以对它们进行修改可能会在不一样线程中的调用者产生冲突。
缓存可用的清除策略有:
默认的清除策略是 LRU
flushInterval(刷新间隔)属性能够被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认状况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
size(引用数目)属性能够被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
readOnly(只读)属性能够被设置为 true 或 false。只读的缓存会给全部调用者返回缓存对象的相同实例。 所以这些对象不能被修改。这就提供了可观的性能提高。而可读写的缓存会(经过序列化)返回缓存对象的拷贝。 速度上会慢一些,可是更安全,所以默认值是 false。
二级缓存是事务性的。这意味着,当 SqlSession 完成并提交 ( commit ) 时,或是完成并回滚 ( close ) 时,二级缓存都会被刷新。无论是否配置了 flushCache=true 。
Mybatis 的缓存包括一级缓存(sqlSession 级别)和二级缓存(mapper 级别),因此 mapper 映射器中配置的是二级缓存,咱们先大概知道有这个概念,由于后续咱们会针对这两种缓存进行详细介绍,并且还会讲解如何自定义缓存,由于 Mybatis 的缓存默认都是以 map 的数据结构存储在本地,因此自定义缓存能够把存储介质拓展到磁盘或数据库redis等;并且一级缓存是默认开启的,二级缓存须要咱们手工开启,这些后续都会详细讲解,提早预告。
缓存获取顺序:二级缓存 > 一级缓存 > 数据库
回想一下 cache 的内容,对某一命名空间的语句,只会使用该命名空间的缓存进行缓存或刷新。 但你可能会想要在多个命名空间中共享相同的缓存配置和实例。要实现这种需求,你可使用 cache-ref 元素来引用另外一个缓存。
<cache-ref namespace="com.vo.UserMapper"/>
这个元素能够用来定义可重用的 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 元素官方已经不建议使用,而且再后续版本会退出舞台。首先对于咱们 Java 来讲,特别不但愿在代码中经过传递 map 来传参,这样对于后续维护或者参数查找都是极不负责任的,咱们推荐使用 JavaBean 来传值参数,这是 parameterMap 被抛弃的其中一个缘由;另外也因为 parameterType 属性的诞生就能很好的代替 parameterMap ,而且还能自定义 JavaBean 类型的传参,因此 parameterMap 退出舞台,实属正常。
我一直来都但愿本身只输出观点,而不是输出字典,但其中有些知识点又是极其冗杂,知识输出真是个难搞的差事,如何既能把知识脉络梳理的完整,又能讲得浅显易懂,言简意赅,确实是后续文章分解输出的研究方向。
本篇完,本系列下一篇咱们讲《 Mybatis系列全解(六):Mybatis最硬核的API你知道几个? 》。
BIU ~ 文章持续更新,微信搜索「潘潘和他的朋友们」第一时间阅读,随时有惊喜。本文会在 GitHub https://github.com/JavaWorld 收录,热腾腾的技术、框架、面经、解决方案,咱们都会以最美的姿式第一时间送达,欢迎 Star ~ 咱们将来 不止文章!想进读者群的伙伴欢迎撩我我的号:panshenlian,备注「加群」咱们群里欢聊吧 ~