Mybatis一对一、一对多、多对多

前言

表与表之间的关系:

  • 一对一: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,需要注意,标签绑定的方法返回值对象是什么,resultMaptype 属性就填什么
    首先我们得先区分,哪个查询出来的是属于 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语句查询结果
![[Pasted image 20250315174437.png]]

可以看到,前面的用户信息全部属于同一个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)])
*/

这样就完成了一对多查询

三、多对多

多对多的情况,可以先看,最开始所说的多对多的情况,会采取中间表的方式来生成外键构建表与表之间的关系,从这个中间表的角度来看,本质上还是一对多的情况
只要从中间表的角度来看,就可以联想到一对多的做法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值