前言
表与表之间的关系:
- 一对一:ab两表,在任意一张表上创建外键
- 一对多:ab两表,在多的那个表上来创建外键
- 多对多:ab两表,创建一个中间表,来关联两个表
以下所有的查询,都是根据需求决定在哪个实体类新增对应的成员变量
测试sql:
/*
Navicat MySQL Data Transfer
Source Server : localhost
Source Server Version : 50622
Source Host : localhost:3306
Source Database : heima81
Target Server Type : MYSQL
Target Server Version : 50622
File Encoding : 65001
Date: 2019-08-20 17:08:12
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for tb_item
-- ----------------------------
DROP TABLE IF EXISTS `tb_item`;
CREATE TABLE `tb_item` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`item_name` varchar(32) NOT NULL COMMENT '商品名称',
`item_price` float(6,1) NOT NULL COMMENT '商品价格',
`item_detail` text COMMENT '商品描述',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of tb_item
-- ----------------------------
INSERT INTO `tb_item` VALUES ('1', 'iPhone 6', '5288.0', '苹果公司新发布的手机产品。');
INSERT INTO `tb_item` VALUES ('2', 'iPhone 6 plus', '6288.0', '苹果公司发布的新大屏手机。');
-- ----------------------------
-- Table structure for tb_order
-- ----------------------------
DROP TABLE IF EXISTS `tb_order`;
CREATE TABLE `tb_order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NOT NULL,
`order_number` varchar(20) NOT NULL COMMENT '订单号',
PRIMARY KEY (`id`),
KEY `FK_orders_1` (`user_id`),
CONSTRAINT `FK_orders_1` FOREIGN KEY (`user_id`) REFERENCES `tb_user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of tb_order
-- ----------------------------
INSERT INTO `tb_order` VALUES ('1', '1', '20140921001');
INSERT INTO `tb_order` VALUES ('2', '2', '20140921002');
INSERT INTO `tb_order` VALUES ('3', '1', '20140921003');
-- ----------------------------
-- Table structure for tb_orderdetail
-- ----------------------------
DROP TABLE IF EXISTS `tb_orderdetail`;
CREATE TABLE `tb_orderdetail` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_id` int(32) DEFAULT NULL COMMENT '订单号',
`item_id` int(32) DEFAULT NULL COMMENT '商品id',
`total_price` double(20,0) DEFAULT NULL COMMENT '商品总价',
`status` int(11) DEFAULT NULL COMMENT '状态',
PRIMARY KEY (`id`),
KEY `FK_orderdetail_1` (`order_id`),
KEY `FK_orderdetail_2` (`item_id`),
CONSTRAINT `FK_orderdetail_1` FOREIGN KEY (`order_id`) REFERENCES `tb_order` (`id`),
CONSTRAINT `FK_orderdetail_2` FOREIGN KEY (`item_id`) REFERENCES `tb_item` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of tb_orderdetail
-- ----------------------------
INSERT INTO `tb_orderdetail` VALUES ('1', '1', '1', '5288', '1');
INSERT INTO `tb_orderdetail` VALUES ('2', '1', '2', '6288', '1');
INSERT INTO `tb_orderdetail` VALUES ('3', '2', '2', '6288', '1');
INSERT INTO `tb_orderdetail` VALUES ('4', '3', '1', '5288', '1');
-- ----------------------------
-- Table structure for tb_user
-- ----------------------------
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_name` varchar(100) DEFAULT NULL COMMENT '用户名',
`password` varchar(100) DEFAULT NULL COMMENT '密码',
`name` varchar(100) DEFAULT NULL COMMENT '姓名',
`age` int(10) DEFAULT NULL COMMENT '年龄',
`sex` int(11) DEFAULT NULL COMMENT '0-女 1-男',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`user_name`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of tb_user
-- ----------------------------
INSERT INTO `tb_user` VALUES ('1', 'zhangsan', '123456', '张三', '30', '1');
INSERT INTO `tb_user` VALUES ('2', 'lisi', '123456', '李四', '21', '0');
INSERT INTO `tb_user` VALUES ('3', 'wangwu', '123456', '王五', '22', '1');
INSERT INTO `tb_user` VALUES ('4', 'zhangwei', '123456', '张伟', '20', '1');
INSERT INTO `tb_user` VALUES ('5', 'lina', '123456', '李娜', '28', '0');
INSERT INTO `tb_user` VALUES ('6', '蔡徐坤', '123', '小菜', '18', '1');
对应映射类
// item
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Item {
private Integer id;
private String itemName;
private Float itemPrice;
private String itemDetail;
}
//order
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private Integer id;
private String orderNumber; // 订单编号
}
// orderdetail
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Orderdetail {
private Integer id;
private Double totalPrice;
private Integer status;
}
// user
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable{
private Long id;
// 用户名
private String userName;
// 密码
private String password;
// 姓名
private String name;
// 年龄
private Integer age;
//0-女 1-男
private Integer sex;
}
Mybatis工具类
package com.dongmianmao.util;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
public class MybatisUtil {
static SqlSessionFactory ssf = null;
private MybatisUtil(){}
static{
String resource = "mybatis-config.xml";
InputStream resourceAsStream = MybatisUtil.class.getClassLoader().getResourceAsStream(resource);
ssf = new SqlSessionFactoryBuilder().build(resourceAsStream);
}
public static SqlSession OpenSession(){
return ssf.openSession();
}
public static SqlSession OpenSession(Boolean bool){
return ssf.openSession(bool);
}
}
套路
- 1.基于需求,编写SQL语句
- 2.基于SQL语句的查询结果,分析类与类之间的关联(建立实体类和实体类的关联)
- 3.在映射文件中,基于SQL查询结果,配置映射关联
一、一对一查询
需求:根据订单编号查询出订单信息,并查询出下单人信息
在映射接口中定义方法传入订单编号
public interface OrderMapper {
/**
* 根据给定的订单id查出对应用户信息
* @orderNumber 订单编号
* @return 订单对象
*/
Order findOrderByNumber(@Param("orderNumber")String orderNumber);
}
可以使用内连接得到以下sql语句
select tb_order.id as order_id,
tb_order.order_number ,
tb_user.id as user_id,
tb_user.user_name,
tb_user.password,
tb_user.name,
tb_user.age,
tb_user.sex
from tb_order
inner join tb_user
on tb_order.user_id = tb_user.id where tb_order.order_number=对应订单编号;
编写好SQL语句后,就完成了套路一
在配置映射文件中为:
<select id="findOrderByNumber" resultType="order">
select tb_order.id as order_id,
tb_order.order_number ,
tb_user.id as user_id,
tb_user.user_name,
tb_user.password,
tb_user.name,
tb_user.age,
tb_user.sex
from tb_order
inner join tb_user
on tb_order.user_id = tb_user.id where tb_order.order_number=#{orderNumber};
</select>
从以上SQL语句查询出后可以发现,没有任何映射实体类包含以上所有字段
解决方案
根据需求,查询的是关于order的,所以在order中添加新属性,可以看到,在Order表中才是多的那方,而查询出的结果前面是order实体类,后面都是user实体类,即修改 Order 实体类,添加User成员变量,以及对应的get 和 set方法
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private Integer id;
private String orderNumber; // 订单编号
private User user; // 用户对象
public User getOrderUser() {
return orderUser;
}
public void setOrderUser(User orderUser) {
this.orderUser = orderUser;
}
}
解决好关系后,就是完成了套路二
接下来进行套路三,来根据查询结果来进行配置映射
这里需要了解 resultMap 的两个作用
- 增强版的驼峰映射,解决查询字段名和映射字段不一致的问题
- 解决多表查询关联映射
注意:在写resultMap的时候的type,需要注意,标签绑定的方法返回值对象是什么,resultMap 的 type 属性就填什么
首先我们得先区分,哪个查询出来的是属于 Order 的,哪个是属于 User 的
select tb_order.id as order_id,
tb_order.order_number ,
tb_user.id as user_id,
tb_user.user_name,
tb_user.password,
tb_user.name,
tb_user.age,
tb_user.sex
from tb_order
inner join tb_user
on tb_order.user_id = tb_user.id where tb_order.order_number=对应订单编号;
由以上划分的回车,可以知道,前两条是属于 Ordre 的 ,后面全部属于 User
接下来配置 resultMap ,先将 Order 的给配置了
<resultMap id="orderMap" type="com.dongmianmao.Pojo.Order">
<!--配置:查询结果和Order类的映射关联-->
<id column="order_id" property="id"/>
<result column="order_number" property="orderNumber"/>
</resultMap>
接下来配置 一对一 的配置,这个时候得了解 resultMap中针对一对一查询的子标签,及其属性==<association>
==
==<association>
==标签是用于针对一对一查询绑定映射关系的子标签,有以下属性
属性名 | 属性参数 | 属性作用 | 是否必须 |
---|---|---|---|
property | 在多的表中新创建的成员映射对象名,此处指orderUser | 绑定映射属性名 | 是 |
javaType | 在多的表中新创建的成员映射对象路径 | 指定映射实体类路径 | 是 |
autoMapping | 布尔值 | 是否自动绑定 | 是 |
关于 autoMapping 要明白,这个 resultMap 只需要绑定对应和实体映射类成员变量名不一致的字段,其他一致的,让他自动绑定即可 | |||
其余的参数,同父标签一致,使用 ==<id> 绑定主键,使用<column> ==绑定其他字段,就有以下xml配置 |
<resultMap id="orderMap" type="com.dongmianmao.Pojo.Order">
<!--配置:查询结果和Order类的映射关联-->
<id column="order_id" property="id"/>
<result column="order_number" property="orderNumber"/>
<!--
配置:一对一查询
配置:查询结果和User类的映射关联
-->
<association property="orderUser" javaType="com.dongmianmao.Pojo.User" autoMapping="true">
<id column="user_id" property="id"></id>
<result column="user_name" property="userName"></result>
</association>
</resultMap>
完整配置:
<resultMap id="orderMap" type="com.dongmianmao.Pojo.Order">
<!--配置:查询结果和Order类的映射关联-->
<id column="order_id" property="id"/>
<result column="order_number" property="orderNumber"/>
<!--
配置:一对一查询
配置:查询结果和User类的映射关联
-->
<association property="orderUser" javaType="com.dongmianmao.Pojo.User" autoMapping="true">
<id column="user_id" property="id"></id>
<result column="user_name" property="userName"></result>
</association>
</resultMap>
<select id="findOrderByNumber" resultType="order">
select tb_order.id as order_id,
tb_order.order_number ,
tb_user.id as user_id,
tb_user.user_name,
tb_user.password,
tb_user.name,
tb_user.age,
tb_user.sex
from tb_order
inner join tb_user
on tb_order.user_id = tb_user.id where tb_order.order_number=#{orderNumber};
</select>
测试类
@Test
public void testFinOrderByNumber(){
SqlSession sqlSession = MybatisUtil.OpenSession();
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
Order order = mapper.findOrderByNumber("20140921003");
System.out.println("订单信息:"+order);
System.out.println("下单用户信息:"+order.getOrderUser());
sqlSession.close();
}
/**
查询结果
*订单信息:Order(id=3, orderNumber=20140921003, orderUser=User(id=1, userName=zhangsan, password=123456, name=张*三, age=30, sex=1))
*下单用户信息:User(id=1, userName=zhangsan, password=123456, name=张三, age=30, sex=1)
*/
这样就完成了配置,就可以正常使用 findOrderByNumber 方法查询出对应参数了
二、一对多
需求:查询id为1的用户及其订单信息
套路一,先编写对应的SQL语句,可以得到以下SQL语句
select tb_user.id as user_id,
tb_user.user_name,
tb_user.password,
tb_user.name,
tb_user.age,
tb_user.sex,
tb_order.id as orderId,
tb_order.order_number
from tb_user
inner join tb_order on tb_user.id = tb_order.user_id where tb_order.user_id = 1;
套路二、根据SQL语句分析类与类之间的关联
除了最后两个字段是属于 Order ,其他字段都属于 User
SQL语句查询结果
可以看到,前面的用户信息全部属于同一个User,而订单信息是多个不同的订单,这就是一对多
而针对这种情况,就需要使用一种能容纳多个对象的容器,相信你已经想到了,那就是:List
将泛型改为 Order 类型 List<Order>
根据需求,这个是针对用户来进行查找订单的,所以在用户映射对象中添加新东西
就是我们所需要的解决方案
先在映射接口定义对应方法
package com.dongmianmao.Dao;
import com.dongmianmao.Pojo.User;
import org.apache.ibatis.annotations.Param;
public interface UserMapper {
// 根据id找订单信息
User findUserById(@Param("id")int id);
}
修改对应的实体类,增加 orders 集合对象
package com.dongmianmao.Pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable{
private Long id;
// 用户名
private String userName;
// 密码
private String password;
// 姓名
private String name;
// 年龄
private Integer age;
//0-女 1-男
private Integer sex;
// 一对多,一个用户有多个订单
private List<Order> orders;
public List<Order> getOrders() {
return orders;
}
public void setOrders(List<Order> orders) {
this.orders = orders;
}
}
到这就已经确定好映射类的关系了
套路三、基于SQL查询结果,配置映射关联
先在 resultMap 中,先将对应的 User类的查询结果 给配置好
<resultMap id="userMap" type="com.dongmianmao.Pojo.User" autoMapping="true">
<id column="user_id" property="id"/>
<result column="user_name" property="userName"/>
</resultMap>
只修改两个和映射变量不一致的字段
在进行配置一对多之前,先了解一个 resultMap 的一个子标签==<collection>
,该标签是针对一对多情况而使用的标签,用于将多==的对象内容存储进容器中,有以下属性:
属性名 | 属性参数 | 属性作用 | 是否必须 |
---|---|---|---|
property | 映射类中的容器变量名 | 绑定集合变量 | 是 |
javaType | 指定容器的类型 | 指定容器的类型 | 是 |
ofType | 指定容器中存储的数据类型 | 指定容器中存储的数据类型 | 是 |
知道这些后,一样的,在子标签内使用 id result 标签 来绑定对应的主键和字段,完整 配置 如下 |
<resultMap id="userMap" type="com.dongmianmao.Pojo.User" autoMapping="true">
<id column="user_id" property="id"/>
<result column="user_name" property="userName"/>
<!--配置一对多关系-->
<collection property="orders" javaType="list" ofType="com.dongmianmao.Pojo.Order">
<!--配置查询结果和Order的映射-->
<id column="orderId" property="id"/>
<result column="order_number" property="orderNumber"/>
</collection>
</resultMap>
<select id="findUserById" resultMap="userMap">
select tb_user.id as user_id,
tb_user.user_name,
tb_user.password,
tb_user.name,
tb_user.age,
tb_user.sex,
tb_order.id as orderId,
tb_order.order_number
from tb_user
inner join tb_order on tb_user.id = tb_order.user_id
where tb_order.user_id = #{id};
</select>
最后是测试类:
@Test
public void testFindUserById(){
SqlSession sqlSession = MybatisUtil.OpenSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User userById = mapper.findUserById(1);
System.out.println(userById);
sqlSession.close();
}
/**
*查询结果
*User(id=1, userName=zhangsan, password=123456, name=张三, age=30, sex=1, orders=[Order(id=1, orderNumber=20140921001, orderUser=null), Order(id=3, orderNumber=20140921003, orderUser=null)])
*/
这样就完成了一对多查询
三、多对多
多对多的情况,可以先看,最开始所说的多对多的情况,会采取中间表的方式来生成外键构建表与表之间的关系,从这个中间表的角度来看,本质上还是一对多的情况
只要从中间表的角度来看,就可以联想到一对多的做法