返回现场笔记
2 分钟阅读

Hello Mybatis 03 数据关联

讲解 Mybatis 中 resultMap 的用法,并通过 association 和 collection 标签实现一对一、一对多的数据关联查询。

  • #java
  • #mybatis
  • #orm

ResultMap

在实际的开发中,数据库不总是我们希望看到的样子。比如我们希望User的主键是id但是数据库偏偏喜欢叫它u_id,这样一来原先的resultType似乎就失效了,不带这么玩的,整个人都不好了。

于是mybatis给出了他的方案——resultMap。把我们从复杂的命名问题中解救出来~~~

在上一篇中已经用mybatis generator生成好了一个BlogMapper.xml。现在让我们分析下这个文件。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="pro.app.inter.BlogMapper" >
  <resultMap id="BaseResultMap" type="pro.app.model.Blog" >
    <id column="b_id" property="bId" jdbcType="INTEGER" />
    <result column="b_title" property="bTitle" jdbcType="VARCHAR" />
    <result column="b_content" property="bContent" jdbcType="VARCHAR" />
    <result column="user_id" property="userId" jdbcType="INTEGER" />
  </resultMap>

  <sql id="Base_Column_List" >
    b_id, b_title, b_content, user_id
  </sql>
  <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
    select
    <include refid="Base_Column_List" />
    from blog
    where b_id = #{bId,jdbcType=INTEGER}
  </select>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer" >
    delete from blog
    where b_id = #{bId,jdbcType=INTEGER}
  </delete>
  <insert id="insert" parameterType="pro.app.model.Blog" >
    <selectKey resultType="java.lang.Integer" keyProperty="bId" order="AFTER" >
      SELECT LAST_INSERT_ID()
    </selectKey>
    insert into blog (b_title, b_content, user_id
      )
    values (#{bTitle,jdbcType=VARCHAR}, #{bContent,jdbcType=VARCHAR}, #{userId,jdbcType=INTEGER}
      )
  </insert>
  <insert id="insertSelective" parameterType="pro.app.model.Blog" >
    <selectKey resultType="java.lang.Integer" keyProperty="bId" order="AFTER" >
      SELECT LAST_INSERT_ID()
    </selectKey>
    insert into blog
    <trim prefix="(" suffix=")" suffixOverrides="," >
      <if test="bTitle != null" >
        b_title,
      </if>
      <if test="bContent != null" >
        b_content,
      </if>
      <if test="userId != null" >
        user_id,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides="," >
      <if test="bTitle != null" >
        #{bTitle,jdbcType=VARCHAR},
      </if>
      <if test="bContent != null" >
        #{bContent,jdbcType=VARCHAR},
      </if>
      <if test="userId != null" >
        #{userId,jdbcType=INTEGER},
      </if>
    </trim>
  </insert>
  <update id="updateByPrimaryKeySelective" parameterType="pro.app.model.Blog" >
    update blog
    <set >
      <if test="bTitle != null" >
        b_title = #{bTitle,jdbcType=VARCHAR},
      </if>
      <if test="bContent != null" >
        b_content = #{bContent,jdbcType=VARCHAR},
      </if>
      <if test="userId != null" >
        user_id = #{userId,jdbcType=INTEGER},
      </if>
    </set>
    where b_id = #{bId,jdbcType=INTEGER}
  </update>
  <update id="updateByPrimaryKey" parameterType="pro.app.model.Blog" >
    update blog
    set b_title = #{bTitle,jdbcType=VARCHAR},
      b_content = #{bContent,jdbcType=VARCHAR},
      user_id = #{userId,jdbcType=INTEGER}
    where b_id = #{bId,jdbcType=INTEGER}
  </update>
</mapper>

在文件的开头位置,就能够发现一个名为BaseResultMap的resultMap标签。在这个标签当中我们可以发现几个子标签。这几个子标签对通过column和property属性将数据库字段和model对应类型关联起来。而标签代表这个model类的属性对应的数据库字段为这张表的主键。定义完成这个BaseResultMap之后,我们就可以在后面的标签对中使用它作为返回的结果使用。 这里可以注意到select的属性resultMap="BaseResultMap" 。返回的数据通过resultMap被封装成了相应的对象,如果返回的数据是多条,mybatis也会自动将结果集转换为List集合。

这里还可以关注下resultMap下的sql标签对,在这个标签对中可以写入数据库的字段名称。在CRUD的xml文件中使用标签引用这它,在数据库需要修改的时候,我们就不用去修改这个配置文件下每一个sql语句。

数据关联

有人会说。这样resultMap和resultType有什么区别!还要多写这么一堆配置,这不是吃饱了撑着么!!!真的是这个样子?

我们之前设计了两张表,一个用户表和一个博客列表,一个用户可以有多篇博客吧,一篇博客只有一个作者。现在就让resultMap帮助我们把数据关联起来~

先给表Blog添加几条数据

INSERT INTO `blog`.`blog` (`b_id`, `b_title`, `b_content`, `user_id`) VALUES ('1', '001', 'mybatis001', '1');
INSERT INTO `blog`.`blog` (`b_id`, `b_title`, `b_content`, `user_id`) VALUES ('2', '002', 'mybatis002', '1');
INSERT INTO `blog`.`blog` (`b_id`, `b_title`, `b_content`, `user_id`) VALUES ('3', '003', 'mybatis003', '1');
INSERT INTO `blog`.`blog` (`b_id`, `b_title`, `b_content`, `user_id`) VALUES ('4', '004', 'mybatis004', '1');
INSERT INTO `blog`.`blog` (`b_id`, `b_title`, `b_content`, `user_id`) VALUES ('5', '005', 'mybatis005', '1');

在此之前我们需要再定义一个BlogVo类,并继承Blog类,然后添加一个User类型属性user。

package pro.app.model;

public class BlogVo extends Blog{
    private User user;
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
}

接着在BlogMapper.xml中添加一个resultMap。

<resultMap id="BaseResultMapWithUser" type="pro.app.model.BlogVo" >
    <id column="b_id" property="bId" jdbcType="INTEGER" />
    <result column="b_title" property="bTitle" jdbcType="VARCHAR" />
    <result column="b_content" property="bContent" jdbcType="VARCHAR" />
    <association property="user"  javaType="pro.app.model.User">
        <id column="user_id" property="id" jdbcType="INTEGER" />
        <result column="name" property="name" jdbcType="VARCHAR" />
        <result column="age" property="age" jdbcType="INTEGER" />
    </association>
  </resultMap>

在resultMap中通过子标签association,我们将外键与对应的model类型对应起来。用association中的property属性对应java属性的用户。association下的子标签id对应blog表中的外键,这也是数据关联的关键!接着再给这个文件添加一个select标签。

 <select id="selectByPrimaryKeyWithUser" resultMap="BaseResultMapWithUser" parameterType="java.lang.Integer" >
    select *
    from blog b
    join user u
    on b.user_id=u.id
    where b.b_id= #{id,jdbcType=INTEGER}
 </select>

ok,让我们在BlogMapper.java里添加一个selectByPrimaryKeyWithUser()方法。

package pro.app.inter;

import org.apache.ibatis.annotations.Param;

import pro.app.model.Blog;
import pro.app.model.BlogVo;

public interface BlogMapper {
    int deleteByPrimaryKey(Integer bId);

    int insert(Blog record);

    int insertSelective(Blog record);

    Blog selectByPrimaryKey(Integer bId);

    int updateByPrimaryKeySelective(Blog record);

    int updateByPrimaryKey(Blog record);

    BlogVo selectByPrimaryKeyWithUser(@Param("id")Integer id);

}

建立一个测试类来看看效果

package pro.test;

import java.io.Reader;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import pro.app.inter.BlogMapper;
import pro.app.model.Blog;
import pro.app.model.BlogVo;

public class BlogVoTest {
    private static SqlSessionFactory sqlSessionFactory;
    private static Reader reader;
    static{
        try{
            reader= Resources.getResourceAsReader("Configuration.xml");
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    public static SqlSessionFactory getSession(){
        return sqlSessionFactory;
    }
    public static void main(String[] args) {
        SqlSession session = sqlSessionFactory.openSession();
        try {
            BlogMapper blogs = session.getMapper(BlogMapper.class);
            BlogVo bv=blogs.selectByPrimaryKeyWithUser(1);
            System.out.println(bv.getUser().getName());
        } finally {
            session.close();
        }
    }
}

控制台输出了:

Mybatis

ok!mybatis帮我们把博客的作者选出来了。现在我们来通过作者选出他所有的文章。

同样我们在UserMapper.xml里添加一个resultMap

  <resultMap id="BaseResultMapWithBlogs" type="pro.app.model.UserVo" >
        <id column="id" property="id" jdbcType="INTEGER" />
        <result column="name" property="name" jdbcType="VARCHAR" />
        <result column="age" property="age" jdbcType="INTEGER" />
        <collection property="blogs" ofType="pro.app.model.Blog" column="b_id">
            <id column="b_id" property="bId" jdbcType="INTEGER" />
            <result column="b_title" property="bTitle" jdbcType="VARCHAR" />
            <result column="b_content" property="bContent" jdbcType="VARCHAR" />
        </collection>
    </resultMap>

这里的resultMap还多个了一个collection标签,顾名思义这表示一个集合,collection的column表示结果集对应的table的主键。现在再添加一个select标签。

    <select id="selectOneWithBlogs" resultMap="BaseResultMapWithBlogs" parameterType="java.lang.Integer">
        select *
        from blog b
        join user u
        on b.user_id=u.id
        where u.id = #{id,jdbcType=INTEGER}
    </select>

在UserDAO.java里添加一个selectOneWithBlogs()方法。

package pro.app.inter;
import org.apache.ibatis.annotations.Param;

import pro.app.model.User;
import pro.app.model.UserVo;

public interface UserDAO {
    public User selectOne(@Param("id")Integer id);

    public void insertOne(User user);

    public void deleteOne(@Param("id")Integer id);

    public void updateOne(User user);

    UserVo selectOneWithBlogs(@Param("id")Integer id);
}

新建一个测试类,观察结果。

package pro.test;

import java.io.Reader;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import pro.app.inter.UserDAO;
import pro.app.model.Blog;
import pro.app.model.UserVo;

public class UserVoTest {
    private static SqlSessionFactory sqlSessionFactory;
    private static Reader reader;
    static{
        try{
            reader= Resources.getResourceAsReader("Configuration.xml");
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    public static SqlSessionFactory getSession(){
        return sqlSessionFactory;
    }
    public static void main(String[] args) {
        SqlSession session = sqlSessionFactory.openSession();
        try {
            UserDAO users = session.getMapper(UserDAO.class);
            UserVo user=users.selectOneWithBlogs(1);
            for(Blog b:user.getBlogs()){
                System.out.println(b.getbTitle());
                System.out.println(b.getbContent());
            }
        } finally {
            session.close();
        }
    }
}

控制台输出

001
mybatis001
002
mybatis002
003
mybatis003
004
mybatis004
005
mybatis005

bingo!!!用户对应的博客都被选出来了~

总结

通过resultMap 实现数据的关联。