分享知识, 不咕自己.
前言
复杂映射是关系型数据库
的主要特性. 是指采用了关系模型来组织数据的数据库, 它以行和列
形式储存数据从而组成表
的概念, 以便于用户的理解. 再实际开发中, 通常我们会利用数据表之间的关系
组合查询, 从中提取出我们需要的数据. 复杂关系分为三种一对一、一对多和多对多
一、准备
开始之前, 我们需要对前几章中创建的测试数据库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表之后. 我们先分析以下这两张表的关系.
如上图所示. 我们要模拟一个订单的场景. 我们在日常生活中在使用电商软件下单的时候. 一个用户(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应该是什么类型呢?
. 我们的需求是既要查询出订单信息还要携带对应的用户信息
. 那么它查询出来的结果应该是这样的:
所以不管我们的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>
复制代码
那么上面的代码是什么意思呢. 我们可以画个图便于理解
那么我们在之前写的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类图来帮助大家梳理复杂关系. 加深对于项目整理的理解
. 这样开发起来也会得心应手. 有了整体的认知更容易帮助大家发现项目中的有点与不足
. 更容易发散出有建设性的意见. 升职加薪指日可待. 加油打工人!
六、历史连接
如果有概念含糊不清或者错误的情况. 欢迎大家指出. 感谢.