[03] mapper.xml的基本元素概述


1、select

我们基于这个持久层接口 GirlDao:
public interface GirlDao {

    List<Girl> findByAge(int age);

    Girl findById(long id);

    int insertGirl(Girl girl);

    int updateGirl(Girl girl);

    int deleteGirl(long id);
}

关于select,我们还是先使用最先提到的简单的mapper的一个例子:
<mapper namespace="dulk.learn.mybatis.dao.GirlDao">
    <select id="findById" parameterType="long" resultType="dulk.learn.mybatis.pojo.Girl">
        SELECT * FROM girl WHERE id = #{id}
    </select>
</mapper>

这里的 <select> 标签,即代表的是 select 操作,你应该可以联想到,我们当然还有 <insert>、<update>、<delete> 标签。纵观这段内容,需要提醒你知道的是:
  • select 标签的 id 属性用来标记和区别该 select,如果需要对应接口则 id 和接口方法名要一致
  • parameterType 表示输入参数的类型
  • resultType 表示输出结果的类型
  • #{ } 表示传入的动态参数的占位符

简单来说就是,这个语句叫 findById,接受long(或Long)类型参数(#{id} 则说明参数名为id),返回Girl类型的对象。其实这也相当于告诉MyBatis创建一个预处理语句对象,并传入相应的值,所以以上又相当于JDBC的如下操作:
String findById = "SELECT * FROM girl WHERE id = ?";
PreparedStatement ps = conn.prepareStatement(findById);
ps.setLong(1, id);

1.1 输入参数 parameterType

在如上的示例中我们已经看到,输入参数的属性为 parameterType,其值是将会传入这条语句的参数类的 “完全限定名” 或 “别名”(还记得别名吗?参见mybatis-config.xml全局配置文件说明中的typeAliases)。

另外可喜的是,这个属性其实是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,所以这个属性的默认值是 unset,当然,往往我们在开发的时候还是主动进行了限定,因为很多时候我们传入的将会是一个封装了参数的类。

上面的含义是说,假如你传入的是一个自定义的对象,那么占位符的属性会在该对象属性中进行查找并赋值:
<insert id="insertUser" parameterType="User">
    insert into users (id, username, password)
    values (#{id}, #{username}, #{password})
</insert>

如上若 User 类型的参数对象传递到了语句中,id、username 和 password 属性将会被查找,然后将它们的值传入预处理语句的参数中。

1.2 输出结果 resultType / resultMap

输出结果有两种形式来进行限定,即你要么使用 resultType,要么使用 resultMap,两者不能同时出现在相同的语句标签中:
  • resultType - 返回的期望类型的类的完全限定名或别名(注:若是集合情形,那应该是集合可包含的类型,而不能是集合本身)
  • resultMap - 外部 resultMap 的命名引用

resultType很好理解,其实就和parameterType的使用性质是一样的。那么来简单说明一下这里的resultMap和其外部命名引用是什么意思。

其实在select标签之外,有一个同级的标签,也叫 resultMap(注意我们刚才提到的resultMap是作为select标签的属性出现的),该标签主要针对一些复杂的结果映射,用来 “描述语句和对象之间的映射关系”,你可以理解为我们手动地匹配查询出的列名和对象的属性之间的对应关系(注意是“查询出的列名”而非数据库原本的列名,这意味着你语句中是否有使用“AS”关键字会相应对该映射关系的描述造成影响)。

还是之前的mapper的例子,用resultMap来配置的话,就应该是如下面貌:
<mapper namespace="dulk.learn.mybatis.dao.GirlDao">
    <!--定义resultMap-->
    <resultMap id="girlResultMap" type="dulk.learn.mybatis.pojo.Girl">
        <!--id表查询结果中的唯一标识-->
        <id property="id" column="id" />
        <!--result表示对普通列名的映射,propertyi表对象属性名,column表查询出的列名-->
        <result property="age" column="age" />
    </resultMap>

    <!--引用外部resultMap,即girlResultMap-->
    <select id="findById" parameterType="long" resultMap="girlResultMap">
        SELECT * FROM girl WHERE id = #{id}
    </select>
</mapper>

所以,如果你是使用ResultType作为输出映射,只有查询出来的列名和对象属性名一致才可映射成功。否则,你就需要定义一个resultMap来描述两者之间的关系。

另外值得一提的是,对于生成的结果是单个还是集合,往往是根据对应接口的返回值类型来确定的,假如 GirlDao 中 List<Girl> findByAge(int age),则返回集合(MyBatis内部调用selectList方法);而诸如 Girl findById(long id) 返回单个对象调用(MyBatis内部调用selectOne方法),所以哪怕你知道你sql会返回的是多个对象,你在 resultType 中也只需要定义的是单个元素的类型,而不是集合。

2、insert, update 和 delete

我们已经知道了<select>,举一反三来看,其实 <insert>、<update>、<delete> 也就不难使用了,如下例:
<!--使用了useGeneratedKeys和keyProperty来将生成的主键设置到对象属性中-->
<insert id="insertGirl" parameterType="dulk.learn.mybatis.pojo.Girl" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO girl (age)
    VALUES (#{age})
</insert>

<update id="updateGirl" parameterType="dulk.learn.mybatis.pojo.Girl">
    UPDATE girl
    SET age = #{age}
    WHERE id = #{id}
</update>

<delete id="deleteGirl" parameterType="long">
    DELETE FROM girl
    WHERE id = #{id}
</delete>

只是需要注意的是:
  • insert / update / delete 都没有 resultType 或 resultMap,他们的返回值是受影响的行数
  • 若数据库支持自动生成主键,可设置 useGeneratedKeys 为 true,并使用 keyProperty 将生成的主键值设置到目标属性上(如上例的insert)

做了个单元测试如下:
public class Test {

    @org.junit.Test
    public void testMyBatis() throws IOException {
        //读取配置文件
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        //获取工厂类
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        //获取SqlSession数据库会话对象
        SqlSession sqlSession = factory.openSession();
        //获取Dao
        GirlDao girlDao = sqlSession.getMapper(GirlDao.class);

        Girl girl = new Girl();
        girl.setAge(18);

        //insert a girl with age 18
        int resultInsert = girlDao.insertGirl(girl);
        Assert.assertEquals(1, resultInsert);
        Assert.assertEquals(18, girlDao.findById(girl.getId()).getAge());

        //update girl's age from 18 to 20
        girl.setAge(20);
        int resultUpdate = girlDao.updateGirl(girl);
        Assert.assertEquals(1, resultUpdate);
        Assert.assertEquals(20, girlDao.findById(girl.getId()).getAge());

        //delete girl
        int resultDelete = girlDao.deleteGirl(girl.getId());
        Assert.assertEquals(1, resultDelete);
        Assert.assertNull(girlDao.findById(girl.getId()));
    }
    
}

3、sql

sql元素用来定义可重用的SQL代码片段,其他语句可以用过 <include> 标签来将之包含其中。看个例子就能明白了:
<mapper namespace="dulk.learn.mybatis.dao.GirlDao">

    <resultMap id="girlResultMap" type="dulk.learn.mybatis.pojo.Girl">
        <id property="id" column="id" />
        <result property="age" column="age" />
        <result property="cupSize" column="cup_size" />
    </resultMap>

    <!--定义可重用的sql片段-->
    <sql id="girlColumn">
      id, age
    </sql>

    <select id="findById" parameterType="long" resultMap="girlResultMap">
        <!--通过include引用外部sql片段-->
        SELECT <include refid="girlColumn" />
        FROM girl WHERE id = #{id}
    </select>

</mapper>


posted @ 2018-07-19 11:31  Dulk  阅读(4217)  评论(0编辑  收藏  举报