MyBatis入门及进阶 – 复杂映射

分享知识, 不咕自己.

前言

复杂映射是关系型数据库的主要特性. 是指采用了关系模型来组织数据的数据库, 它以行和列形式储存数据从而组成的概念, 以便于用户的理解. 再实际开发中, 通常我们会利用数据表之间的关系组合查询, 从中提取出我们需要的数据. 复杂关系分为三种一对一、一对多和多对多

一、准备

开始之前, 我们需要对前几章中创建的测试数据库mybatis_test进行扩充. 除了user表外, 我们还需要新建另一张表orders订单表, 并模拟实际需求进行练习.

二、一对一关系

首先, 我们需要新建orders表. 代码如下:

CREATE TABLE `orders` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `uid` int(11) DEFAULT NULL,
  `total_amount` double(11,2) DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
复制代码

在orders表中插入测试数据.

insert into orders values(1,1,200,"2021-05-13 21:31:31");
insert into orders values(2,1,4000,"2021-05-2 10:31:31");
insert into orders values(3,2,30,"2021-05-5 21:11:31");
insert into orders values(4,2,320,"2021-05-6 21:22:31");
insert into orders values(5,3,64,"2021-05-7 21:33:31");
insert into orders values(6,4,99,"2021-05-8 21:44:31");
insert into orders values(7,3,12,"2021-05-9 21:55:31");
insert into orders values(8,3,78,"2021-05-10 21:11:31");
复制代码

创建好orders表之后. 我们先分析以下这两张表的关系.

微信截图_20210513211313.png

如上图所示. 我们要模拟一个订单的场景. 我们在日常生活中在使用电商软件下单的时候. 一个用户(user)会生成多个订单(orders). 相反的一个订单(order)只属于一个用户(user). 所以相对于用户和订单是一对多的关系, 相对于订单和用户是一对一的关系.
所以, 我们在练习一对一关系的时候, 会站在订单的角度编写代码.

有了用户(user)表和订单(orders)表. 我们还需要在测试项目mybatis_quick_start中创建对应的实体映射类. 代码如下

public class Orders {

    private Integer id;
    private Integer uid;
    private Double totalAmount;
    private Date createTime;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getUid() {
        return uid;
    }

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public Double getTotalAmount() {
        return totalAmount;
    }

    public void setTotalAmount(Double totalAmount) {
        this.totalAmount = totalAmount;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
}
复制代码

那么现在, 我们来考虑一个问题. 既然我是要做的操作是多表组合查询. 那么我们在Mapper.xml中select标签的resultType应该是什么类型呢?. 我们的需求是既要查询出订单信息还要携带对应的用户信息. 那么它查询出来的结果应该是这样的:

微信截图_20210513215000.png

所以不管我们的resultType是使用Orders类型还是使用User类型都不能满足映射关系. 那我们该怎么办呢?. 其实很简单. 我们只需要在Orders映射实体中添加一个User类型的属性, 表示orders和user一对一关系

public class Orders {

    private Integer id;
    private Integer uid;
    private Double totalAmount;
    private Date createTime;

    // 表示订单属于那个用户
    private User user;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getUid() {
        return uid;
    }

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public Double getTotalAmount() {
        return totalAmount;
    }

    public void setTotalAmount(Double totalAmount) {
        this.totalAmount = totalAmount;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
    
    @Override
    public String toString() {
        return "Orders{" +
                "id=" + id +
                ", uid=" + uid +
                ", totalAmount=" + totalAmount +
                ", createTime=" + createTime +
                ", user=" + user +
                '}';
    }
}
复制代码

接着我们在dao文件夹下创建一个OrdersDao的接口, 让MyBatis为我们创建动态代理的实现对象. 并声明方法.

public interface OrdersDao {

    List<Orders> getOrderAndUser();
}
复制代码

接下来在resources文件夹下创建OrdersMapper.xml, 注意namespace路径正确.

<?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.lgy.dao.OrdersDao">

    <!-- 查询订单和用户 -->
    <select id="getOrderAndUser" resultType="orders">
        select * from orders o, user u where o.uid = u.id;
    </select>

</mapper>
复制代码

最后我们还需要在核心配置文件mybatis-config.xml中注册我们新建的映射配置文件(OrdersMapper.xml)

    <mappers>
        <mapper resource="UserMapper.xml"></mapper>
        <mapper resource="OrdersMapper.xml"></mapper>
    </mappers>
复制代码

如果现在编写测试类执行呢. 其实是发生一个BindingException(绑定异常)的错误的. 因为即使我们在Orders类中声明了User类型的对象. 但是Mybatis是不明白查询出来的user表信息应该映射到User对象里的, 为了解决这个问题. MyBatis为我们提供了一个resultMap的标签来让我们声明出多表组合查询中的映射关系.

    <resultMap id="orderAndUser" type="com.lgy.pojo.Orders">
        <result property="id" column="id"></result>
        <result property="uid" column="uid"></result>
        <result property="totalAmount" column="total_amount"></result>
        <result property="createTime" column="create_time"></result>
        
        <association property="user" javaType="com.lgy.pojo.User">
            <result property="id" column="uid"></result>
            <result property="username" column="username"></result>
            <result property="gender" column="gender"></result>
            <result property="age" column="age"></result>
        </association>
    </resultMap>
复制代码

那么上面的代码是什么意思呢. 我们可以画个图便于理解

1620916281(1).png

那么我们在之前写的resultType=orders就是错误的. 我们应该改为resultMap=orderAndUserM

    <!-- 查询订单和用户 -->
    <select id="getOrderAndUser" resultMap="orderAndUser">
        select * from orders o, user u where o.uid = u.id;
    </select>
复制代码

现在我们就可以编写测试方法了. 代码如下:

    @Test
    public void testGetOrderAndUser() throws IOException {
        // 获取配置文件 转换为输入流
        InputStream resourceAsStream =
                Resources.getResourceAsStream("mybatis-config.xml");
        // 获取 SqlSessionFactory 后面细说
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(resourceAsStream);
        // 获取sqlSessin
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 通过JDK动态代理获取接口代理对象
        OrdersDao ordersDao = sqlSession.getMapper(OrdersDao.class);
        List<Orders> orders = ordersDao.getOrderAndUser();

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

        // 关闭sqlSession
        sqlSession.close();
    }
复制代码

由于篇幅原因就不展示测试结果了. 请自行测试. 如果User对象信息无法显示, 请在User类中重写toString方法

三、一对多

一对多关系我们可以接着使用orders表和user表. 相对于用户来说一个用户可以拥有多个订单. 不同的是, 为了表示一对多的映射关系, 我们应该在User类中声明一个List<Orders>的集合对象. 代码如下:

public class User {

    private Integer id;
    private String username;
    private Integer gender;
    private Integer age;
    private List<Orders> orders;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Integer getGender() {
        return gender;
    }

    public void setGender(Integer gender) {
        this.gender = gender;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public List<Orders> getOrders() {
        return orders;
    }

    public void setOrders(List<Orders> orders) {
        this.orders = orders;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", gender=" + gender +
                ", age=" + age +
                ", orders=" + orders +
                '}';
    }
}
复制代码

接着我们在UserDao接口中声明getUserAndOrders方法

---- 省略其他代码
List<User> getUserAndOrders();
----
复制代码

在UserMapper.xml映射文件中编写resultMap映射规则并编写查询sql

    ---- 省略其他代码
    <resultMap id="userAndOrders" type="com.lgy.pojo.User">
        <result property="id" column="id"></result>
        <result property="username" column="username"></result>
        <result property="gender" column="gender"></result>
        <result property="age" column="age"></result>
        
        <collection property="orders" ofType="com.lgy.pojo.Orders">
            <result property="uid" column="uid"></result>
            <result property="totalAmount" column="total_amount"></result>
            <result property="createTime" column="create_time"></result>
        </collection>
    </resultMap>

    <select id="getUserAndOrders" resultMap="userAndOrders">
        select * from user u, orders o where u.id = o.uid
    </select>
    ----
复制代码

需要注意的是, 因为一对多映射所以我们要使用collection标签, 并且映射对象属性使用ofType. 测试类代码如下:

    @Test
    public void testGetUserAndOrders() throws IOException {
        // 获取配置文件 转换为输入流
        InputStream resourceAsStream =
                Resources.getResourceAsStream("mybatis-config.xml");
        // 获取 SqlSessionFactory 后面细说
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(resourceAsStream);
        // 获取sqlSessin
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 通过JDK动态代理获取接口代理对象
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        List<User> userAndOrders = userDao.getUserAndOrders();

        for (User userAndOrder : userAndOrders) {
            System.out.println(userAndOrder);
        }

        // 关闭sqlSession
        sqlSession.close();
    }
复制代码

四、多对多

一对一和一对多关系中我们使用两张表就可以表示其关系, 但是在多对多关系中我们除了两张记录数据的实体表, 还需要一张记录关系的中间表. 接下来我们以用户权限为例子练习多对多的查询. 首先我们需要在测试数据库mybatis_test创建两张表: role(权限表)和user_role_middle(用户和权限的中间表)

CREATE TABLE `role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role_name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `user_role_middle` (
  `role_id` int(11) DEFAULT NULL,
  `user_id` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
复制代码
insert into role values(1,'管理员');
insert into role values(2,'操作员');
insert into role values(3,'用户');

insert into user_role_middle values(1,1);
insert into user_role_middle values(3,1);
insert into user_role_middle values(3,2);
insert into user_role_middle values(2,2);
复制代码

在这组数据中用户1既是管理员又是用户, 用户2即使操作员又是用户. 正好满足我们多对多的关系. 接下来我们要在测试项目mybatis_quick_start中的pojo报下创建权限表(role)和用户权限中间表(user_role_middle)的映射实体类. 代码如下

public class Role {
    
    private Integer id;
    private String roleName;
    
    private List<User> users;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    @Override
    public String toString() {
        return "Role{" +
                "id=" + id +
                ", roleName='" + roleName + '\'' +
                ", users=" + users +
                '}';
    }
}
复制代码
public class UserRoleMiddle {

    private Integer roleId;
    private Integer userId;

    public Integer getRoleId() {
        return roleId;
    }

    public void setRoleId(Integer roleId) {
        this.roleId = roleId;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    @Override
    public String toString() {
        return "UserRoleMiddle{" +
                "roleId=" + roleId +
                ", userId=" + userId +
                '}';
    }
}
复制代码

因为映射关系中用户(User)可以拥有多个权限(Role), 所以在User类中也要声明List<Role>属性

    ---- 省略get set toString 方法
    private Integer id;
    private String username;
    private Integer gender;
    private Integer age;
    
    private List<Orders> orders;
    private List<Role> roles;
    ----
复制代码

在dao文件夹下创建RoleDao接口并在resources文件夹下创建RoleMapper.xml映射配置文件

public interface RoleDao {

    // 查询所有权限下的用户
    List<Role> getRoles();
}
复制代码
<mapper namespace="com.lgy.dao.RoleDao">

    <resultMap id="roleAndUsers" type="com.lgy.pojo.Role">
        <result property="id" column="id"></result>
        <result property="roleName" column="role_name"></result>

        <collection property="users" ofType="com.lgy.pojo.User">
            <result property="id" column="uid"></result>
            <result property="username" column="username"></result>
            <result property="gender" column="gender"></result>
            <result property="age" column="age"></result>
        </collection>
    </resultMap>

    <!-- 查询订单和用户 -->
    <select id="getRoles" resultMap="roleAndUsers">
        select * from role r, user u, user_role_middle m where r.id = m.user_id and u.id = m.role_id;
    </select>

</mapper>
复制代码

最后只需要在核心配置类mybatis-config.xml中配置RoleMapper.xml路径就可以编写测试类了.

    ---- 省略其他配置
    <mappers>
        <mapper resource="UserMapper.xml"></mapper>
        <mapper resource="OrdersMapper.xml"></mapper>
        <mapper resource="RoleMapper.xml"></mapper>
    </mappers>
    ----
复制代码

查询用户所有权限的方法不再展示, 当作拓展练习.

由于篇幅原因不展示测试结果, 大家可以自行测试

五、总结

随着项目逐渐的推荐, 数据库中的表增多. 表之间的关系也会越错综复杂. 大家可以多使用UML类图来帮助大家梳理复杂关系. 加深对于项目整理的理解. 这样开发起来也会得心应手. 有了整体的认知更容易帮助大家发现项目中的有点与不足. 更容易发散出有建设性的意见. 升职加薪指日可待. 加油打工人!

六、历史连接

如果有概念含糊不清或者错误的情况. 欢迎大家指出. 感谢.

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享