自学Mybatis系列(3)——MyBatis的映射器


写在前面:十分感谢《深入浅出Mybatis技术原理与实战》这本书,大多数地方是书上的话,希望自己能在后面的文章中多写一些自己的理解。而且最重要的是!每次照书无脑敲的时候,都好羞愧啊(害羞脸)。后面一定注意这些问题。最后要感谢点赞、评论以及指正的朋友们,你们是坠吼的!!!


Mapper 映射器

映射器是个好东西,按照官方文档的话来说就是:

The true power of MyBatis is in the Mapped Statements. This is where the magic happens.

简单翻译就是:Mybatis真正的力量来源于它的映射语句,那里是见证奇迹的地方!
Mapper XML文件相对简单。 如果你将它们与等效的JDBC代码进行比较,你会节省95%的代码!MyBatis的目的是专注于SQL,并尽力去帮我们节省时间和精力去做其他的部分。
Mapper XML文件只有几个一级元素,介绍一下:

  • cache - 给定命名空间的缓存配置。
  • cache-ref - 从另一个命名空间引用缓存配置。
  • resultMap - 最复杂最也是最强大的元素,描述如何从数据库结果集中加载对象
  • parameterMap - 已过时!
  • sql - 可以被其他语句引用的SQL的可重用块。
  • insert - 映射INSERT语句。
  • update - 映射UPDATE语句。
  • delete - 映射DELETE语句。
  • select - 映射SELECT语句。

我们将逐个击破!

select

select语句是我们在Mybatis中使用最多的语句。我们使用查询语句的频率高于我们使用修改语句的频率,对于插入,更新或删除,来说,他们中可能会包含着很多的选择。 直接举例子吧:

<select id="selectPerson" parameterType="int" resultType="map">
         SELECT * FROM PERSON WHERE ID = #{id}
</select>

此语句接受类型为int(或Integer)的参数,并返回Map类型的数据。
注意参数符号:#{id}
让我们看一下如果使用JDBC的话代码是什么样子的:

// JDBC代码,不是Mybatis
      String sql = "SELECT * FROM PERSON WHERE ID=?";
      PreparedStatement ps = conn.prepareStatement(sql);
      ps.setInt(1,id);

可以想象,如果字段很多的话,传统方式下我们将会在书写很多重复的代码,这样不仅很枯燥,还很容易出错。例如:

<select
  id="selectPerson"
  parameterType="int"
  parameterMap="deprecated"
  resultType="hashmap"
  resultMap="personResultMap"
  flushCache="false"
  useCache="true"
  timeout="10000"
  fetchSize="256"
  statementType="PREPARED"
  resultSetType="FORWARD_ONLY">

逐个进行解释:

  • id:它和Mapper命名空间组合起来是唯一的。
  • parameterType:可以给出类的全名吗,也可以给类的别名,但是必须是内部定义或者自定义的。
  • parameterMap:即将废弃。
  • resultType:返回值的类型,也可以使用别名,不能和resultMap同时使用!
  • resultMap:可以执行强大的映射功能,解决很多的映射困难,不能与resultType同时使用。resultMap给了我们自己定义映射规则的机会。
  • flushCache:如果设置true将会清空本地缓存和二级缓存。默认为false。
  • useCache:默认为true,是启动二级缓存的开关。
  • timeout:设置超时参数,等超时抛出异常,单位是秒。默认值由厂商提供。
  • fetchSize:设置获取记录的总条数。
  • statementType:设置使用哪个JDBC的Statement工作。可以取值:STATEMENT、PREPARED。默认值为PREPARED。
    -resultSetType:取值为FORWARD_ONLY 、SCROLL_SENSITIVE 、SCROLL_INSENSITIVE。默认值为unset(取决于驱动程序)。

给映射器传递多个参数有三种方式:
1.使用Map传参。
2.使用注解方式传参。
3.使用JavaBean传参。
首先,使用Map传参会导致业务可读性丧失,很难继续扩展和维护,这里很不推荐这种方式。
其次,使用@Param注解传递多个参数,这种方式的使用受到参数个数的影响,如果参数个数很多的话,那要写n多个@Param吗?所以一般来说,如果参数个数在五个以下的话,使用这种方式最好。
最后,如果参数多于五个,我们使用JavaBean。

insert、update、delete

先粘个代码,展示一下元素,官方文档的代码,感受一下:

<insert
  id="insertAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  keyProperty=""
  keyColumn=""
  useGeneratedKeys=""
  timeout="20">
<update
  id="updateAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">
<delete
  id="deleteAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">

具体都是干啥的,这里真的懒得写了,虽然元素很多,但是能用上的没几个。懂SQL的童鞋看了示例马上就能学会:

<insert id="insertAuthor">
  insert into Author (id,username,password,email,bio)
  values (#{id},#{username},#{password},#{email},#{bio})
</insert>
<update id="updateAuthor">
  update Author set
    username = #{username},
    password = #{password},
    email = #{email},
    bio = #{bio}
  where id = #{id}
</update>
<delete id="deleteAuthor">
  delete from Author where id = #{id}
</delete>

没错,官方的文档就是这么粗暴。我们主要讨论需要去重点处理的问题——主键回填和自定义
首先,我们可以使用keyProperty属性指定那个是主键字段,同时使用userGeneratedKeys属性设置这个主键是否使用数据库的内置策略生成。
举个栗子:

<insert id="insertAuthor" useGeneratedKeys="true" keyProperty="id">
      insert into Author (username,password,email,bio)
      values (#{username},#{password},#{email},#{bio})
</insert>

这里就是指定了id为自增字段。我们建立POJO,提供getter和setter方法,就可以使用主键回填了。
但是,有时候我们需要根据一些特殊的关系设置主键id的值,比如我们要求:如果表t_role没有记录,我们设置id=1,否则我们取最大id加2来设置新的主键,这时候我们就需要这么写:

<insert id="insertRole" parameterType="role" userGeneratedKeys="true" keyProperty="id">
      <selectKey keyProperty="id" resultType="int" order="BEFORE">
        select if(max(id) is null,1,max(id)+2) as newId from t_role
      </selectKey>
      insert into t_role
        (id, role_name,note)
      values
        (#{id}, #{rolename}, #{note})
    </insert>

sql

此元素可用于定义可以包含在其他语句中的SQL代码的可重用片段。 它可以是静态的(在加载阶段)参数化。 不同的属性值在include实例中可能不同。 例如:

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

上面我们用sql元素定义了userColumns,他可以很方便的使用include元素的refid属性引用,达到重用的功能。请看:

  <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 t1
         cross join t2
    </select>

我们也可以给refid参数值

<sql id="someinclude">
  select * from <include refid="${tableName}" />
</sql>

这样实现了定义一处,很多地方也可以引用。


参数

对于储存过程来说,有三种参数:输入参数、输出参数、输入输出参数。大部分情况下Mybatis回去推断返回数据的类型,所以大部分情况下是不要去配置参数类型和结果类型的。但返回为空的字段类型是需要去设置的,因为Mybatis无法判断null是什么类型!
关于参数我想说一下特殊字符替换和处理的问题(#{}和${})
这里我要说两点:
1.#{}方式能够很大程度防止sql注入。
2.排序时使用order by 动态参数时需要注意,用$而不是#
3.尽量都使用#{}

resultMap

resultMap元素是MyBatis中最重要和最强大的元素。 就是有了这个东西才允许我们节省90%的代码。JDBC需要从ResultSets检索数据,也就是说,因为resultMap在某些情况下Mybatis允许你做的事情,JDBC并不支持。
resultMap定义的主要是一个结果集的映射关系。MyBatis现有的版本只支持查询。resultMap很复杂,在这只能简单列一下它的构成,具体的得我深入了解以后,再单独开一个番外去研究。
resultMap里的元素:

<resultMap>
    <constructor> <!-- 配置构造方法 -->
        <idArg/>
        <arg/>
    </constructor>
    <id/>  <!-- 定义主键 -->
    <result/>   <!-- 定义普通列的映射关系 -->
    <association/>    <!-- 一对一级联 -->
    <collection/>     <!-- 一对多级联 -->
    <discriminator>  <!-- 鉴别器级联 -->
        <case/>  <!-- 类似switch/case -->
    </discriminator>
</resultMap>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容