【MyBatis-3】MyBatis xml映射文件详解

旧城等待, 2024-04-17 19:50 149阅读 0赞

MyBatis之xml 映射文件详解

  • 1 Sql标签
    • 1.1 select
    • 1.2 insert, update 和 delete
    • 1.3 sql
  • 2 参数
    • 2.1 字符串替换
  • 3 结果映射

MyBatis 的真正强大在于它的映射语句,SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):

  • cache – 对给定命名空间的缓存配置。
  • cache-ref – 对其他命名空间缓存配置的引用。
  • resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
  • parameterMap – 已被废弃!老式风格的参数映射。更好的办法是使用内联参数,此元素可能在将来被移除。文档中不会介绍此元素。
  • sql – 可被其他语句引用的可重用语句块。
  • insert – 映射插入语句
  • update – 映射更新语句
  • delete – 映射删除语句
  • select – 映射查询语句

1 Sql标签

1.1 select

查询语句是 MyBatis 中最常用的元素之一,光能把数据存到数据库中价值并不大,只有还能重新取出来才有用,多数应用也都是查询比修改要频繁。对每个插入、更新或删除操作,通常间隔多个查询操作。这是 MyBatis 的基本原则之一,也是将焦点和努力放在查询和结果映射的原因。简单查询的 select 元素是非常简单的。比如:

  1. <select id="selectPerson" parameterType="int" resultType="hashmap">
  2. SELECT * FROM PERSON WHERE ID = #{
  3. id}
  4. </select>

这个语句被称作 selectPerson,接受一个 int(或 Integer)类型的参数,并返回一个 HashMap 类型的对象,其中的键是列名,值便是结果行中的对应值。
注意参数符号:

  1. #{
  2. id}

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

  1. // 近似的 JDBC 代码,非 MyBatis 代码...
  2. String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
  3. PreparedStatement ps = conn.prepareStatement(selectPerson);
  4. ps.setInt(1,id);

当然,使用 JDBC 意味着需要更多的代码来提取结果并将它们映射到对象实例中,而这就是 MyBatis 节省你时间的地方。参数和结果映射还有更深入的细节。这些细节会分别在后面单独的小节中呈现。

select 元素允许你配置很多属性来配置每条语句的作用细节。

  1. <select
  2. id="selectPerson"
  3. parameterType="int"
  4. parameterMap="deprecated"
  5. resultType="hashmap"
  6. resultMap="personResultMap"
  7. flushCache="false"
  8. useCache="true"
  9. timeout="10"
  10. fetchSize="256"
  11. statementType="PREPARED"
  12. resultSetType="FORWARD_ONLY">









































































属性 描述
id 在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType 将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler) 推断出具体传入语句的参数,默认值为未设置(unset)
parameterMap 这是引用外部 parameterMap 的已经被废弃的方法。请使用内联参数映射和 parameterType 属性。
resultType 从这条语句中返回的期望类型的类的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。可以使用 resultType 或 resultMap,但不能同时使用。
resultMap 外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂映射的情形都能迎刃而解。可以使用 resultMap 或 resultType,但不能同时使用。
flushCache 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。
useCache 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
timeout 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
useCache 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。
fetchSize 这是一个给驱动的提示,尝试让驱动程序每次批量返回的结果行数和这个设置值相等。 默认值为未设置(unset)(依赖驱动)。
useCache 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
statementType STATEMENT,PREPARED 或 CALLABLE 中的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
resultSetType FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖驱动)。
databaseId 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。
resultOrdered 这个设置仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。 这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false。
resultSets 这个设置仅对多结果集的情况适用。它将列出语句执行后返回的结果集并给每个结果集一个名称,名称是逗号分隔的。

1.2 insert, update 和 delete

数据变更语句 insert,update 和 delete 的实现非常接近:

  1. <insert
  2. id="insertAuthor"
  3. parameterType="domain.blog.Author"
  4. flushCache="true"
  5. statementType="PREPARED"
  6. keyProperty=""
  7. keyColumn=""
  8. useGeneratedKeys=""
  9. timeout="20">
  10. <update
  11. id="updateAuthor"
  12. parameterType="domain.blog.Author"
  13. flushCache="true"
  14. statementType="PREPARED"
  15. timeout="20">
  16. <delete
  17. id="deleteAuthor"
  18. parameterType="domain.blog.Author"
  19. flushCache="true"
  20. statementType="PREPARED"
  21. timeout="20">

















































属性 描述
id 命名空间中的唯一标识符,可被用来代表这条语句。
parameterType 将要传入语句的参数的完全限定类名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器推断出具体传入语句的参数,默认值为未设置(unset)。
parameterMap 这是引用外部 parameterMap 的已经被废弃的方法。请使用内联参数映射和 parameterType 属性。
flushCache 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:true(对于 insert、update 和 delete 语句)。
timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。
statementType STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
useGeneratedKeys (仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。
keyProperty (仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认值:未设置(unset)。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
keyColumn (仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望使用多个生成的列,也可以设置为逗号分隔的属性名称列表。
databaseId 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。

下面就是 insert,update 和 delete 语句的示例:

  1. <insert id="insertAuthor">
  2. insert into Author (id,username,password,email,bio)
  3. values (#{
  4. id},#{
  5. username},#{
  6. password},#{
  7. email},#{
  8. bio})
  9. </insert>
  10. <update id="updateAuthor">
  11. update Author set
  12. username = #{
  13. username},
  14. password = #{
  15. password},
  16. email = #{
  17. email},
  18. bio = #{
  19. bio}
  20. where id = #{
  21. id}
  22. </update>
  23. <delete id="deleteAuthor">
  24. delete from Author where id = #{
  25. id}
  26. </delete>

如前所述,插入语句的配置规则更加丰富,在插入语句里面有一些额外的属性和子元素用来处理主键的生成,而且有多种生成方式。
首先,如果你的数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),那么你可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置到目标属性上就 OK 了。例如,如果上面的 Author 表已经对 id 使用了自动生成的列类型,那么语句可以修改为:

  1. <insert id="insertAuthor" useGeneratedKeys="true"
  2. keyProperty="id">
  3. insert into Author (username,password,email,bio)
  4. values (#{
  5. username},#{
  6. password},#{
  7. email},#{
  8. bio})
  9. </insert>

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

  1. <insert id="insertAuthor" useGeneratedKeys="true"
  2. keyProperty="id">
  3. insert into Author (username, password, email, bio) values
  4. <foreach item="item" collection="list" separator=",">
  5. (#{
  6. item.username}, #{
  7. item.password}, #{
  8. item.email}, #{
  9. item.bio})
  10. </foreach>
  11. </insert>

每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子:

  1. @Alias("author")
  2. public class Author {
  3. ...
  4. }

对于不支持自动生成类型的数据库或可能不支持自动生成主键的 JDBC 驱动,MyBatis 有另外一种方法来生成主键。
这里有一个简单(但很不规范)的示例,它可以生成一个随机 ID(你最好不要这么做,但这里展示了 MyBatis 处理问题的灵活性及其所关心的广度):

  1. <insert id="insertAuthor">
  2. <selectKey keyProperty="id" resultType="int" order="BEFORE">
  3. select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
  4. </selectKey>
  5. insert into Author
  6. (id, username, password, email,bio, favourite_section)
  7. values
  8. (#{
  9. id}, #{
  10. username}, #{
  11. password}, #{
  12. email}, #{
  13. bio}, #{
  14. favouriteSection,jdbcType=VARCHAR})
  15. </insert>

在上面的示例中,selectKey 元素中的语句将会首先运行,Author 的 id 会被设置,然后插入语句会被调用。这可以提供给你一个与数据库中自动生成主键类似的行为,同时保持了 Java 代码的简洁。
selectKey 元素描述如下:

  1. <selectKey
  2. keyProperty="id"
  3. resultType="int"
  4. order="BEFORE"
  5. statementType="PREPARED">

selectKey 元素的属性:






























属性 描述
keyProperty selectKey 语句结果应该被设置的目标属性。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
keyColumn 匹配属性的返回结果集中的列名称。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
resultType 结果的类型。MyBatis 通常可以推断出来,但是为了更加精确,写上也不会有什么问题。MyBatis 允许将任何简单类型用作主键的类型,包括字符串。如果希望作用于多个生成的列,则可以使用一个包含期望属性的 Object 或一个 Map。
order 这可以被设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它会首先生成主键,设置 keyProperty 然后执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 中的语句 - 这和 Oracle 数据库的行为相似,在插入语句内部可能有嵌入索引调用。
statementType 与前面相同,MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 语句的映射类型,分别代表 PreparedStatement 和 CallableStatement 类型。

1.3 sql

这个元素可以被用来定义可重用的 SQL 代码段,这些 SQL 代码可以被包含在其他语句中。它可以(在加载的时候)被静态地设置参数。 在不同的包含语句中可以设置不同的值到参数占位符上。比如:

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

这个 SQL 片段可以被包含在其他语句中,例如:

  1. <select id="selectUsers" resultType="map">
  2. select
  3. <include refid="userColumns"><property name="alias" value="t1"/></include>,
  4. <include refid="userColumns"><property name="alias" value="t2"/></include>
  5. from some_table t1
  6. cross join some_table t2
  7. </select>

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

  1. <sql id="sometable">
  2. ${
  3. prefix}Table
  4. </sql>
  5. <sql id="someinclude">
  6. from
  7. <include refid="${include_target}"/>
  8. </sql>
  9. <select id="select" resultType="map">
  10. select
  11. field1, field2, field3
  12. <include refid="someinclude">
  13. <property name="prefix" value="Some"/>
  14. <property name="include_target" value="sometable"/>
  15. </include>
  16. </select>

2 参数

之前见到的所有语句中,使用的都是简单参数。实际上参数是 MyBatis 非常强大的元素。对于简单的使用场景,大约 90% 的情况下都不需要使用复杂的参数,比如:

  1. <select id="selectUsers" resultType="User">
  2. select id, username, password
  3. from users
  4. where id = #{
  5. id}
  6. </select>

上面的这个示例说明了一个非常简单的命名参数映射。参数类型被设置为 int,这样这个参数就可以被设置成任何内容。原始类型或简单数据类型(比如 Integer 和 String)因为没有相关属性,它会完全用参数值来替代。 然而,如果传入一个复杂的对象,行为就会有一点不同了。比如:

  1. <insert id="insertUser" parameterType="User">
  2. insert into users (id, username, password)
  3. values (#{
  4. id}, #{
  5. username}, #{
  6. password})
  7. </insert>

如果 User 类型的参数对象传递到了语句中,id、username 和 password 属性将会被查找,然后将它们的值传入预处理语句的参数中。
对向语句中传递参数来说,这真是既简单又有效。不过参数映射的功能远不止于此。
首先,像 MyBatis 的其他部分一样,参数也可以指定一个特殊的数据类型。

  1. #{
  2. property,javaType=int,jdbcType=NUMERIC}

像 MyBatis 的其它部分一样,javaType 几乎总是可以根据参数对象的类型确定下来,除非该对象是一个 HashMap。这个时候,你需要显式指定 javaType 来确保正确的类型处理器(TypeHandler)被使用。

JDBC 要求,如果一个列允许 null 值,并且会传递值 null 的参数,就必须要指定 JDBC Type。阅读 PreparedStatement.setNull()的 JavaDoc 文档来获取更多信息。

要更进一步地自定义类型处理方式,你也可以指定一个特殊的类型处理器类(或别名),比如:

  1. #{
  2. age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}

尽管看起来配置变得越来越繁琐,但实际上,很少需要如此繁琐的配置。
对于数值类型,还有一个小数保留位数的设置,来指定小数点后保留的位数。

  1. #{
  2. height,javaType=double,jdbcType=NUMERIC,numericScale=2}

最后,mode 属性允许你指定 IN,OUT 或 INOUT 参数。如果参数的 mode 为 OUT 或 INOUT,就像你在指定输出参数时所期望的行为那样,参数对象的属性实际值将会被改变。 如果 mode 为 OUT(或 INOUT),而且 jdbcType 为 CURSOR(也就是 Oracle 的 REFCURSOR),你必须指定一个 resultMap 引用来将结果集 ResultMap 映射到参数的类型上。要注意这里的 javaType 属性是可选的,如果留空并且 jdbcType 是 CURSOR,它会被自动地被设为 ResultMap。

  1. #{
  2. department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}

MyBatis 也支持很多高级的数据类型,比如结构体(structs),但是当使用 out 参数时,你必须显式设置类型的名称。比如(再次提示,在实际中要像这样不能换行):

  1. #{
  2. middleInitial, mode=OUT, jdbcType=STRUCT, jdbcTypeName=MY_TYPE, resultMap=departmentResultMap}

尽管所有这些选项很强大,但大多时候你只须简单地指定属性名,其他的事情 MyBatis 会自己去推断,顶多要为可能为空的列指定 jdbcType。

  1. #{
  2. firstName}
  3. #{
  4. middleInitial,jdbcType=VARCHAR}
  5. #{
  6. lastName}

2.1 字符串替换

默认情况下,使用 #{} 格式的语法会导致 MyBatis 创建 PreparedStatement 参数占位符并安全地设置参数(就像使用 ? 一样)。 这样做更安全,更迅速,通常也是首选做法,不过有时你就是想直接在 SQL 语句中插入一个不转义的字符串。 比如,像 ORDER BY,你可以这样来使用:

  1. ORDER BY ${
  2. columnName}

这里 MyBatis 不会修改或转义字符串。
当 SQL 语句中的元数据(如表名或列名)是动态生成的时候,字符串替换将会非常有用。 举个例子,如果你想通过任何一列从表中 select 数据时,不需要像下面这样写:

  1. @Select("select * from user where id = #{id}")
  2. User findById(@Param("id") long id);
  3. @Select("select * from user where name = #{name}")
  4. User findByName(@Param("name") String name);
  5. @Select("select * from user where email = #{email}")
  6. User findByEmail(@Param("email") String email);
  7. // and more "findByXxx" method

可以只写这样一个方法:

  1. @Select("select * from user where ${column} = #{value}")
  2. User findByColumn(@Param("column") String column, @Param("value") String value);

其中 ${column} 会被直接替换,而 #{value} 会被使用 ? 预处理。 因此你就可以像下面这样来达到上述功能:

  1. User userOfId1 = userMapper.findByColumn("id", 1L);
  2. User userOfNameKid = userMapper.findByColumn("name", "kid");
  3. User userOfEmail = userMapper.findByColumn("email", "noone@nowhere.com");

这个想法也同样适用于用来替换表名的情况。

提示:用这种方式接受用户的输入,并将其用于语句中的参数是不安全的,会导致潜在的 SQL注入攻击,因此要么不允许用户输入这些字段,要么自行转义并检验。

3 结果映射

resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的长达数千行的代码。ResultMap 的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系就行了。
你已经见过简单映射语句的示例了,但并没有显式指定 resultMap。比如:

  1. <select id="selectUsers" resultType="map">
  2. select id, username, hashedPassword
  3. from some_table
  4. where id = #{id}
  5. </select>

上述语句只是简单地将所有的列映射到 HashMap 的键上,这由 resultType 属性指定。虽然在大部分情况下都够用,但是 HashMap 不是一个很好的领域模型。你的程序更可能会使用 JavaBean 或 POJO(Plain Old Java Objects,普通老式 Java 对象)作为领域模型。MyBatis 对两者都提供了支持。看看下面这个 JavaBean:

  1. package com.someapp.model;
  2. public class User {
  3. private int id;
  4. private String username;
  5. private String hashedPassword;
  6. public int getId() {
  7. return id;
  8. }
  9. public void setId(int id) {
  10. this.id = id;
  11. }
  12. public String getUsername() {
  13. return username;
  14. }
  15. public void setUsername(String username) {
  16. this.username = username;
  17. }
  18. public String getHashedPassword() {
  19. return hashedPassword;
  20. }
  21. public void setHashedPassword(String hashedPassword) {
  22. this.hashedPassword = hashedPassword;
  23. }
  24. }

基于 JavaBean 的规范,上面这个类有 3 个属性:id,username 和 hashedPassword。这些属性会对应到 select 语句中的列名。
这样的一个 JavaBean 可以被映射到 ResultSet,就像映射到 HashMap 一样简单。

  1. <select id="selectUsers" resultType="com.someapp.model.User">
  2. select id, username, hashedPassword
  3. from some_table
  4. where id = #{
  5. id}
  6. </select>

类型别名是你的好帮手。使用它们,你就可以不用输入类的完全限定名称了。比如:

  1. <!-- mybatis-config.xml 中 -->
  2. <typeAlias type="com.someapp.model.User" alias="User"/>
  3. <!-- SQL 映射 XML 中 -->
  4. <select id="selectUsers" resultType="User">
  5. select id, username, hashedPassword
  6. from some_table
  7. where id = #{
  8. id}
  9. </select>

这些情况下,MyBatis 会在幕后自动创建一个 ResultMap,再基于属性名来映射列到 JavaBean 的属性上。如果列名和属性名没有精确匹配,可以在 SELECT 语句中对列使用别名(这是一个基本的 SQL 特性)来匹配标签。比如:

  1. <select id="selectUsers" resultType="User">
  2. select
  3. user_id as "id",
  4. user_name as "userName",
  5. hashed_password as "hashedPassword"
  6. from some_table
  7. where id = #{
  8. id}
  9. </select>

ResultMap 最优秀的地方在于,虽然你已经对它相当了解了,但是根本就不需要显式地用到他们。 上面这些简单的示例根本不需要下面这些繁琐的配置。 但出于示范的原因,让我们来看看最后一个示例中,如果使用外部的 resultMap 会怎样,这也是解决列名不匹配的另外一种方式。

  1. <resultMap id="userResultMap" type="User">
  2. <id property="id" column="user_id" />
  3. <result property="username" column="user_name"/>
  4. <result property="password" column="hashed_password"/>
  5. </resultMap>

而在引用它的语句中使用 resultMap 属性就行了(注意我们去掉了 resultType 属性)。比如:

  1. <select id="selectUsers" resultMap="userResultMap">
  2. select user_id, user_name, hashed_password
  3. from some_table
  4. where id = #{
  5. id}
  6. </select>

发表评论

表情:
评论列表 (有 0 条评论,149人围观)

还没有评论,来说两句吧...

相关阅读

    相关 MybatisXML映射文件

    MyBatis 针对 SQL 构建,真正强大在于它的映射语句。 SQL 映射文件有很少的几个顶级元素(按照它们应该被定义的顺序): cache – 给定命名空间的缓存配