MyBatis高级查询

高级查询

  • MyBatis 的高级复杂查询操作

    • 多表联查、嵌套查询

    • 结果集映射

字段介绍

结果映射
  • constructor: 用于在实例化类时,注入结果到构造方法中
    • idArg:ID 参数
      • 标记出作为 ID 的结果可以帮助提高整体性能
    • arg:普通参数
      • 将被注入到构造方法的一个普通结果
  • id:主键字段
    • 标记出作为 ID 的结果可以帮助提高整体性能
  • result:普通字段
    • 注入到字段或 JavaBean 属性的普通结果
  • association:一个复杂类型的关联
    • 许多结果包装成这种类型
    • 嵌套结果映射:关联可以是 resultMap 元素或对其它结果映射的引用
  • collection:复杂类型的集合
    • 嵌套结果映射:集合可以是 resultMap 元素,或是对其它结果映射的引用
  • discriminator:使用结果值决定使用哪个 resultMap
    • case:基于某些值的结果映射
    • 嵌套结果映射:case 也是一个结果映射,因此具有相同的结构和元素
      • 或者引用其它的结果映射
属性列表
ResultMap
属性描述
id当前命名空间中的一个唯一标识,用于标识一个结果映射。
type类的完全限定名, 或类型别名
autoMapping自动结果映射。 设置后会覆盖全局的属性 autoMappingBehavior。默认值:未设置(unset)
id 和 result
属性描述
property映射到列结果的字段或属性
当 JavaBean 存在同名属性(property)时先使用该属性,否则 MyBatis 寻找指定名称的字段(field)
可使用点式分隔形式进行复杂属性导航,例:“username” 或 “address.street.number”
column数据库中的列名或列别名
一般和传递给 resultSet.getString(columnName) 方法的参数一样
javaTypeJava 类的全限定名或类型别名
如果映射到 JavaBean,MyBatis 通常可以推断类型,通常无需声明
如果映射到 HashMap,应明确指定 javaType 保证行为与期望的相一致
jdbcTypeJDBC 类型,只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型
JDBC 的要求而非 MyBatis 的要求
直接面向 JDBC 编程,需要对可以为空值的列指定类型
typeHandler默认的类型处理器,可以覆盖默认的类型处理器
属性值是类型处理器实现类的全限定名,或类型别名
JDBC 类型
  • 支持的 JDBC 类型
  • MyBatis 通过内置的 jdbcType 枚举类型支持下面的 JDBC 类型
SQL内数据类型
BITFLOATCHARTIMESTAMPOTHERUNDEFINED
TINYINTREALVARCHARBINARYBLOBNVARCHAR
SMALLINTDOUBLELONGVARCHARVARBINARYCLOBNCHAR
INTEGERNUMERICDATELONGVARBINARYBOOLEANNCLOB
BIGINTDECIMALTIMENULLCURSORARRAY

constructor

  • MyBatis 支持私有属性和私有 JavaBean 属性来完成注入
  • 或通过构造方法进行注入
    • constructor 元素就是为此而生的
示例
<!-- 
搜索一个声明了三个形参的构造方法
参数类型以 java.lang.Integer, java.lang.String 和 int 的顺序给出
-->
<constructor>
   <idArg column="id" javaType="int"/>
   <arg column="username" javaType="String"/>
   <arg column="age" javaType="_int"/>
</constructor>
<!--
从版本 3.4.3 开始,可在指定参数名称后以任意顺序编写 arg 元素
通过名称来引用构造方法参数,可以添加 @Param 注解,
或使用 '-parameters' 编译并启用 useActualParamName 选项(默认开启)来编译项目
-->
<constructor>
   <idArg column="id" javaType="int" name="id" />
   <arg column="age" javaType="_int" name="age" />
   <arg column="username" javaType="String" name="username" />
</constructor>
属性列表
  • 名称和类型相同的属性可以省略 javaType
  • 剩余的属性和规则和普通的 id 和 result 元素一样
属性描述
column数据库列名或列别名
javaTypeJava 类的完全限定名或类型别名
如果映射到 JavaBean,MyBatis 通常可以推断类型
如果映射到 HashMap,应明确指定 javaType 保证行为与期望的相一致
jdbcTypeJDBC 类型
typeHandler覆盖默认的类型处理器;属性值是类型处理器实现类的完全限定名,或类型别名
select加载复杂类型属性的映射语句的 ID
会从 column 属性中指定的列检索数据,作为参数传递给此 select 语句
resultMap结果映射的 ID,可将嵌套的结果集映射到一个合适的对象树中
可以作为使用额外 select 语句的替代方案
可以将多表连接操作的结果映射成一个单一的 ResultSetResultSet 将会将包含重复或部分数据重复的结果集
为了将结果集正确地映射到嵌套的对象树中,MyBatis 允许“串联”结果映射,以便解决嵌套结果集的问题
name构造方法形参的名字;从 3.4.3 版本开始,指定具体的参数名后可以以任意顺序写入 arg 元素

association

关联
  • 关联(association)元素处理 “有一个” 类型的关系

    • 比如:一个博客有一个用户
    • 即表的字段同时是其他表的字段
      • 对应实体类的属性为其他类对象
  • 关联结果映射和其它类型的映射工作方式差不多

    • 需要指定目标属性名以及属性的 javaType
      • 很多时候 MyBatis 可以推断,必要的情况下可以设置 JDBC 类型
    • 想要覆盖获取结果值的过程可以设置类型处理器
  • 关联的不同之处

    • 需要告诉 MyBatis 如何加载关联
    • MyBatis 有两种不同的方式加载关联
      1. 嵌套 Select 查询
        • 通过执行另外一个 SQL 映射语句来加载期望的复杂类型
      2. 嵌套结果映射
        • 使用嵌套的结果映射来处理连接结果的重复子集
  • 和普通的结果映射相比,只在 selectresultMap 属性上有所不同

属性描述
property映射到列结果的字段或属性
javaType一个 Java 类的完全限定名,或一个类型别名
jdbcTypeJDBC 类型
typeHandler覆盖默认的类型处理器
嵌套 Select 查询
属性
属性描述
column数据库中列名或列别名
使用复合主键时可使用 column="{prop1=col1,prop2=col2}" 语法指定多个传递给嵌套 Select 查询语句的列名;使得 prop1prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数
select加载复杂类型属性的映射语句的 ID,从 column 属性指定的列中检索数据,作为参数传递给目标 select 语句
使用复合主键时可使用 column="{prop1=col1,prop2=col2}" 语法指定多个传递给嵌套 Select 查询语句的列名;使得 prop1prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数
fetchType可选;有效值为 lazyeager
指定后在映射中忽略全局配置参数 lazyLoadingEnabled,使用属性的值
示例
  • 两个 select 查询语句

    • 一个用来加载博客(Blog),另外一个用来加载作者(Author)
  • 博客的结果映射描述了应使用 selectAuthor 语句加载 author 属性

    • 其它所有的属性将会被自动加载,只要它们的列名和属性名相匹配
<!-- 将 selectAuthor 的查询结果嵌套进 selectBlog -->
<resultMap id="blogResult" type="Blog">
	<association property="author" column="author_id" 
                 javaType="Author" select="selectAuthor"/>
</resultMap>
<!-- 查询一,属性中存在引用类型 Author 需要连表查询赋值 -->
<select id="selectBlog" resultMap="blogResult">
  SELECT * FROM BLOG WHERE ID = #{id}
</select>
<!-- 查询二,查询 Author 结果 -->
<select id="selectAuthor" resultType="Author">
  SELECT * FROM AUTHOR WHERE ID = #{id}
</select>
  • 这种方式虽然很简单,但在大型数据集或大型数据表上表现不佳
    • 被称为 N + 1 查询问题
  • N+1 查询问题会导致成百上千的 SQL 语句被执行
    • 执行单独的 SQL 语句来获取结果的一个列表:+1
    • 对列表返回的每条记录执行 select 查询语句来为每条记录加载详细信息: N
嵌套结果映射
属性
  • id 元素在嵌套结果映射中非常重要
    • 应总是指定一个或多个可以唯一标识结果的属性
      • 不指定属性 MyBatis 仍可以工作,但会产生严重的性能问题
  • 只需要指定可以唯一标识结果的最少属性
    • 可以选择主键,复合主键也可以
属性描述
resultMap结果映射的 ID,可将此关联的嵌套结果集映射到一个合适的对象树
可以作为使用额外 select 语句的替代方案:将多表连接操作的结果映射成一个单一的 ResultSetResultSet 有部分数据是重复的
为将结果集正确地映射到嵌套的对象树中, MyBatis 允许“串联”结果映射,以便解决嵌套结果集的问题
columnPrefix连接多个表时可能需要使用列别名避免在 ResultSet 中产生重复的列名
指定 columnPrefix 列名前缀允许将带有这些前缀的列映射到一个外部的结果映射中
notNullColumn默认情况下,在至少一个被映射到属性的列不为空时子对象才会被创建
在此属性上指定非空的列来改变默认行为,指定后 Mybatis 将只在这些列非空时才创建一个子对象
使用逗号分隔来指定多个列。默认值:未设置(unset)
autoMapping自动结果映射,会覆盖全局的属性 autoMappingBehavior
注意,本属性对外部的结果映射无效,所以不能搭配 selectresultMap 元素使用。默认值:未设置(unset)
示例 1
流程
  • 将博客表和作者表连接在一起,而不是执行一个独立的查询语句

    • 建立两个结果集:Blog、Author

      • 将 Author 嵌套进 Blog 以得到引用类型属性 author 的结果
        • 外部的结果映射元素来映射关联
      • Author 的结果映射可以被重用
    • 或直接将 Author 结果集写入 Blog 结果集中

      • 将所有的结果映射放在一个具有描述性的结果映射元素中
        • 可直接将结果映射作为子元素嵌套在内
      • 无法复用 Author 结果集嵌套进其它查询
    • 复用结果集映射

      • 复用时一般字段名与原结果映射中字段名不一致
      • 指定 columnPrefix 以便重复使用该结果映射
查询 sql
<!-- 一个博客一个作者查询博客 -->
<!-- sql 查询语句,设置查询结果字段别名以得到清晰、唯一的映射字段名 -->
<select id="selectBlog" resultMap="blogResult">
  select
    B.id            as blog_id,
    B.title         as blog_title,
    B.author_id     as blog_author_id,
    A.id            as author_id,
    A.username      as author_username,
    A.password      as author_password,
    A.email         as author_email,
    A.bio           as author_bio
  from Blog B left outer join Author A on B.author_id = A.id
  where B.id = #{id}
</select>
结果集映射
<!-- 博客(Blog)作者(author)的关联元素委托名为 “authorResult” 的结果映射来加载作者对象的实例 -->
<resultMap id="blogResult" type="Blog">
    <id property="id" column="blog_id" />
    <result property="title" column="blog_title"/>
    <association property="author" column="blog_author_id" 
                 javaType="Author" resultMap="authorResult"/>
</resultMap>

<resultMap id="authorResult" type="Author">
    <id property="id" column="author_id"/>
    <result property="username" column="author_username"/>
    <result property="password" column="author_password"/>
    <result property="email" column="author_email"/>
    <result property="bio" column="author_bio"/>
</resultMap>

<!-- 直接映射,等价上面两个结果集 -->
<resultMap id="blogResult" type="Blog">
    <id property="id" column="blog_id" />
    <result property="title" column="blog_title"/>
    <association property="author" javaType="Author">
        <id property="id" column="author_id"/>
        <result property="username" column="author_username"/>
        <result property="password" column="author_password"/>
        <result property="email" column="author_email"/>
        <result property="bio" column="author_bio"/>
    </association>
</resultMap>
示例 2
  • 博客(blog)有一个共同作者(co-author)
查询 sql
<!-- 两次外联 author 表以找到两个作者 -->
<select id="selectBlog" resultMap="blogResult">
    select
    B.id            as blog_id,
    B.title         as blog_title,
    A.id            as author_id,
    A.username      as author_username,
    A.password      as author_password,
    A.email         as author_email,
    A.bio           as author_bio,
    CA.id           as co_author_id,
    CA.username     as co_author_username,
    CA.password     as co_author_password,
    CA.email        as co_author_email,
    CA.bio          as co_author_bio
    from Blog B
    left outer join Author A on B.author_id = A.id
    left outer join Author CA on B.co_author_id = CA.id
    where B.id = #{id}
</select>
结果集映射
  • 结果中列名与 authorResult 结果集映射中的列名不同
    • 需指定 columnPrefix 以便重复使用该结果映射来映射 co-author 的结果
<resultMap id="blogResult" type="Blog">
    <id property="id" column="blog_id" />
    <result property="title" column="blog_title"/>
    <association property="author" resultMap="authorResult" />
    <!-- 复用 authorResult 结果集映射 -->
    <association property="coAuthor" resultMap="authorResult" 
                 columnPrefix="co_" />
</resultMap>

<resultMap id="authorResult" type="Author">
    <id property="id" column="author_id"/>
    <result property="username" column="author_username"/>
    <result property="password" column="author_password"/>
    <result property="email" column="author_email"/>
    <result property="bio" column="author_bio"/>
</resultMap>
多结果集
ResultSet
  • 关联的多结果集

    属性描述
    column当使用多个结果集时指定结果集中用于与 foreignColumn 匹配的列|
    多个列名以逗号隔开,以识别关系中的父类型与子类型
    foreignColumn指定外键对应的列名,指定的列将与父类型中 column 的给出的列进行匹配
    resultSet指定用于加载复杂类型的结果集名字
  • 从版本 3.2.3 开始,MyBatis 提供了另一种解决 N+1 查询问题的方法

    • 某些数据库允许存储过程返回多个结果集
      • 或一次性执行多个语句,每个语句返回一个结果集
    • 利用此特性在不使用连接的情况下只访问数据库一次就能获得相关数据
示例
  • 存储过程执行下面的查询并返回两个结果集

    • 第一个结果集返回博客(Blog)的结果

    • 第二个则返回作者(Author)的结果

    SELECT * FROM BLOG WHERE ID = #{id};
    SELECT * FROM AUTHOR WHERE ID = #{id};
    
  • 在映射语句中必须通过 resultSets 属性为每个结果集指定一个名字,多个名字使用逗号隔开

    <select id="selectBlog" resultSets="blogs, authors" 
            resultMap="blogResult" statementType="CALLABLE">
      {call getBlogsAndAuthors(#{id, jdbcType=INTEGER, mode=IN})}
    </select>
    
  • 指定使用 authors 结果集的数据填充 author 关联

    <resultMap id="blogResult" type="Blog">
        <id property="id" column="id" />
        <result property="title" column="title"/>
        <association property="author" javaType="Author" resultSet="authors" 
                     column="author_id" foreignColumn="id">
            <id property="id" column="id"/>	
            <result property="username" column="username"/>
            <result property="password" column="password"/>
            <result property="email" column="email"/>
            <result property="bio" column="bio"/>
        </association>
    </resultMap>
    

collection

  • 处理有很多个类型的关联

    • 集合元素和关联元素几乎是一样的
    <collection property="posts" ofType="domain.blog.Post">
        <id property="id" column="post_id"/>
        <result property="subject" column="post_subject"/>
        <result property="body" column="post_body"/>
    </collection>
    
  • 不同:ofType属性

    • 这个属性非常重要
    • 用来将 JavaBean(或字段)属性的类型和集合存储的类型区分开来
嵌套 Select 查询
  • 一个博客(Blog)只有一个作者(Author)

    • 但一个博客有很多文章(Post)
    // 博客类中属性定义
    private List<Post> posts;
    
  • 映射嵌套结果集合到 List 中,可以使用集合元素

    1. 可以使用嵌套 Select 查询
    2. 或基于连接的嵌套结果映射集合
示例
<resultMap id="blogResult" type="Blog">
    <collection property="posts" javaType="ArrayList" 
                column="id" ofType="Post" 
                select="selectPostsForBlog"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
    SELECT * FROM BLOG WHERE ID = #{id}
</select>

<select id="selectPostsForBlog" resultType="Post">
    SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>
ofType
  • 即 List 集合的泛型类型
<!-- 博客类中 posts 属性定义:private List<Post> posts; -->
<!-- 表名 posts 是一个存储 Post 的 list 集合 -->
<collection property="posts" javaType="ArrayList" 
            column="id" ofType="Post" 
            select="selectPostsForBlog"/>
<!-- 一般情况下,MyBatis 可以推断 javaType 属性,因此并不需要填写。很多时候可以简略 -->
<collection property="posts" column="id" 
            ofType="Post" select="selectPostsForBlog"/>
嵌套结果映射
  • 集合的嵌套结果映射
  • 除了新增的 ofType 属性,和关联的完全相同
示例
SQL
  • 连接了博客表和文章表
  • 并且为每一列都赋予了一个有意义的别名
    • 以便映射保持简单
<select id="selectBlog" resultMap="blogResult">
    select
    B.id as blog_id,
    B.title as blog_title,
    B.author_id as blog_author_id,
    P.id as post_id,
    P.subject as post_subject,
    P.body as post_body,
    from Blog B
    left outer join Post P on B.id = P.blog_id
    where B.id = #{id}
</select>
集合映射
<!-- 内嵌 -->
<resultMap id="blogResult" type="Blog">
    <id property="id" column="blog_id" />
    <result property="title" column="blog_title"/>
    <collection property="posts" ofType="Post">
        <id property="id" column="post_id"/>
        <result property="subject" column="post_subject"/>
        <result property="body" column="post_body"/>
    </collection>
</resultMap>

<!-- 更详略的、可重用的结果映射,等价上面 -->
<resultMap id="blogResult" type="Blog">
    <id property="id" column="blog_id" />
    <result property="title" column="blog_title"/>
    <collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/>
</resultMap>

<resultMap id="blogPostResult" type="Post">
    <id property="id" column="id"/>
    <result property="subject" column="subject"/>
    <result property="body" column="body"/>
</resultMap>
多结果集
ResultSet
  • 通过执行存储过程实现

  • 会执行两个查询并返回两个结果集

    • 一个是博客的结果集,另一个是文章的结果集
    SELECT * FROM BLOG WHERE ID = #{id};
    SELECT * FROM POST WHERE BLOG_ID = #{id};
    
  • 在映射语句中,必须通过 resultSets 属性为每个结果集指定名字

    • 多个名字使用逗号隔开
    <select id="selectBlog" resultSets="blogs, posts" resultMap="blogResult">
        {call getBlogsAndPosts(#{id, jdbcType=INTEGER, mode=IN})}
    </select>
    
  • 指定 posts 集合将使用存储在 posts 结果集中的数据进行填充

    <resultMap id="blogResult" type="Blog">
        <id property="id" column="id" />
        <result property="title" column="title"/>
        <collection property="posts" ofType="Post" resultSet="posts" 
                    column="id" foreignColumn="blog_id">
            <id property="id" column="id"/>
            <result property="subject" column="subject"/>
            <result property="body" column="body"/>
        </collection>
    </resultMap>
    
选择
  • 对关联或集合的映射,并没有深度、广度或组合上的要求
    • 但在映射时要留意性能问题
  • MyBatis 的好处
    • 可以在不对代码引入重大变更(如果有)的情况下,允许之后改变想法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值