第7章 MyBatis框架入门详解(2)

 MyBatis语法

通过前面的介绍我们已经了解到了MyBatis的基本使用方法,其最大的特点之一就是可以通过mapper.xml配置文件定义需要的查询语句,前文简单对MyBatis的语法进行了介绍,这一章继续对其进行介绍。MyBatis的语法主要可以分为几大类

  • SQL类型映射相关:select、insert、 update 、delete标签

  • 动态SQL生成相关:if、where、trim、set和sql标签

  • 结果映射相关:resultType,resultMap

  • 关联映射相关:association、collection

SQL类型映射相关

MyBatis与SQL类型映射相关的主要是select、insert、 update 、delete和sql标签,它们主要是用于指明生成不同类型的SQL语句即到底是生成常见的增、删、查、改哪一类,其中select标签用于映射select语句,update标签用于映射update语句,delete标签用于映射delete语句,sql标签主要是为一段SQL语句取一个名字,方便再其他地方引用。

select标签

select标签用来映射查询语句

    <select id="selectOne" parameterType="Long" resultMap="BaseResultMap">
        SELECT
            *
        FROM user
        WHERE id = #{id}
    </select>
 

MyBatis的标签都有很多属性,如select有

  • id:命名空间中唯一的标识符,可以被用来引用这条语句。
  • parameterType:传入这条语句的参数类,这个属性是可选的,当这个参数没有填写时,MyBatis可以通过TypeHandler推断出具体传入语句的参数,默认值为未设置。
  • resultType:从这条语句将返回的类型的类,注意不能和resultMap混用。
  • resultMap:外部resultMap的命名引用。结果集的映射是MyBatis最强大的特性,许多复杂映射的情形都能通过resultMap迎刃而解。返回时可以使用resultMap 或resultType,但不能同时使用。
  • flushCache:是否清空缓存,如果为true,则只要语句被调用,都会清空本地缓存和二级缓存,默认值为false
  • useCache:是否使用缓存,如果为true,将会导致本条语句的结果被二级缓存,在select标签当中默认值为true。
  • timeout:等待数据库返回请求结果的时间,超时则抛出异常。默认值未设置,依赖具体驱动。
  • fetchSize:驱动程序每次批量返回的结果行数。默认值未设置,依赖具体驱动。
  • statementType:值为STATEMENT、PREPARED 或CALLABLE。这会让MyBatis分别使用JDBC中的Statement、PreparedStatement或CallableStatement,默认值为PREPARED。
  • resultSetType:结果集的类型,值为FORWARD_ONLY、SCROLL_SENSITIVE 或SCROLLINSENSITIVE,默认值未设置,依赖具体驱动。
  • databaseId:MyBatis用于根据不同数据库选择合适SQL语句,如果配置了databaseIdProvider,MyBatis会加载所有的不带databaseId或匹配当前databaseId的语句。如果带或者不带的语句都有,则不带的会被忽略。

我们在UserMapper.java添加方法selectOne,然后在测试类中添加测试代码

    @Test
    public void testSelectOneUser() {
         User user = userMapper.selectOne(1L);
         System.out.println("name:" + user.getName() + 
         ", age:" +user.getAge() + ", city_id:" + user.getCity_id());
    }
 

执行输出,说明成功执行了select语句

insert标签

insert标签用于生成insert语句,例如

 <insert id="insertOneUser" parameterType="com.cjl.chapter6.model.User" 
        useGeneratedKeys="true" timeout="20">
        INSERT INTO user(name, age, city_id) 
        VALUES(
               #{name, jdbcType=CHAR}, 
               #{age, jdbcType=INTEGER}, 
               #{city_id, jdbcType=INTEGER})
 </insert>
insert的属性和select基本一致,但多了三个属性useGeneratedKeys,keyProperty,keyColumn
  • id:命名空间中唯一的标识符,可以被用来引用这条语句。
  • parameterType:传入这条语句的参数类,这个属性是可选的,当这个参数没有填写时,MyBatis可以通过TypeHandler推断出具体传入语句的参数,默认值为未设置。
  • resultType:从这条语句将返回的类型的类,注意不能和resultMap混用。
  • resultMap:外部resultMap的命名引用。结果集的映射是MyBatis最强大的特性,许多复杂映射的情形都能通过resultMap迎刃而解。返回时可以使用resultMap 或resultType,但不能同时使用。
  • flushCache:是否清空缓存,如果为true,则只要语句被调用,都会清空本地缓存和二级缓存,默认值为false
  • useCache:是否使用缓存,如果为true,将会导致本条语句的结果被二级缓存,在select标签当中默认值为true。
  • timeout:等待数据库返回请求结果的时间,超时则抛出异常。默认值未设置,依赖具体驱动。
  • fetchSize:驱动程序每次批量返回的结果行数。默认值未设置,依赖具体驱动。
  • statementType:值为STATEMENT、PREPARED 或CALLABLE。这会让MyBatis分别使用JDBC中的Statement、PreparedStatement或CallableStatement,默认值为PREPARED。
  • resultSetType:结果集的类型,值为FORWARD_ONLY、SCROLL_SENSITIVE 或SCROLLINSENSITIVE,默认值未设置,依赖具体驱动。
  • databaseId:MyBatis用于根据不同数据库选择合适SQL语句,如果配置了databaseIdProvider,MyBatis会加载所有的不带databaseId或匹配当前databaseId的语句。如果带或者不带的语句都有,则不带的会被忽略。
  • useGeneratedKeys :这会令MyBatis使用JDBC的getGeneratedKeys方法来获取由数据库内部生成的主键(比如,像MySQL和SQL Server这样的关系数据库管理系统的自动递增字段),默认值为false
  • keyProperty:唯一标记一个属性,MyBatis会通过getGeneratedKeys的返回值或者通过insert语句的selectKey子元素设置它的键值,默认为unset。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
  • keyColumn:通过生成的键值设置表中的列名,这个设置仅对某些数据库(像PostgreSQL) 是必须的,当主键列不是表中的第一列时需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。

我们在UserMapper.java添加方法insertOneUser,然后在测试类中添加测试代码

    @Test
    public void testInsertOneUser() {
        testSelectAllUser();
        userMapper.insertOneUser(new User("张晓敏",28,2));
        testSelectAllUser();
    }
 

随后执行测试代码,我们看到数据库中成功添加了我们插入的数据

update标签

用于生成update语句,示例如下

    <update id="updateByPrimaryKey" parameterType="com.cjl.chapter6.model.User">
        update user
            set name = #{name, jdbcType=CHAR},
                age = #{age, jdbcType=INTEGER},
                city_id = #{city_id, jdbcType=INTEGER}
        where id = #{id, jdbcType=INTEGER}
    </update>
 

update的属性和insert是一样的

  • id:命名空间中唯一的标识符,可以被用来引用这条语句。
  • parameterType:传入这条语句的参数类,这个属性是可选的,当这个参数没有填写时,MyBatis可以通过TypeHandler推断出具体传入语句的参数,默认值为未设置。
  • resultType:从这条语句将返回的类型的类,注意不能和resultMap混用。
  • resultMap:外部resultMap的命名引用。结果集的映射是MyBatis最强大的特性,许多复杂映射的情形都能通过resultMap迎刃而解。返回时可以使用resultMap 或resultType,但不能同时使用。
  • flushCache:是否清空缓存,如果为true,则只要语句被调用,都会清空本地缓存和二级缓存,默认值为false
  • useCache:是否使用缓存,如果为true,将会导致本条语句的结果被二级缓存,在select标签当中默认值为true。
  • timeout:等待数据库返回请求结果的时间,超时则抛出异常。默认值未设置,依赖具体驱动。
  • fetchSize:驱动程序每次批量返回的结果行数。默认值未设置,依赖具体驱动。
  • statementType:值为STATEMENT、PREPARED 或CALLABLE。这会让MyBatis分别使用JDBC中的Statement、PreparedStatement或CallableStatement,默认值为PREPARED。
  • resultSetType:结果集的类型,值为FORWARD_ONLY、SCROLL_SENSITIVE 或SCROLLINSENSITIVE,默认值未设置,依赖具体驱动。
  • databaseId:MyBatis用于根据不同数据库选择合适SQL语句,如果配置了databaseIdProvider,MyBatis会加载所有的不带databaseId或匹配当前databaseId的语句。如果带或者不带的语句都有,则不带的会被忽略。
  • useGeneratedKeys :这会令MyBatis使用JDBC的getGeneratedKeys方法来获取由数据库内部生成的主键(比如,像MySQL和SQL Server这样的关系数据库管理系统的自动递增字段),默认值为false
  • keyProperty:唯一标记一个属性,MyBatis会通过getGeneratedKeys的返回值或者通过insert语句的selectKey子元素设置它的键值,默认为unset。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
  • keyColumn:通过生成的键值设置表中的列名,这个设置仅对某些数据库(像PostgreSQL) 是必须的,当主键列不是表中的第一列时需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。

我们添加测试函数

    @Test
    public void testUpdateByPrimaryKey() {
        User user = userMapper.selectOne(6L);
        user.setName("张晓敏更新");
        user.setAge(29);
        userMapper.updateByPrimaryKey(user);
    }
 

执行后输出,相应数据被成功更新。

delete标签

用于生成delete语句

    <delete id="deleteByPrimaryKey" parameterType="long">
        delete  from user
            where id = #{id, jdbcType=INTEGER}
    </delete>
 

delete标签的属性和select是一致的(注意:delete标签没有useGeneratedKeys、keyProperty、keyColumn属性)

  • id:命名空间中唯一的标识符,可以被用来引用这条语句。
  • parameterType:传入这条语句的参数类,这个属性是可选的,当这个参数没有填写时,MyBatis可以通过TypeHandler推断出具体传入语句的参数,默认值为未设置。
  • resultType:从这条语句将返回的类型的类,注意不能和resultMap混用。
  • resultMap:外部resultMap的命名引用。结果集的映射是MyBatis最强大的特性,许多复杂映射的情形都能通过resultMap迎刃而解。返回时可以使用resultMap 或resultType,但不能同时使用。
  • flushCache:是否清空缓存,如果为true,则只要语句被调用,都会清空本地缓存和二级缓存,默认值为false
  • useCache:是否使用缓存,如果为true,将会导致本条语句的结果被二级缓存,在select标签当中默认值为true。
  • timeout:等待数据库返回请求结果的时间,超时则抛出异常。默认值未设置,依赖具体驱动。
  • fetchSize:驱动程序每次批量返回的结果行数。默认值未设置,依赖具体驱动。
  • statementType:值为STATEMENT、PREPARED 或CALLABLE。这会让MyBatis分别使用JDBC中的Statement、PreparedStatement或CallableStatement,默认值为PREPARED。
  • resultSetType:结果集的类型,值为FORWARD_ONLY、SCROLL_SENSITIVE 或SCROLLINSENSITIVE,默认值未设置,依赖具体驱动。
  • databaseId:MyBatis用于根据不同数据库选择合适SQL语句,如果配置了databaseIdProvider,MyBatis会加载所有的不带databaseId或匹配当前databaseId的语句。如果带或者不带的语句都有,则不带的会被忽略。

我们添加相应的测试方法

@Test
    public  void testDeleteByPrimaryKey() {
        userMapper.deleteByPrimaryKey(6L);
    }
 

运行之后,id=6的元素被成功删除

SQL动态生成相关

sql标签

sql元素可以被用来定义可重用的SQL代码段,可以使用include包含在其他语句中

    <sql id="Base_Column_List">
        id,name,age,city_id
    </sql>

    <select id="selectUseSqlTag" parameterType="Long" 
        resultType="com.cjl.chapter6.model.User">
        select
        <include refid="Base_Column_List"/>
        from user where id = #{id}
    </select>
上面相当于
select  id,name,age,city_id  from user where id = #{id}
我们添加测试代码进行验证
    @Test
    public void testSelectUseSqlTag() {
        User user = userMapper.selectUseSqlTag(2L);
        System.out.println("name: " + user.getName() + 
        " age: " + user.getAge()+" city_id: " + user.getCity_id());

    }
执行后输出

​if 标签

if 标签在mybatis的开发工作中主要用于where查询、insert插入和update更新三种操作中。当测试条件为真时插入相应的语句

    <select id="selectUserByIdLike"  parameterType="hashmap"  
        resultType="com.cjl.chapter6.model.User">
        select * from user where age = #{age}
        <if test = "city_id != null">
            and city_id=#{city_id}
        </if>
    </select>
 

我们添加代码进行验证

    @Test
    public  void testSelectUserByIdLike() {
        HashMap<String, Integer> paraMap = new HashMap<>();
        paraMap.put("age", 28);
        paraMap.put("city_id", 3);
        User user = userMapper.selectUserByIdLike(paraMap);
        System.out.println("name: " + user.getName() + 
        " age: " + user.getAge()+" city_id: " + user.getCity_id());
    }
 

这里参数中传入age=28, city_id=3, 最终我们成功选到了所需的数据,说明if语句成功添加了相应的条件

注意和Java不同,在 Mybaits 中,只有 if 标签,并没有 else 标签,我们可以使用 chose when otherwise 代替

<choose>
    <when test="">
        //...
    </when>
    <otherwise>
        //...
    </otherwise>
</choose>
 

不想使用所有的条件语句,而只想从中择其一二。针对这种情况,MyBatis提供了choose标签,它有点像Java中的switch语句

<select id="selectUserByIdChoose"  parameterType="hashmap"  
        resultType="com.cjl.chapter6.model.User">
        select * from user where age = #{age}
        <choose>
            <when test = "city_id != null">
                and city_id=#{city_id}
            </when>
            <otherwise>
                and id = 1
            </otherwise>
        </choose>
    </select>
 

我们添加第一个测试方法

    @Test
    public void testSelectUserByIdChoose1() {
        HashMap<String, Integer> paraMap = new HashMap<>();
        paraMap.put("age", 20);
        User user = userMapper.selectUserByIdChoose(paraMap);
        System.out.println("name: " + user.getName() + 
            " age: " + user.getAge()+" city_id: " + user.getCity_id());
    }
 

执行输出,此时匹配到了and id = 1

然后我们添加第2个测试方法

   @Test
    public void testSelectUserByIdChoose2() {
        HashMap<String, Integer> paraMap = new HashMap<>();
        paraMap.put("age", 28);
        paraMap.put("city_id", 3);
        User user = userMapper.selectUserByIdChoose(paraMap);
        System.out.println("name: " + user.getName() + 
            " age: " + user.getAge()+" city_id: " + user.getCity_id());
    }
 

执行输出,成功匹配到了and city_id=3

where标签

where标签主要用于多个if语句的处理,例如假如有以下语句,所有的条件都在if中

    <select id="selectUserByIdLike"  parameterType="hashmap"  
        resultType="com.cjl.chapter6.model.User">
        select * from user where 
        <if test  = "age !=null ">
            and age = #{age}
        </if>
        <if test = "city_id != null">
            and city_id=#{city_id}
        </if>
    </select>
 

如果调用时age和city_id 都没有传入,那会生成

select * from user where 
 

这条语句显然不符合sql语法。但是where语句可以智能处理。where 标签知道只有在一个以上的if条件有值的情况下才去插入"WHERE"子句。而且,若最后的内容是"AND"或"OR"开头的,where 标签也知道如何将他们去除。

    <select id="selectUserByWhere"  parameterType="hashmap"  
       resultType="com.cjl.chapter6.model.User">
        select * from user 
        <where>
            <if test  = "age !=null ">
                age = #{age}
            </if>
            <if test = "city_id != null">
                and city_id=#{city_id}
            </if>           
        </where>
    </select>
然后在UserMapper.java中添加
List<User>  selectUserByWhere(HashMap<String , Integer> map);
 

然后添加测试代码

    @Test
    public void testSelectUserByWhere()
    {
        HashMap<String, Integer> paraMap = new HashMap<>();
        //参数是空的
        List<User> users = userMapper.selectUserByWhere(paraMap);
        for (User user : users) {
            System.out.println("name:" + user.getName() + ", 
            age:" +user.getAge() + ", city_id:" + user.getCity_id());
        }
    }
 

执行测试代码输出,说明在参数为空时执行了select * from user语句

trim标签

在动态生成sql语句的时候,经常会多添加或者少添加某些前缀比如and,逗号等等,这个时候可以通过 trim 标签来进行动态修改,其基本使用方法为

<trim prefix="" suffix="" suffixOverrides="" prefixOverrides=""></trim>
 

trim标签有4个属性:

  • prefix:当trim元素内包含内容时,会给内容增加prefix指定的前缀。

  • prefixOverrides:当trim元素内包含内容时,会把内容中匹配的前缀字符串去掉。

  • suffix:当trim元素内包含内容时,会给内容增加suffix指定的后缀。

  • suffixOverrides:当trim元素内包含内容时,会把内容中匹配的后缀字符串去掉。

下面以prefix和prefixOverrides为示例介绍

    <select id="selectUserUsePrefix"  parameterType="hashmap"  
       resultType="com.cjl.chapter6.model.User">
        select * from user
        <trim prefix="where">
            age = #{age} and city_id=#{city_id}
        </trim> 
    </select>
 

在trim包含的字段外会被添加where字符串

select * from user where age=#{age} and city_id=#{city_id}
 

再看prefixOverrides用法,假设有如下sql模板

    <select id="selectUserByPrefixOverrides"  parameterType="hashmap"
            resultType="com.cjl.chapter6.model.User">
        select * from user
        <trim prefix="where">
            <if test  = "age !=null ">
                age = #{age}
            </if>
            <if test = "city_id != null">
                and city_id=#{city_id}
            </if>
        </trim>
    </select>
 

如果 age 为空city_id不为空,则最终会生成

select * from user where and city_id = ?
 

多出来的and将会导致sql语法错误,这时可以通过添加profixOverrides,自动去除字符串头部的and

    <select id="selectUserByPrefixOverrides"  parameterType="hashmap"
            resultType="com.cjl.chapter6.model.User">
        select * from user
        <trim prefix="where"  prefixOverrides="and">
            <if test  = "age !=null ">
                age = #{age}
            </if>
            <if test = "city_id != null">
                and city_id=#{city_id}
            </if>
        </trim>
    </select>

foreach标签

很多时候我们需要根据一组符合条件的输入,查找相应符合条件的输出,例如我们需要筛选查找id在{1-5}范围内的用户。这个时候我们就需要使用foreach标签构造查找语句了。foreach 标签主要用在构建 in 条件中,它可以在 SQL 语句中遍历一个集合。先简单举个例子

<select id="selectByIds" resultType="cn.mybatis.domain.User">
    select * from user where id in
    <foreach collection="list" index="index" item="item" 
        open="(" separator="," close=")">
        #{item}
    </foreach>
</select>
 

foreach标签将会把输出的id组合成in(1,2,3,4,5),整个生成的sql语句为

select * from user where id in(1,2,3,4,5)
 

foreach 标签的属性主要有 collection,item,index,open,separator,close。其含义如下所示:

  • collection:遍历的对象。当遍历对象是List、Array对象时,collection属性值分别默认用"list"、"array"代替,而Map对象没有默认的属性值。
  • item:表示在遍历过程中每个元素的别名。该参数为必选项。
  • index:在list、array中,index为元素的序号索引,但是在Map中,index为遍历元素的key值。该参数为可选项。
  • open:遍历集合时的开始符号,通常与 close=")" 搭配使用。使用场景为 IN()、values()时。该参数为可选项。
  • separator:元素之间的分隔符,类比在 IN() 的时候,separator=",",最终所有遍历的元素将会以设定的逗号符号隔开。该参数为可选项。
  • close:遍历集合时的结束符号,通常与 open="(" 搭配使用。该参数为可选项。

collection 属性值的三种情况

如果参数类型为List时,collection的默认属性值为list

如果参数类型为Array时,collection的默认属性值为array;

如果传入的参数类型为Map时,collection的属性值可为下面三种情况:

  • 遍历 map.keys
  • 遍历 map.values
  • 遍历 map.entrySet()

set标签

set 标签可以被用于动态包含需要更新的列,智能移除追加到条件末尾多余的逗号

   <select id="selectUserSet"  parameterType="hashmap"
            resultType="com.cjl.chapter6.model.User">
        update user
        <set>
            <if test  = "age !=null ">
                age = #{age},
            </if>
            
            <if test = "name != null">
                name = #{name},
            </if>
            
            <if test = "city_id != null">
                city_id=#{city_id}
            </if>
        </set>
       where id = #{id}
    </select>
 

如果某个传入的参数不为null,则会生成该字段语句,同时set会自动处理末尾的逗号。

SQL结果映射相关

HashMap自动映射

MyBatis可以对我们的查询结果进行自动映射,通常自动映射的原则是结果中key的名字与数据库中的字段一致

这种HashMap自动映射通常在我们查询部分字段时使用

    <select id="selectNames" parameterType="hashmap" resultType="hashmap">
        select name, age, city_id from user where id = #{id}
    </select>
 

resultType映射

上面的例子里resultType使用了hashmap,其实如果使用其他POJO类MyBatis也可以根据字段名一致的原则自动映射结果,这在我们使用类的形式查询时非常适用。

    <select id="selectPojo"     resultType="com.cjl.chapter6.model.User">
        select  * from user where id = #{id}
    </select>

resultMap映射

resultType能满足我们日常开发的绝大部分场景,但是使用resultType需要遵守数据库字段和属性字段名称一致的映射规则,不一致就得使用别名,如果开发时数据库由其他团队负责或者维护一段前人传递下来的代码时往往会遇到不一致的情况,这样如果使用别名就使得SQL语句变得复杂。所以MyBatis提供了resultMap标签定义SQL查询结果字段与实体属性的映射关系。下面我们展示一个简单的例子

    <resultMap id="BaseResultMap" type="com.cjl.chapter6.model.User">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="name" property="name" jdbcType="VARCHAR"/>
        <result column="age" property="age" jdbcType="INTEGER"/>
        <result column="city_id" property="city_id" jdbcType="INTEGER"/>
    </resultMap>
这里使用resultMap标签定义一个映射关系,接下来我们使用它,注意这里我们就不再使用resultType了。
    <select id="selectOne" parameterType="Long" resultMap="BaseResultMap">
        SELECT
            *
        FROM user
        WHERE id = #{id}
    </select>
 

另外注意MyBatis中resultType和resultMap不能同时使用。resultMap是MyBatis的核心元素之一,它还可以通过collection标签和association标签配置关联关系,以应对多表查询出的复杂结果。

关联映射相关

一对一映射

association通常用来映射一对一的关系,比如在我们的简易旅游公司管理系统中规定一个用户User对应一个常住城市(City),一个用户只能属于一个常住城市。下面以用户和常住为例来演示association标签配置一对一关联关系。回顾前面一章,我们已经创建了一个城市表,具体sql如下

DROP TABLE IF EXISTS `city`;
CREATE TABLE `city` (
    `city_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '城市编号',
    `city_name` varchar(50) NOT NULL COMMENT '城市名',
    `country` varchar(50) NOT NULL COMMENT '国家',
    primary key (`city_id`)
)ENGINE = InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET = utf8 COMMENT '城市表';

INSERT INTO `city` VALUES (1, '北京', '中国');
INSERT INTO `city` VALUES (2, '上海', '中国');
INSERT INTO `city` VALUES (3, '杭州', '中国');
INSERT INTO `city` VALUES (4, '岘港', '越南');
INSERT INTO `city` VALUES (5, '曼谷', '泰国');
 

我们在model目录下,添加一个City类,添加代码

public class City { 
    private Integer cityId;
    private String cityName;
    private String country;
    
    public City() {

    }

    public City(String cityName, String country) {
        this.cityName = cityName;
        this.country = country;
    }

    //省略getter/ setter
 

然后我们在User.java中修改代码,添加一个City类的属性

public class User {
    private Long id;
    private String name;
    private int age;
    private int city_id;
    private City city;

    public User() {

    }

    public User(String name, int age, int city_id) {
        this.name = name;
        this.age = age;
        this.city_id = city_id;
    }

   //省略getter和setter
   ....
 

接下来我们实践一下一对一关联映射,我们在UserMapper.xml中添加一个select标签,实现根据id查询user,然后再根据当前user的city_id来查找对应的城市

    <select id="selectUserAndCity" parameterType="long" resultMap="UserAndCityMap">
        select u.id, u.name, u.age, u.city_id, c.city_name, c.city_id , c.country
        from user u left join city c on u.city_id = c.city_id where u.id = #{id}
    </select>
 

在UserMapper.java中添加接口方法

User selectUserAndCity(Long id);

随后在测试类中添加测试用例

    @Test
    public void testSelectUserAndCity() {
        User user = userMapper.selectUserAndCity(1L);
        System.out.println("name:" + user.getName()+" age: "+user.getAge()+
                " city name: " + user.getCity().getCityName() + 
       " country: " + user.getCity().getCountry());
    }
 

执行测试用例,输出结果,说明我们成功通过用户ID查询到了对应的城市名

一对多映射

前面我们了解了一对一映射,接下来我们继续了解一对多映射,很多时候我们会遇到一对多映射例如一个班级里面有多个学生,一个旅游城市里有多个景点。MyBatis使用collection标签来处理一对多映射。下面我们就用查询一个旅游城市里所有的景点信息来介绍一对多映射。首先在model目录添加2个java普通类

在Scenic中添加代码

public class Scenic {

    private Integer scenicId;

    private Integer cityId;

    private String scenicName;

    private Integer price;

    public Scenic() {
        
    }

    public Scenic(Integer cityId, String scenicName, Integer price) {
        this.cityId = cityId;
        this.scenicName = scenicName;
        this.price = price;
    }

    //省略getter / setter
   .....
 

在City中添加代码,注意City中有一个List<Scenic> scenics属性

public class City {

    private Integer cityId;

    private String cityName;

    private String country;

    private List<Scenic> scenics;

    public City() {

    }

    public City(String cityName, String country) {
        this.cityName = cityName;
        this.country = country;
    }


    public List<Scenic> getScenics(){
        return scenics;
    }

    public void setScenics(List<Scenic> scenics)
    {
        this.scenics = scenics;
    }
    //省略其余 getter / setter
    .....
 

然后在Mapper目录下添加CityMapper接口

在其中添加代码

import com.cjl.chapter6.model.City;
import org.springframework.stereotype.Component;

@Component
public interface CityMapper {
    City selectCityAndScenics(Long city_id);
}
 

然后在resources目录下的mapper目录下添加CityMapper.xml

在其中添加sql

<?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="com.cjl.chapter6.mapper.CityMapper">

    <resultMap id="CityAndScenicMap" type="com.cjl.chapter6.model.City">
        <id column="city_id" property="cityId" jdbcType="BIGINT"/>
        <result column="city_name" property="cityName" jdbcType="VARCHAR"/>
        <result column="country" property="country" jdbcType="VARCHAR"/>

        <collection property="scenics" ofType="com.cjl.chapter6.model.Scenic">
            <id column="scenic_id" property="scenicId" jdbcType="BIGINT"/>
            <result column="city_id" property="cityId" jdbcType="VARCHAR"/>
            <result column="scenic_name" property="scenicName" jdbcType="VARCHAR"/>
            <result column="price" property="price" jdbcType="INTEGER"/>
        </collection>

    </resultMap>

    <select id="selectCityAndScenics" parameterType="long" resultMap="CityAndScenicMap">
        select c.city_name, c.city_id , c.country, s.scenic_id, s.city_id, s.scenic_name, s.price
        from city c left join scenic s on c.city_id = s.city_id where c.city_id = #{city_id}
    </select>

</mapper>
 

其中CityAndScenicMap中通过collection关键字实现结果中一对多的映射。id为selectCityAndScenics的select语句通过left join查询语句实现对city表和scenic表的联合查询。

完成了上面的准备工作我们编写测试用例

    @Autowired
    private CityMapper cityMapper;

    @Test
    public void testSelectCityAndScenics() {
        City city = cityMapper.selectCityAndScenics(1L);
        System.out.println("所选城市信息: " + "城市名: "+ city.getCityName());
        System.out.println("所选城市景点信息: ");
        for(Scenic scenic : city.getScenics()) {
            System.out.println("景点名: " + scenic.getScenicName() +
              " 景点价格: " + scenic.getPrice() +
                    " 景点编号: " + scenic.getScenicId());
        }
    }
 

运行用例,可以看到输出中我们实现了一对多的查询

到此MyBatis的基本语法就介绍完毕了,在前面的过程中我们都是手工编写java类和映射XML文件,这种方式效率低下,容易出错,MyBatis已经考虑了这个问题,提供了自动生成工具,我们在下一个章节具体介绍

项目源码

可以访问:GitHub - qwdzq/springboot: spring boot 入门

我是陈小房,致力于帮助初级程序员快速成长,愿编程入门更简单!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈小房

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值