MyBatis中ResultMap使用实例解析

实例说明

该项目是在:Spring Boot整合MyBatis框架操作MySQL数据库实例的基础上继续深入使用MaBatis的各项功能。项目开源地址:https://github.com/Yitian-Zhang/springboot-learning(包含下述所有代码)。

实例说明:在通过MyBatis官方文档(地址:https://mybatis.org/mybatis-3/zh/index.html)学习MyBatis的使用时,发现在对ResultMap的使用,仅存在示例的配置说明,并没有一个具体的实例可供学习者操作,这使得学习过程中只停留在理论而不好实践。所以本文依据MyBatis官方文档中给出的最复杂的一个ResultMap映射结果集和相应SELECT查询语句,反推出其中的表结构和类关系,从而还原该对该示例代码进行还原,使之成为可以作为实例操作的学习实例,并提供了开源。

如下是官方文档中,该示例配置代码的截图:

这里就以ResultMap中最复杂的一个示例入手来解析,ResultMap中的各项设置的使用,包括:Constructor,id,result,association,collection。此外,该项目中暂时没有使用discriminator的使用,后续将采用其他的实例进行说明。

创建实例表结构和类

如下表结构和类的关系是通过上述SQL SELECT查询语句和ResultMap结构进行反推得来的,用以模拟上述的示例。使用如下的SQL脚本创建相应的表结构:

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for tb_author
-- ----------------------------
DROP TABLE IF EXISTS `tb_author`;
CREATE TABLE `tb_author` (
  `author_id` int(11) NOT NULL AUTO_INCREMENT,
  `author_username` varchar(255) DEFAULT NULL,
  `author_password` varchar(255) DEFAULT NULL,
  `author_email` varchar(255) DEFAULT NULL,
  `author_bio` varchar(255) DEFAULT NULL,
  `author_favourite_section` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`author_id`)
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;

-- ----------------------------
-- Records of tb_author
-- ----------------------------
BEGIN;
INSERT INTO `tb_author` VALUES (2, 'yitian', '123', '[email protected]', 'my_bio', '12');
COMMIT;

-- ----------------------------
-- Table structure for tb_blog
-- ----------------------------
DROP TABLE IF EXISTS `tb_blog`;
CREATE TABLE `tb_blog` (
  `blog_id` int(11) NOT NULL,
  `blog_title` varchar(255) DEFAULT NULL,
  `author_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`blog_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

-- ----------------------------
-- Records of tb_blog
-- ----------------------------
BEGIN;
INSERT INTO `tb_blog` VALUES (1, 'yitian_blog', 2);
COMMIT;

-- ----------------------------
-- Table structure for tb_comment
-- ----------------------------
DROP TABLE IF EXISTS `tb_comment`;
CREATE TABLE `tb_comment` (
  `comment_id` int(11) NOT NULL,
  `comment_content` varchar(255) DEFAULT NULL,
  `post_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`comment_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

-- ----------------------------
-- Records of tb_comment
-- ----------------------------
BEGIN;
INSERT INTO `tb_comment` VALUES (1, 'Comment1', 1);
INSERT INTO `tb_comment` VALUES (2, 'Comment2', 1);
INSERT INTO `tb_comment` VALUES (3, 'Comment3', 2);
INSERT INTO `tb_comment` VALUES (4, 'Comment4', 3);
COMMIT;

-- ----------------------------
-- Table structure for tb_post
-- ----------------------------
DROP TABLE IF EXISTS `tb_post`;
CREATE TABLE `tb_post` (
  `post_id` int(11) NOT NULL,
  `post_subject` varchar(255) DEFAULT NULL,
  `author_id` int(11) DEFAULT NULL,
  `draft_status` int(11) DEFAULT NULL,
  `post_content` varchar(255) DEFAULT NULL,
  `blog_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`post_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

-- ----------------------------
-- Records of tb_post
-- ----------------------------
BEGIN;
INSERT INTO `tb_post` VALUES (1, 'subject1', 2, 1, 'Post1', 1);
INSERT INTO `tb_post` VALUES (2, 'subject2', 2, 1, 'Post2', 1);
INSERT INTO `tb_post` VALUES (3, 'subject3', 2, 1, 'Post3', 1);
COMMIT;

-- ----------------------------
-- Table structure for tb_tag
-- ----------------------------
DROP TABLE IF EXISTS `tb_tag`;
CREATE TABLE `tb_tag` (
  `tag_id` int(11) NOT NULL,
  `tag_content` varchar(255) DEFAULT NULL,
  `post_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`tag_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

-- ----------------------------
-- Records of tb_tag
-- ----------------------------
BEGIN;
INSERT INTO `tb_tag` VALUES (1, 'Tag1', 1);
INSERT INTO `tb_tag` VALUES (2, 'Tag2', 2);
INSERT INTO `tb_tag` VALUES (3, 'Tag3', 3);
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;

上述SQL脚本创建了如下的表结构,并插入了一些示例的数据:

  1. tb_author:Blog作者表,与Blog一对一的关系。
  2. tb_blog:Blog表,与author一对一,与post一对多。
  3. tb_post:Post文章表,与blog多对一,与comment一对多,与tag一对多。
  4. tb_comment:文章评论表,与post多对一。
  5. tb_tag:文章标签表,与post多对一。

相应的创建上述5个表所对应的Java类结构如下:(其中Data注解为使用的Lombok)

@Data
public class Blog {
    private Integer id;
    private String title;
    private Author author;
    private List<Post> posts;
}

@Data
public class Author {
    private Integer id;
    private String userName;
    private String password;
    private String email;
    private String bio;
    private String favouriteSection;
}

@Data
public class Post {
    private Integer id;
    private String subject;
    private Author author;
    private List<Comment> comments;
    private List<Tag> tags;
    private Integer draftStatus;
    private String content;
}

@Data
public class Comment {
    private Integer id;
    private String content;
}

@Data
public class Tag {
    private Integer id;
    private String content;
}

所以根据以上的关系可以看出:一个blog包含一个author和多个post,一个post包含一个author(实际上和前一个author相同),多个comment和多个tag,comment和tag相互独立。

在MyBatis映射文件中使用ResultMap

单表ResultMap简单使用

有了以上的表和类,并插入了相应的简单数据用于查询,下面就可以使用resultMap来编写对应的映射结果集了。首先使用Author来实现一个单表的查询操作(暂时没有使用多表关联),authorMapper.xml文件内容如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="cn.zyt.springbootlearning.dao.AuthorMapper">

    <resultMap id="authorMap" type="cn.zyt.springbootlearning.domain.mybatis.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"/>
        <result property="favouriteSection" column="author_favourite_section"/>
    </resultMap>

    <select id="getAuthor" parameterType="int" resultMap="authorMap">
        SELECT * FROM tb_author WHERE author_id=#{id}
    </select>
</mapper>

如上所示,仅是简单使用id对author的记录进行查询,并将查询结果使用resultMap进行映射。其中需要关注的点如下:

扫描二维码关注公众号,回复: 9130099 查看本文章
  • select元素和resultMap元素使用resultMap中的id属性与select元素中的resultMap属性进行关联。select中若使用resultMap则resultType属性则会失效。
  • resultMap元素中,type属性指明返回集的类型。
  • <id>和<result>元素都是表明表中的列名和类对象之间的映射关系,id元素用于表中主键(文档中说如此设置有利于提高查询性能,但没有说明原因,为提高性能需要进一步探索),result元素用于普通列名和属性名的对应,property指明类中属性,column指明对应的查询表列名,如果查询中对查询列名设置了as 别名,则column应是对应的别名。

说明:myBatis中实际上有强大的自动映射功能,可以使用别名自动映射,或者使用自动驼峰式映射,但这里为了学习resultMap的使用,没有使用别名的方式进行。

使用如上的映射文件时,创建如下对应的AuthorMapper接口和getAuthor方法,该接口无需实现,既可以使用:

@Repository
public interface AuthorMapper {
    Author getAuthor(Integer id);
}

编写简单的Controller用于上述方法的测试,得到的结果如下:

上述方式为仅使用配置文件的方式,此外还可以使用anotation或provider的方式实现同样的功能:(注:此时需要将authorMappe.xml文件中的select元素注释掉)

@Repository
public interface AuthorMapper {

    /**
     * 简单的SQL查询,可以不需要Provider和mapper.xml文件中的<select>元素
     * 但由于Author的属性值和表的列名不对应,所以需要mapper.xml文件中定义ResultMap进行映射
     */
    @Select("SELECT * FROM tb_author WHERE author_id=#{id}")
    @ResultMap("authorMap")
    Author getAuthor(Integer id);
}

或者创建AuthorProvider类定义相关的SQL语句,并配合ResultMap进行使用:

// 创建AuthorProvider.java文件
public class AuthorProvider {
    public String getAuthor(Integer id) {
        return new SQL() {
            {
                SELECT("*");
                FROM("tb_author");
                WHERE("author_id=#{id}");
            }
        }.toString();
    }
}

// AuthorMapper.java文件
@SelectProvider(type = AuthorProvider.class, method = "getAuthor")
@ResultMap("authorMap")
Author getAuthorProvider(Integer id);

对于以上三种方式总结如下:

【只使用配置文件的方式】:可以使用select元素,指定对应的返回ResultMap的id,然后在AuthorMap接口中声明对应的getAuthor方法,来进行映射。
【使用注解配合使用配置文件的方式】:在不使用如下的Select元素的情况下可以使用如下方式进行:

  1. 直接在接口方法中使用@Select注解,针对较简单的SQL语句较为适合
  2. 使用@SelectProvider并创建相应的Provider类,实现sql语句,针对复杂的SQL场景比较适用。 

同时以上两种注解方式中,都可以使用@ResultMap注解,指明authorMapper.xml配置文件中用于设置查询结果的ResultMap。

简单的ResultMap的使用在此就不再详述了,参考mybatis的官方文档可以进行很好的学习。下面来对最上面说明的示例映射进行解析。

复杂的ResultMap映射关系解析

现在回到上述最复杂的那个列子,需要创建并编写blogMapper.xml配置文件,其中select元素中的查询语句如下:

    <select id="getBlog" parameterType="int" resultMap="blogMap">
        select
            B.blog_id,
            B.blog_title,
            A.author_id,
            A.author_username,
            A.author_password,
            A.author_email,
            A.author_bio,
            A.author_favourite_section,
            P.post_id,
            P.post_subject,
            P.author_id as post_author_id,
            P.draft_status as post_draft_status,
            P.post_content,
            C.comment_id,
            C.comment_content,
            T.tag_id,
            T.tag_content
        from tb_blog B
            left outer join tb_author A on B.author_id=A.author_id
            left outer join tb_post P on B.blog_id=P.blog_id
            left outer join tb_comment C on C.post_id=P.post_id
            left outer join tb_tag T on T.post_id=P.post_id
        where B.blog_id=#{id}
    </select>

可以看到该SQL语句中,存在如下的注意事项:

  1. select语句中,对于没有使用as别名的查询column,MyBatis会直接使用原有的列名进行映射。对于使用as别名的列,则使用别名进行映射。
  2. 如果没有使用别名,并且resultmap没有设置映射关系时,mybatis会自动映射到相应的属性,如果无法映射则会报错。

创建该查询语句的ResultMap结构如下:

<resultMap id="blogMap" type="cn.zyt.springbootlearning.domain.mybatis.Blog">
        <constructor>
            <idArg column="blog_id" javaType="int" name="id"/>
        </constructor>
        <id property="id" column="blog_id"/>
        <result property="title" column="blog_title"/>
        <association property="author" javaType="cn.zyt.springbootlearning.domain.mybatis.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"/>
            <result property="favouriteSection" column="author_favourite_section"/>
        </association>
        <collection property="posts" ofType="cn.zyt.springbootlearning.domain.mybatis.Post">
            <id property="id" column="post_id"/>
            <result property="subject" column="post_subject"/>
            <result property="content" column="post_content"/>
            <result property="draftStatus" column="post_draft_status"/>
            <association property="author" javaType="cn.zyt.springbootlearning.domain.mybatis.Author">
                <id property="id" column="post_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"/>
                <result property="favouriteSection" column="author_favourite_section"/>
            </association>
            <collection property="comments" ofType="cn.zyt.springbootlearning.domain.mybatis.Comment">
                <id property="id" column="comment_id"/>
                <result property="content" column="comment_content"/>
            </collection>
            <collection property="tags" ofType="cn.zyt.springbootlearning.domain.mybatis.Tag">
                <id property="id" column="tag_id"/>
                <result property="content" column="tag_content"/>
            </collection>
        </collection>
    </resultMap>

根据下图进行分析:

注意:该图为MyBatis示例文档中的解析图,与上述实际的代码有一定的出入。具体实现以代码为准,这里只是分析其映射关系。此外,<discriminator>在代码中暂时省去,将draft属性使用<result>进行映射。

<id>和<result>

这两个元素都是设置将查询结果,根据column映射到property的对应关系的,是映射配置的最基本的内容。这两者之间的唯一不同是,id元素表示的结果是对象的标识属性,这会在比较对象实例时用到,这样可以提高整体的性能,尤其是进行缓存和嵌套结果映射时(官方文档给出的说明)。

这两个元素可以使用的属性如下:

<constructor>

<constructor >允许在将查询结果映射为类对象时,使用类相应的构造方法来设置属性的值。这里就是在得到查询结果后,使用如下的构造方法(参数使用了id与查询列进行对应),来设置了Blog类中id属性的值:

    public Blog(Integer id) {
        this.id = id;
    }

注:如果无此构造方法,mybatis在映射结果集时将报错。

<association>

association关系表示“有一个”的关系,对应于上述的实例,就是blog中有一个author。一般使用嵌套结果映射的方式实现关联关系的映射:

        <association property="author" javaType="cn.zyt.springbootlearning.domain.mybatis.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"/>
            <result property="favouriteSection" column="author_favourite_section"/>
        </association>

在resultmap中的第一个association即表示:将查询结果的author_id, _username, _password, _email, _bio, _author_favourite_section列分别映射到对应的属性中。同时这些属性属于Author类,因此根据这些属性,创建一个Author类的实例对象,并将其映射到Blog中关联的author属性中。

<collection>

        <collection property="posts" ofType="cn.zyt.springbootlearning.domain.mybatis.Post">
            <id property="id" column="post_id"/>
            <result property="subject" column="post_subject"/>
            <result property="content" column="post_content"/>
            <result property="draftStatus" column="post_draft_status"/>
            ...
        </collection>

<collection>表示“有多个”的关系。对应与如上的配置,即为:一个Blog有多个Post,反应到类结构上就是:

public class Blog {
    ...
    private List<Post> posts;
}

只不过Post中还有另外的嵌套结果映射,包括一个Author author,对应于:

            <association property="author" javaType="cn.zyt.springbootlearning.domain.mybatis.Author">
                <id property="id" column="post_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"/>
                <result property="favouriteSection" column="author_favourite_section"/>
            </association>

多个评论List<Comment> comments,和多个标签List<Tag> tags对应于:

            <collection property="comments" ofType="cn.zyt.springbootlearning.domain.mybatis.Comment">
                <id property="id" column="comment_id"/>
                <result property="content" column="comment_content"/>
            </collection>
            <collection property="tags" ofType="cn.zyt.springbootlearning.domain.mybatis.Tag">
                <id property="id" column="tag_id"/>
                <result property="content" column="tag_content"/>
            </collection>

结果测试

编写完成上述的ResultMap后,创建BlogMapper接口,并声明其中的getBlog方法如下:

@Repository
public interface BlogMapper {
    Blog getBlog(Integer id);
}

然后通过Controller进行测试,得到最后的结果为:

可以看到已经将SELECT语句得到的查询列,根据ResultMap映射为一个完整的Blog对象。以上方式为使用纯配置文件的方式进行的,也可以使用@SelectProvider注解来实现相应的方法,这里就不再给出。

实例总结

以上实例基于MyBatis官网文档中的例子,反推得到表和类的结构和关联关系,从而实现了简单的ResultMap和复杂ResultMap的使用,以加深对MyBatis的使用。

在对ResultMap的理解过程中,需要明确ResultMap定义的是对SQL查询结果集的映射结构。也就是官方文档中所说的,对于简单的语句根本不需要显式配置resultMap,因为mybatis为自定创建resultmap并完成映射,而对于复杂的返回结果集,resultmap描述的是结果集中所包含对象的关系。这是非常重要的。

发布了296 篇原创文章 · 获赞 35 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/yitian_z/article/details/104193480