MyBatis(二):复杂映射,配置深入

目录

1. MyBatis 高级查询

1.1 ResultMap 属性

1.2 多条件查询

方式一

方式二

方式三(推荐)

1.3 模糊查询

方式一

方式二

${} 与 #{} 区别

2. MyBatis 映射文件深入

2.1 返回主键

2.1.1 useGeneratedKeys

2.1.2 selectKey

2.2 动态 SQL

2.2.1 动态 SQL 之 if

2.2.2 动态 SQL 之 set

2.2.3 动态 SQL 之  foreach

2.2.4 动态 SQL 之  choose

 2.2.5 动态 SQL 之  trim

2.3 SQL 片段

2.4 小结

3. MyBatis 核心配置文件深入

3.1 plugins 标签

3.2 小结

4. MyBatis 多表查询

4.1 数据库表关系介绍

4.2 一对一(多对一)

4.3 一对多

4.4 多对多

4.5 小结

5. MyBatis 嵌套查询

5.1 什么是嵌套查询

5.2 一对一嵌套查询

5.3 一对多嵌套查询

5.4 多对多嵌套查询

5.5 小结


1. MyBatis 高级查询

1.1 ResultMap 属性

建立对象关系映射

  • resultType: 如果实体的属性名与表中字段名一致,将查询结果自动封装到实体类中
  • resutlMap:如果实体的属性名与表中字段名不一致,可以使用 resutlMap 实现手动封装到实体类中

编写 UserMapper 接口

List<User> findAllResultMap();

编写 UserMapper.xml


<!-- id: 标签的唯一标识,type: 封装后实体类型 -->
<resultMap id="userResultMap" type="com.zm.domain.User">
    <!-- 手动配置映射关系 -->
    <!-- id: 用来配置主键 。表中主键字段封装 
         column="id" :表中的字段名 
         property="id" :user实体的属性名-->
    <id property="id" column="uid"></id>
    <!-- result: 表中普通字段的封装 -->
    <result property="username" column="NAME"></result>
    <result property="birthday" column="birthday"></result>
    <result property="sex" column="sex"></result>
    <result property="address" column="address"></result>
</resultMap>

<!-- 查询所有用户 -->
<select id="findAllResultMap" resultMap="userResultMap">
    select * from `user`
</select>

代码测试

@Before
public void init() throws IOException {
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("sqlMapConfig.xml"));
    sqlSession = sqlSessionFactory.openSession();
    mapper = sqlSession.getMapper(UserMapper.class);
}

@After
public void close() {
    sqlSession.close();
}
@Test
public void testFindAllResultMap() {
    List<User> allResultMap = mapper.findAllResultMap();
    for (User user : allResultMap) {
        System.out.println(user);
    }
}

1.2 多条件查询

需求
根据id和username查询user表

方式一

使用 #{arg0}-#{argn}或者#{param1}-#{paramn} 获取参数

UserMapper 接口

List<User> findByIdAndUsername1(int id, String username);

UserMapper.xml

<select id="findByIdAndUsername1" resultMap="userResultMap">
    <!-- select * from `user` where id = #{arg0} and username = #{arg1}-->
    select * from `user` where id = #{param1} and username = #{param2}
</select>

测试

@Test
public void test3() throws IOException {
    mapper = sqlSession.getMapper(UserMapper.class);
    List<User> users = mapper.findByIdAndUsername1(3, "zm");
    for (User user : users) {
        System.out.println(user);
    }
}

方式二

使用注解,引入@Param()注解获取参数

UserMapper 接口

public List<User> findByIdAndUsername2(@Param("id") int id, @Param("username") String username);

UserMapper.xml

<select id="findByIdAndUsername2" resultMap="userResultMap" >

     <!-- 注意:此处{}里面的值要和上面的@Param注解里面的value 值保持一致-->
    select * from `user` where id = #{id} and username = #{username}
</select>

测试

@Test
public void test4() throws IOException {
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> users = mapper.findByIdAndUsername2(4, "zm");
    for (User user : users) {
        System.out.println(user);
    }
}

方式三(推荐)

使用 POJO 对象传递参数

UserMapper 接口

List<User> findByIdAndUsername3(User user);

UserMapper.xml

<select id="findByIdAndUsername3" resultMap="userResultMap" parameterType="com.zm.domain.User">
    select * from `user` where id = #{id} and username = #{username}
</select>

测试

@Test
public void test5() throws IOException {
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    User user1 = new User();
    user1.setId(1);
    user1.setUsername("zm");
    List<User> users = mapper.findByIdAndUsername3(user1);
    for (User user : users) {
        System.out.println(user);
    }
}

1.3 模糊查询

方式一

UserMapper 接口

public List<User> findByUsername1(String username);

UserMapper.xml

<select id="findByUsername1" resultMap="userResultMap" parameterType="string">
    <!-- #{}在mybatis中是占位符,引用参数值的时候会自动添加单引号 -->
    select * from `user` where username like #{username}
</select>

测试

@Test
public void test6() throws IOException {
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    List<User> users = mapper.findByUsername1("%zm%");
    for (User user : users) {
        System.out.println(user);
    }
}

方式二

UserMapper 接口

List<User> findByUsername2(String username);

UserMapper.xml

<select id="findByUsername2" resultMap="userResultMap" parameterType="string">
    <!--parameterType 是基本数据类型或者 String 的时候,
          ${} 里面的值只能写 value,
          ${}: sql 原样拼接 
    -->
    select * from `user` where username like '${value}'
</select>

测试

@Test
public void test7() {
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    List<User> users = mapper.findByUsername2("%zm%");
    for (User user : users) {
        System.out.println(user);
    }
}

${} 与 #{} 区别

#{}表示一个占位符号

  • 通过#{}可以实现 preparedStatement 向占位符中设置值,自动进行 Java 类型和 JDBC 类型转换,#{}可以有效防止 SQL 注入。
  • #{}可以接收简单类型值或 POJO 属性值。
  • 如果 parameterType 传输单个简单类型值,#{}括号中名称随便写。

${} 表示拼接 SQL 串

  • 通过 ${} 可以将 parameterType 传入的内容拼接在 SQL 中且不进行 JDBC 类型转换,会出现 SQL 注入问题。
  • ${} 可以接收简单类型值或 POJO 属性值。
  • 如果 parameterType 传输单个简单类型值,${}括号中只能是 value。

2. MyBatis 映射文件深入

2.1 返回主键

应用场景:向数据库插入一条记录后,希望能立即拿到这条记录在数据库中的主键值。

2.1.1 useGeneratedKeys

注意:只适用于主键自增的数据库,MySQL 和 SQL Server 支持,Oracle 不行。

UserMapper 接口

void saveUser1(User user);

UserMapper.xml

<!--
        useGeneratedKeys="true": 声明返回主键
        keyProperty="id":把返回主键的值,封装到实体中的那个属性上
    -->
<insert id="saveUser1" parameterType="user" useGeneratedKeys="true" keyProperty="id">
    insert into `user` (username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address})
</insert>

测试

@Test
public void test8() throws IOException {
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    User user = new User();
    user.setUsername("某冰冰");
    user.setBirthday(new Date());
    user.setAddress("北京昌平");
    user.setSex("女");

    System.out.println(user);
    mapper.saveUser1(user);
    System.out.println(user);

    sqlSession.commit();
}

2.1.2 selectKey

UserMapper 接口

public void saveUser2(User user);

UserMapper.xml

<!--
        selectKey : 适用范围更广,支持所有类型的数据库
            order="AFTER"    : 设置在 sql 语句执行后,执行此语句
            keyColumn="id"   : 指定主键对应列名
            keyProperty="id" : 把返回主键的值,封装到实体中的那个属性上
            resultType="int" : 指定主键类型
    -->
<insert id="saveUser2" parameterType="user" >
    <selectKey order="AFTER" keyColumn="id" keyProperty="id" resultType="int">
        SELECT LAST_INSERT_ID();
    </selectKey>
    insert into `user` (username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address})
</insert>

测试

@Test
public void test9() {
    User user = new User();
    user.setUsername("张三");
    user.setBirthday(new Date());
    user.setAddress("北京昌平");
    user.setSex("女");

    System.out.println(user);
    mapper.saveUser2(user);
    System.out.println(user);

    sqlSession.commit();
}

2.2 动态 SQL

应用场景:根据不同的条件,来执行不同的 SQL 语句的时候,需要用到动态 SQL。

2.2.1 动态 SQL 之 if

根据 id 和 username 查询,但是不确定两个都有值。

UserMapper 接口

public List<User> findByIdAndUsernameIf(User user);

UserMapper.xml

<!-- test 里面写的就是表达式
        <where>: 相当于 where 1 = 1,但是如果没有条件的话,不会拼接上 where 关键字
        所以当有条件的时候下面的sql语句就相当于:select * from `user` where 1 = 1 and id =#{id} 
                                             and username = {username};
    -->
<select id="findByIdAndUsernameIf" parameterType="user" resultType="com.zm.domain.User">
    select * from `user`
    <where>
        <if test="id != null">
            and id = #{id}
        </if>
        <if test="username != null">
            and username = #{username}
        </if>
    </where>
</select>

测试

@Test
public void test10() throws IOException {
    User user = new User();
    user.setId(7);
    user.setUsername("张三");

    List<User> users = mapper.findByIdAndUsernameIf(user);
    for (User user1 : users) {
        System.out.println(user1);
    }
}

注:if标签一般用于非空验证,如上例,若id为空,if标签里的代码,将不会执行,反之,则会执行。 

2.2.2 动态 SQL 之 set

set标签功能和where标签差不多,set标签代替了sql中set关键字,set标签可以自动去除sql中的多余的“,”。trim标签也可以实现set标签的功能。

动态更新 user 表数据,如果该属性有值就更新,没有值不做处理。

UserMapper 接口

int updateIf(User user);

UserMapper.xml

<!-- <set> : 在更新的时候,会自动添加 set 关键字,还会去掉最后一个条件的逗号 -->
<update id="updateIf" parameterType="user">
    update `user`
    <set>
        <if test="username != null">
            username = #{username},
        </if>
        <if test="birthday != null">
            birthday = #{birthday},
        </if>
        <if test="sex != null">
            sex = #{sex},
        </if>
        <if test="address != null">
            address = #{address},
        </if>
    </set>
    where id = #{id}
</update>

测试

@Test
public void test11() throws IOException {
    User user = new User();
    user.setId(1);
    user.setUsername("李四");
    user.setAddress("广东广州");
    user.setBirthday(new Date());

    if (mapper.updateIf(user) > 0) {
        System.out.println("更新成功");
    } else {
        System.out.println("更新失败");
    }

    sqlSession.commit();
}

2.2.3 动态 SQL 之  foreach

foreach 标签主要用于构建 in 条件,可在 sql 中对集合进行迭代。也常用到批量删除、添加等操作中。

例如:像 select * from user where id in (1,2,3);  这样的语句中,传入的参数部分就要用foreach遍历来完成。

<foreach> 标签用于遍历集合,它的属性:

  • collection - 代表要遍历的集合元素。collection 属性的值有三个分别是 list、array、map 三种,分别对应的参数类型为:List、数组、map 集合。
  • open - 代表语句的开始部分
  • close - 代表结束部分
  • item - 表示在迭代过程中每一个元素的别名。注意是变量名(并非实际的值)
  • sperator - 代表分隔符,表示迭代时每个元素之间以什么分隔
  • index - 表示在迭代过程中每次迭代到的位置(下标)

注意:

foreach时,Collection属性值的三种情况:
如果传入的参数类型为List时,collection的默认属性值为list,同样可以使用@Param注解自定义keyName;
如果传入的参数类型为array时,collection的默认属性值为array,同样可以使用@Param注解自定义keyName;
如果传入的参数类型为Map时,collection的属性值可为三种情况:(1.遍历map.keys;2.遍历map.values;3.遍历map.entrySet())

集合
UserMapper 接口

List<User> findByList(List<Integer> ids);

UserMapper.xml

<sql id="selectUser">
    select * from `user`
</sql>

<!-- 动态 sql 的 foreach 标签:多值查询:根据多个 id 值查询用户 -->
<!--
        collection : 代表要遍历的集合元素,通常写 collection 或者 list
        open       : 代表语句的开始部分
        close      : 代表语句的结束部分
        item       : 代表遍历集合中的每个元素,生成的变量名,这里的变量名就是id
        separator  : 分隔符
        index      : 表示在迭代过程中每次迭代到的位置(下标)
    -->
<select id="findByList" parameterType="list" resultType="user">
    <include refid="selectUser"/>
    <where>
        <foreach collection="collection" index="index" open="id in (" close=")" item="id" separator=",">
            #{id}
        </foreach>
    </where>
</select>

测试

@Test
public void test12() {
    List<Integer> ids = new ArrayList<>();
    ids.add(1);
    ids.add(6);
    ids.add(2);

    List<User> users = mapper.findByList(ids);
    for (User user : users) {
        System.out.println(user);
    }
}

数组
UserMapper 接口

List<User> findByArray(Integer[] ids);

UserMapper.xml

<!--动态 sql 的 foreach 标签:多值查询:根据多个 id 值查询用户-->
<select id="findByArray" parameterType="int" resultType="user">
    <include refid="selectUser"/>
    <where>
        <foreach collection="array" open="id in (" close=")" item="id" separator=",">
            #{id}
        </foreach>
    </where>
</select>

测试

@Test
public void test13() {
    Integer[] ids = {2,6,7};

    List<User> byArray = mapper.findByArray(ids);
    for (User user : byArray) {
        System.out.println(user);
    }
}

2.2.4 动态 SQL 之  choose

choose(when,otherwise)标签相当于switch(case,default) ,如下例,若title 为空,when标签里的代码,将不会执行,默认执行otherwise标签里面的代码。

<select id="queryBy" resultType="Blog">
  SELECT * FROM BLOG WHERE 1=1
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <otherwise>
      AND id= 1
    </otherwise>
  </choose>
</select>

 2.2.5 动态 SQL 之  trim

trim标记是一个格式化的标记,主要用于拼接sql的条件语句(前缀或后缀的添加或忽略),可以完成set或者是where标记的功能。

trim属性主要有以下四个

prefix:在trim标签内sql语句加上前缀
suffix:在trim标签内sql语句加上后缀
prefixOverrides:指定去除多余的前缀内容,如:prefixOverrides=“AND | OR”,去除trim标签内sql语句多余的前缀"and"或者"or"。
suffixOverrides:指定去除多余的后缀内容。
 

这里拿select举例:

<select id="selectByNameOrHobby" resultMap="BaseResultMap">
    select * from student 
    <trim prefix="WHERE" prefixOverrides="AND | OR">
        <if test="name != null and name.length()>0"> 
            AND name=#{name}
        </if>
        <if test="hobby != null and hobby.length()>0">
            AND hobby=#{hobby}
        </if>
    </trim>
</select>

如果name和hobby的值都不为空的话,会执行如下语句

  select * from user WHERE /*and*/ name = ‘xx’ and hobby= ‘xx’

2.3 SQL 片段

应用场景:映射文件中可将重复的 SQL 提取出来,使用时用 include 引用即可,最终达到 SQL 重用的目的

<sql id="selectUser">
    select * from user
</sql>

2.4 小结

MyBatis 映射文件配置:

<select>     : 查询
<insert>     : 插入
<update>     : 修改
<delete>     : 删除
<selectKey>  : 返回主键
<where>      : where条件
<if>         : if判断
<foreach>    : for循环
<set>        : set 设置
<sql>        : sql 片段抽取

3. MyBatis 核心配置文件深入

3.1 plugins 标签

MyBatis 可以使用第三方的插件来对功能进行扩展,分页助手 PageHelper 是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据。

开发步骤:
①导入通用 PageHelper 的坐标
②在 MyBatis 核心配置文件中配置 Page Helper 插件
③测试分页数据获取

1. 导入通用 PageHelper 坐标

<!-- 分页助手 -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>3.7.5</version>
</dependency>
<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>0.9.1</version>
</dependency>

2. 在 MyBatis 核心配置文件中配置 PageHelper 插件

<plugins>
    <plugin interceptor="com.github.pagehelper.PageHelper">
        <!--dialect: 指定方言。MySQL 用 limit 来实现分页 -->
        <property name="dialect" value="mysql"/>
    </plugin>
</plugins>

3. 测试分页代码实现

@Test
public void test14() throws IOException {
    // 设置分页参数
    // 参数 1: 当前页
    // 参数 2: 每页显示的条数
    PageHelper.startPage(1, 2);

    List<User> users = mapper.findAllResultMap();
    for (User user : users) {
        System.out.println(user);
    }

    // 获取分页相关的其他参数
    PageInfo<User> pageInfo = new PageInfo<User>(users);
    System.out.println("总条数:"+ pageInfo.getTotal());
    System.out.println("总页数:"+ pageInfo.getPages());
    System.out.println("是否是第一页:"+ pageInfo.isIsFirstPage());
}

3.2 小结

MyBatis核心配置文件常用标签:

  1. properties标签:该标签可以加载外部的 properties 文件
  2. typeAliases 标签:设置类型别名
  3. environments 标签:数据源环境配置标签
  4. plugins 标签:配置 MyBatis 的插件

4. MyBatis 多表查询

4.1 数据库表关系介绍

关系型数据库表关系

  • 一对一
  • 一对多
  • 多对多

举例
人和身份证号就是一对一:一个人只能有一个身份证号,一个身份证号只能属于一个人
用户和订单就是一对多,订单和用户就是多对一:一个用户可以下多个订单,多个订单属于同一个用户
学生和课程就是多对多:一个学生可以选修多门课程,一个课程可以被多个学生选修
特例:一个订单只从属于一个用户,所以 MyBatis 将多对一看成了一对一

案例环境准备

USE `mybatis_db`;

DROP TABLE IF EXISTS `orders`;

CREATE TABLE `orders` (
    `id` INT (11) NOT NULL AUTO_INCREMENT
    ,`ordertime` VARCHAR(255) DEFAULT NULL
    ,`total` DOUBLE DEFAULT NULL
    ,`uid` INT (11) DEFAULT NULL
    ,PRIMARY KEY (`id`)
    ,KEY `uid`(`uid`)
    ,CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `user`(`id`)
    ) ENGINE = INNODB AUTO_INCREMENT = 4 DEFAULT CHARSET = utf8;

-- ----------------------------
-- Records of orders
-- ----------------------------
INSERT INTO `orders`
VALUES (
    '1'
    ,'2020-12-12'
    ,'3000'
    ,'1'
    );

INSERT INTO `orders`
VALUES (
    '2'
    ,'2020-12-12'
    ,'4000'
    ,'1'
    );

INSERT INTO `orders`
VALUES (
    '3'
    ,'2020-12-12'
    ,'5000'
    ,'2'
    );

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
    `id` INT (11) NOT NULL AUTO_INCREMENT
    ,`rolename` VARCHAR(255) DEFAULT NULL
    ,`roleDesc` VARCHAR(255) DEFAULT NULL
    ,PRIMARY KEY (`id`)
    ) ENGINE = INNODB AUTO_INCREMENT = 3 DEFAULT CHARSET = utf8;

-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role`
VALUES (
    '1'
    ,'CTO'
    ,'CTO'
    );

INSERT INTO `sys_role`
VALUES (
    '2'
    ,'CEO'
    ,'CEO'
    );

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
    `userid` INT (11) NOT NULL
    ,`roleid` INT (11) NOT NULL
    ,PRIMARY KEY (
        `userid`
        ,`roleid`
        )
    ,KEY `roleid`(`roleid`)
    ,CONSTRAINT `sys_user_role_ibfk_1` FOREIGN KEY (`userid`) REFERENCES `sys_role`(`id`)
    ,CONSTRAINT `sys_user_role_ibfk_2` FOREIGN KEY (`roleid`) REFERENCES `user`(`id`)
    ) ENGINE = INNODB DEFAULT CHARSET = utf8;

-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role`
VALUES (
    '1'
    ,'1'
    );

INSERT INTO `sys_user_role`
VALUES (
    '2'
    ,'1'
    );

INSERT INTO `sys_user_role`
VALUES (
    '1'
    ,'2'
    );

INSERT INTO `sys_user_role`
VALUES (
    '2'
    ,'2'
    );

4.2 一对一(多对一)

介绍

一对一查询模型
用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户。

一对一查询的需求:查询所有订单,与此同时查询出每个订单所属的用户。

从订单角度来看:一个订单 orders 对一个用户 user

一对一查询语句

SELECT * FROM orders o LEFT JOIN `user` u ON o.`uid`=u.`id`

代码实现

Orders 实体

public class Orders {

    private Integer id;
    private String ordertime;
    private Double total;
    private Integer uid;

    /**
     * 表示当前订单属于那个用户 association
     */
    private User user;
 
    // getter and setter ...
    // toString
}

OrderMapper 接口

public interface OrderMapper {
    List<Orders> findAllWithUser();
}

OrderMapper.xml 映射

<!--
        association : 在进行一对一关联查询配置时,使用 association 标签进行关联
            property="user" :要封装实体的属性名
            javaType="com.zm.domain.User" 要封装的实体的属性类型
    -->
<resultMap id="orderMap" type="com.zm.domain.Orders">
    <id property="id" column="id"/>
    <result property="ordertime" column="ordertime"/>
    <result property="total" column="total"/>
    <result property="uid" column="uid"/>
    <association property="user" javaType="com.zm.domain.User">
        <id property="id" column="uid"></id>
        <result property="username" column="username"></result>
        <result property="birthday" column="birthday"></result>
        <result property="sex" column="sex"></result>
        <result property="address" column="address"></result>
    </association>
</resultMap>

<select id="findAllWithUser" resultMap="orderMap">
    SELECT * FROM orders o LEFT JOIN `user` u ON o.uid = u.id
</select>

测试代码

public class MyBatisTest {
    SqlSession sqlSession;
    OrderMapper mapper;

    @Before
    public void init() throws IOException {
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("sqlMapConfig.xml"));
        sqlSession = sqlSessionFactory.openSession();
        mapper = sqlSession.getMapper(OrderMapper.class);
    }

    @After
    public void close() {
        sqlSession.close();
    }

    @Test
    public void test1() {
        List<Orders> orders = mapper.findAllWithUser();

        for (Orders order : orders) {
            System.out.println(order);
        }
    }
}

4.3 一对多

介绍

一对多查询模型
用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户。

一对多查询的需求:查询所有用户,与此同时查询出该用户具有的订单。

从用户的角度看:一个用户 user 对多个订单 orders。

一对多查询语句

SELECT u.*, o.id oid, o.ordertime, o.total, o.uid FROM orders o RIGHT JOIN `user` u ON o.uid = u.id 

代码实现

User 实体

public class User {

    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    /**
     * 表示多方关系: 集合 - 代表了当前用户所具有的订单列表 collection
     */
    private List<Orders> ordersList;
    
    // getter and setter ...
    // toString
}

UserMapper 接口

public interface UserMapper {
    public List<User> findAllWithOrder();
}

UserMapper.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="com.zm.mapper.UserMapper">

    <!-- 一对多关联查询:查询所有的用户,同时还要查询出每个用户所关联的订单信息 -->
    <!--
        collection : 一对多使用 collection 标签进行关联
    -->
    <resultMap id="userMap" type="com.zm.domain.User">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="birthday" column="birthday"></result>
        <result property="sex" column="sex"></result>
        <result property="address" column="address"></result>
        <collection property="ordersList" ofType="com.zm.domain.Orders">
            <id property="id" column="oid"></id>
            <result property="ordertime" column="ordertime"/>
            <result property="total" column="total"/>
            <result property="uid" column="uid"/>
        </collection>
    </resultMap>

    <select id="findAllWithOrder"  resultMap="userMap">
       SELECT u.*, o.id oid, o.ordertime, o.total, o.uid FROM orders o RIGHT JOIN `user` u ON o.uid = u.id
    </select>

</mapper>

测试代码

@Test
public void test2() throws IOException {
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    List<User> userList = userMapper.findAllWithOrder();

    for (User user : userList) {
        System.out.println(user);
    }
}

4.4 多对多

介绍

多对多查询的模型
用户表和角色表的关系为,一个用户有多个角色,一个角色被多个用户使用。

多对多查询的需求:查询所有用户同时查询出该用户的所有角色。

用户表 - user,中间表 - user_role,角色表 - role。

多对多查询语句

SELECT
  u.*, r.`id` rid, r.`rolename`, r.`roleDesc`
FROM
  `user` u
  LEFT JOIN `sys_user_role` ur
    ON u.`id` = ur.`userid`
  LEFT JOIN `sys_role` r
    ON ur.`roleid` = r.`id`;

代码实现

User 和 Role 实体

public class User {

    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    private List<Role> roleList;
    
    ...
}



public class Role {

    private Integer id;
    private String rolename;
    private String roleDesc;
    
    ...
}

UserMapper 接口

List<User> findAllWithRole();

UserMapper.xml 映射

<resultMap id="userRoleMap" type="user">
    <id property="id" column="id"/>
    <result property="username" column="username"></result>
    <result property="birthday" column="birthday"></result>
    <result property="sex" column="sex"></result>
    <result property="address" column="address"></result>

    <collection property="roleList" ofType="role">
        <id column="rid" property="id"></id>
        <result column="rolename" property="rolename"></result>
        <result column="roleDesc" property="roleDesc"></result>
    </collection>
</resultMap>

<select id="findAllWithRole" resultMap="userRoleMap">
    SELECT u.*, r.`id` rid, r.`rolename`, r.`roleDesc` FROM `user` u
    LEFT JOIN `sys_user_role` ur ON u.`id` = ur.`userid`
    LEFT JOIN `sys_role` r ON ur.`roleid` = r.`id`
</select>

测试代码

@Test
public void test3() {
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    List<User> allWithRole = userMapper.findAllWithRole();

    for (User user : allWithRole) {
        System.out.println(user);
    }
}

4.5 小结

MyBatis 多表配置方式:

  • 多对一(一对一)配置:使用 <resultMap> + <association> 做配置
  • 一对多配置:使用 <resultMap> + <collection> 做配置
  • 多对多配置:使用 <resultMap> + <collection> 做配置;多对多的配置跟一对多很相似,难度在于 SQL 语句的编写。

5. MyBatis 嵌套查询

5.1 什么是嵌套查询

嵌套查询就是将原来多表查询中的联合查询语句拆成单个表的查询,再使用 MyBatis 的语法嵌套在一起。

联合查询

SELECT * FROM orders o LEFT JOIN `user` u ON o.uid=u.id

嵌套查询 - 使用 MyBatis,将以下二步嵌套起来:

-- 先查询订单
SELECT * FROM orders
-- 再根据订单 uid 外键,查询用户
SELECT * FROM user WHERE id = # { 根据订单查询的 uid }

5.2 一对一嵌套查询

介绍

需求:查询一个订单,与此同时查询出该订单所属的用户

一对一查询语句

-- 先查询订单
SELECT * FROM orders;
-- 再根据订单 uid 外键,查询用户
SELECT * FROM `user` WHERE id = #{ 订单的 uid };

代码实现

OrderMapper 接口

List<Orders> findAllWithUser2();

OrderMapper.xml 映射

<resultMap id="orderMap2" type="com.zm.domain.Orders">
    <id property="id" column="id"/>
    <result property="ordertime" column="ordertime"/>
    <result property="total" column="total"/>
    <result property="uid" column="uid"/>
    <association property="user" javaType="com.zm.domain.User"
                 select="com.zm.mapper.UserMapper.findById" column="uid" fetchType="eager"/>

    <!--这里的select属性里就是statementid,根据这个statementid可以找到根据id查询用户的SQL语句然 
        后执行
        column="uid" : 表示要将结果中的那个字段作为参数进行传递 (这里我们执行的第一条sql是查询了 
                       订单表,然后根据订单表中的uid这个字段去查询user表,也就是说第二次执行的sql 
                       需要的参数是uid,所以这里的column就是uid。)   
    -->

</resultMap>

<select id="findAllWithUser2" resultMap="orderMap2">
    SELECT * FROM orders
</select>

UserMapper 接口

User findById(Integer uid);

UserMapper.xml 映射

<!--根据id查询用户
        useCache="true" 代表当前这个 statement 是使用二级缓存
    -->
<select id="findById" resultType="com.zm.domain.User" parameterType="int" useCache="true">
    SELECT * FROM user WHERE id = #{uid}
</select>

测试代码

@Test
public void test4() {
    OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);
    List<Orders> allWithUser2 = orderMapper.findAllWithUser2();

    for (Orders orders : allWithUser2) {
        System.out.println(orders);
    }
}

5.3 一对多嵌套查询

介绍

需求:查询所有用户,与此同时查询出该用户具有的订单。

一对多查询语句

-- 先查询用户
SELECT * FROM `user`;
-- 再根据用户 id 主键,查询订单列表
SELECT * FROM orders where uid = #{ 用户 id };

代码实现

UserMapper 接口

List<User> findAllWithOrder2();

UserMapper.xml 映射

<resultMap id="userOrderMap" type="com.zm.domain.User">
    <id property="id" column="id"/>
    <result property="username" column="username"></result>
    <result property="birthday" column="birthday"></result>
    <result property="sex" column="sex"></result>
    <result property="address" column="address"></result>

    <collection property="ordersList" ofType="com.zm.domain.Orders" column="id"
                select="com.zm.mapper.OrderMapper.findByUid" ></collection>
</resultMap>

<select id="findAllWithOrder2" resultMap="userOrderMap">
    SELECT * FROM `user`
</select>

OrderMapper 接口

List<Orders> findByUid(Integer uid);

OrderMapper.xml 映射

<select id="findByUid" parameterType="int" resultType="com.zm.domain.Orders">
    SELECT * FROM orders WHERE uid = #{uid}
</select>

测试代码

@Test
public void test5() throws IOException {
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    List<User> allWithOrder2 = userMapper.findAllWithOrder2();

    for (User user : allWithOrder2) {
        System.out.println(user);
    }
}

5.4 多对多嵌套查询

介绍

需求:查询用户,同时查询出该用户的所有角色

多对多查询语句

-- 先查询用户
SELECT * FROM `user`;
-- 再根据用户 id 主键,查询角色列表
SELECT * FROM sys_role r INNER JOIN sys_user_role ur ON r.`id` = ur.`rid` 
    WHERE ur.`uid` = #{ 用户 id };

代码实现

UserMapper 接口

List<User> findAllWithRole2();

UserMapper.xml 映射

<resultMap id="userRoleMap2" type="com.zm.domain.User">
    <id property="id" column="id"/>
    <result property="username" column="username"></result>
    <result property="birthday" column="birthday"></result>
    <result property="sex" column="sex"></result>
    <result property="address" column="address"></result>

    <collection property="roleList" ofType="com.zm.domain.Role" column="id"
                select="com.zm.mapper.RoleMapper.findByUid"></collection>
</resultMap>

<select id="findAllWithRole2" resultMap="userRoleMap2">
    SELECT * FROM USER
</select>

RoleMapper 接口

public interface RoleMapper {
    List<Role> findByUid(Integer uid);
}

RoleMapper.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="com.zm.mapper.RoleMapper">

    <select id="findByUid" resultType="com.zm.domain.Role" parameterType="int">
        SELECT * FROM sys_role r INNER JOIN sys_user_role ur ON ur.roleid = r.id
            WHERE ur.userid = #{uid}
    </select>

</mapper>

测试代码

@Test
public void test6() throws IOException {
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> allWithRole2 = mapper.findAllWithRole2();

    for (User user : allWithRole2) {
        System.out.println(user);
    }
}

5.5 小结

  • 一对一配置:使用 <resultMap> + <association> 做配置,通过 column 条件,执行 select 查询

  • 一对多配置:使用 <resultMap> + <collection> 做配置,通过 column 条件,执行 select 查询

  • 多对多配置:使用 <resultMap> + <collection> 做配置,通过 column 条件,执行 select 查询

优点:简化多表查询操作。

缺点:执行多次 SQL 语句,浪费数据库性能。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值