大厂Java面试题:来自京东的 MyBatis 面试题:MyBatis 能实现一对一,一对多查询吗?如何实现?

大家好,我是王有志。今天给大家带来的是一道来自京东的 MyBatis 面试题:MyBatis 能实现一对一,一对多查询吗?如何实现?

关于 MyBatis 一对一查询,一对多查询的内容,以及更多复杂查询的写法,可以在这两篇文章中找到更详细的内容:《MyBatis映射器:一对多关联查询》《MyBatis映射器:一对一关联查询》

一对一查询

在 MyBatis 中有多种方式实现一对一关联查询:

  • 使用数据库别名实现一对一关联查询的自动映射;
  • 使用 resultMap 元素实现一对一关联查询;
  • 使用 resultMap 元素实现一对一嵌套查询。

首先我们准备 3 张表,用户订单表(user_order),支付订单表(pay_order)和订单明细表(order_item),并对这些表进行初始化,建表 SQL 语句和初始化 SQL 语句如下:

create table user_order (
  order_id     int auto_increment comment '订单表主键' primary key,
  user_id      int            not null comment 'user表的主键',
  order_no     varchar(50)    null comment '订单号',
  order_price  decimal(18, 2) not null comment '订单价格',
  order_status int            null comment '订单状态',
  create_date  date           null comment '订单创建时间',
  pay_date     date           null comment '订单支付时间'
) comment '用户订单表';

create table pay_order (
  pay_order_id int            not null comment '支付订单表主键' primary key,
  order_id     int            not null comment '订单表主键',
  pay_order_no varchar(50)    null comment '支付订单号',
  pay_amount   decimal(18, 2) null comment '支付金额',
  pay_channel  int            null comment '支付渠道',
  pay_status   int            null comment '支付状态',
  create_date  date           null comment '支付订单创建时间',
  finish_date  date           null comment '支付订单完成时间'
) comment '支付订单表';

create table order_item (
  item_id         int            not null comment '订单商品表主键' primary key,
  order_id        int            not null comment '订单表主键',
  commodity_id    int            not null comment '商品表主键',
  commodity_price decimal(18, 2) not null comment '商品价格',
  commodity_count int            not null comment '商品数量'
) comment '订单明细表';

INSERT INTO user_order (order_id, user_id, order_no, order_price, order_status, create_date, pay_date)
VALUES (1, 1, 'D202405082208045788', 10000.00, 1, '2024-05-14', '2024-05-14');
INSERT INTO user_order (order_id, user_id, order_no, order_price, order_status, create_date, pay_date) 
VALUES (2, 1, 'D202405131542336954', 9900.00, 1, '2024-05-01', '2024-05-01');

INSERT INTO pay_order (pay_order_id, order_id, pay_order_no, pay_amount, pay_channel, pay_status, create_date, finish_date)
VALUES (1, 1, 'Z202405082208557945', 10000.00, 37, 1, '2024-05-15', '2024-05-15');
INSERT INTO pay_order (pay_order_id, order_id, pay_order_no, pay_amount, pay_channel, pay_status, create_date, finish_date) 
VALUES (2, 2, 'Z202405131543079921', 9900.00, 98, 1, '2024-05-01', '2024-05-01');

INSERT INTO order_item (item_id, order_id, commodity_id, commodity_price, commodity_count) 
VALUES (1, 1, 350071, 100.00, 10);
INSERT INTO order_item (item_id, order_id, commodity_id, commodity_price, commodity_count) 
VALUES (2, 1, 350083, 500.00, 10);
INSERT INTO order_item (item_id, order_id, commodity_id, commodity_price, commodity_count) 
VALUES (3, 1, 360302, 400.00, 10);
INSERT INTO order_item (item_id, order_id, commodity_id, commodity_price, commodity_count) 
VALUES (4, 2, 350891, 77.00, 100);
INSERT INTO order_item (item_id, order_id, commodity_id, commodity_price, commodity_count) 
VALUES (5, 2, 330001, 220.00, 10);

这 3 张表的关系如下:

接着我们来定义这 3 张表对应的 Java 对象,pay_order 表对应的 Java 对象 PayOrderDO,如下:

public class PayOrderDO {
  /**
   * 支付订单表主键
   */
  private Integer payOrderId;

  /**
   * 订单表主键
   */
  private Integer orderId;

  /**
   * 支付订单号
   */
  private String payOrderNo;

  /**
   * 支付金额
   */
  private BigDecimal payAmount;

  /**
   * 支付渠道
   */
  private Integer payChannel;

  /**
   * 支付状态
   */
  private Integer payStatus;

  /**
   * 支付订单创建时间
   */
  private Date createDate;

  /**
   * 支付订单完成时间
   */
  private Date finishDate;
}

order_item 表对应的 Java 对象 OrderItemDO,如下:

public class OrderItemDO {

  /**
   * 订单商品表主键
   */
  private Integer itemId;

  /**
   * 订单表主键Java
   */
  private Integer orderId;

  /**
   * 商品表主键
   */
  private Integer commodityId;

  /**
   * 商品价格
   */
  private BigDecimal commodityPrice;

  /**
   * 商品数量
   */
  private Integer commodityCount;
}

最后来定义 user_order 表对应的 Java 对象 UserOrderDO,这里需要在 UserOrderDO 中组合 PayOrderDO 和 OrderItemDO,如下:

public class UserOrderDO   {
  /**
   * 订单表主键
   */
  private Integer orderId;

  /**
   * user表的主键
   */
  private Integer userId;

  /**
   * 订单号
   */
  private String orderNo;

  /**
   * 订单价格
   */
  private BigDecimal orderPrice;

  /**
   * 订单状态
   */
  private Integer orderStatus;

  /**
   * 订单创建时间
   */
  private Date createDate;

  /**
   * 订单支付时间
   */
  private Date payDate;

  /**
   * 支付订单信息
   */
  private PayOrderDO payOrder;

  /**
   * 用户订单明细
   */
  private List<OrderItemDO> orderItems;
}

最后就是 Mapper 接口和接口对应的映射器文件了,这个大家自行实现一个空的 Mapper 接口和映射器文件即可。

使用数据库别名实现一对一关联查询的自动映射

第一种方式我们借助数据库别名实现一对一关联查询的自动映射。

我们为 UserOrderMapper 接口中添加查询方法,并且为映射器 UserOrderMapper.xml 中添加对应的 SQL 语句。UserOrderMapper 接口中的方法:

UserOrderDO selectUserOrderAndPayOrderByOrderNoUseAlias(@Param("orderNo") String orderNo);

映射器 UserOrderMapper.xml 中对应的 SQL 语句:

<select id="selectUserOrderAndPayOrderByOrderNoUseAlias" resultType="com.wyz.entity.UserOrderDO">
  select  uo.order_id     as orderId,
          uo.user_id      as userId,
          uo.order_no     as orderNo,
          uo.order_price  as orderPrice,
          uo.order_status as orderStatus,
          uo.create_date  as createDate,
          uo.pay_date     as payDate,
          po.pay_order_id as "payOrder.payOrderId",
          po.order_id     as "payOrder.orderId",
          po.pay_order_no as "payOrder.payOrderNo",
          po.pay_amount   as "payOrder.payAmount",
          po.pay_channel  as "payOrder.payChannel",
          po.pay_status   as "payOrder.payStatus",
          po.create_date  as "payOrder.createDate",
          po.finish_date  as "payOrder.finishDate"
  from user_order uo, pay_order po
  where uo.order_no = #{orderNo,jdbcType=VARCHAR}
    and uo.order_id = po.order_id;
</select>

这种方式中,我们直接使用了 resultType 属性,将 SQL 语句的查询结果直接映射到 Java 对象上,并没有使用 resultMap 元素自定义映射规则。

使用 resultMap 元素实现一对一关联查询

我们还可以借助 resultMap 元素实现自定义映射规则,从而将结果集映射到 Java 对象上。

我们为 UserOrderMapper 接口中添加查询方法,并且为映射器 UserOrderMapper.xml 中添加对应的 SQL 语句。UserOrderMapper 接口中的方法:

UserOrderDO selectUserOrderAndPayOrderByOrderNo(@Param("orderNo") String orderNo);

接着我们来改写映射器 UserOrderMapper.xml 中对应的 SQL 语句:

<select id="selectUserOrderAndPayOrderByOrderNo" resultMap="userOrderContainPayOrderMap">
  select uo.order_id,
         uo.user_id,
         uo.order_no,
         uo.order_price,
         uo.order_status,
         uo.create_date,
         uo.pay_date,
         po.pay_order_id as po_pay_order_id,
         po.order_id     as po_order_id,
         po.pay_order_no as po_pay_order_no,
         po.pay_amount   as po_pay_amount,
         po.pay_channel  as po_pay_channel,
         po.pay_status   as po_pay_status,
         po.create_date  as po_create_date,
         po.finish_date  as po_finish_date
  from user_order uo,pay_order po
  where uo.order_no = #{orderNo,jdbcType=VARCHAR}
    and uo.order_id = po.order_id;
</select>

注意,在 SQL 语句的查询字段中,因为 user_order 表与 pay_order 表有重名字段 order_id 和 create_date,因我为 pay_order 表的所有字段的别名都添加了前缀“po_”。

最后我们来写映射规则“userOrderContainPayOrderMap”,如下:

<resultMap id="userOrderContainPayOrderMap" type="com.wyz.entity.UserOrderDO">
  <id property="orderId" column="order_id" jdbcType="INTEGER"/>
  <result property="userId" column="user_id" jdbcType="INTEGER"/>
  <result property="orderNo" column="order_no" jdbcType="VARCHAR"/>
  <result property="orderPrice" column="order_price" jdbcType="DECIMAL"/>
  <result property="orderStatus" column="order_status" jdbcType="INTEGER"/>
  <result property="createDate" column="create_date" jdbcType="DATE"/>
  <result property="payDate" column="pay_date" jdbcType="DATE"/>
  <association property="payOrder" javaType="com.wyz.entity.PayOrderDO" columnPrefix="po_">
    <id property="payOrderId" column="pay_order_id" jdbcType="INTEGER"/>
    <result property="orderId" column="order_id" jdbcType="INTEGER"/>
    <result property="payOrderNo" column="pay_order_no" jdbcType="VARCHAR"/>
    <result property="payAmount" column="pay_amount" jdbcType="DECIMAL"/>
    <result property="payChannel" column="pay_channel" jdbcType="INTEGER"/>
    <result property="payStatus" column="pay_status" jdbcType="INTEGER"/>
    <result property="createDate" column="create_date" jdbcType="DATE"/>
    <result property="finishDate" column="finish_date" jdbcType="DATE"/>
  </association>
</resultMap>

association 元素表示一个复杂 Java 对象的关联,property 属性声明了被关联对象在关联对象中的名称,即 PayOrderDO 对象在 UserOrderDO 对象中的字段名,javaType 属性声明了被关联对象的类型,columnPrefix 属性用于声明该对象字段名称的前缀,配置 columnPrefix 属性后,在 id 元素以及 result 元素的 column 属性中就可以省略这个前缀。

使用 resultMap 元素实现一对一嵌套查询

resultMap 元素还可以实现嵌套查询,嵌套查询是通过两次查询,并合并查询结果实现的。

我们为 UserOrderMapper 接口中添加查询方法,并且为映射器 UserOrderMapper.xml 中添加对应的 SQL 语句。UserOrderMapper 接口中的方法:

UserOrderDO selectUserOrderAndPayOrderByOrderNoNest(@Param("orderNo") String orderNo);

映射器 UserOrderMapper.xml 中对应的 SQL 语句:

<select id="selectUserOrderAndPayOrderByOrderNoNest" resultMap="userOrderContainPayOrderNestMap">
  select * from user_order
  where order_no = #{orderNo,jdbcType=VARCHAR}
</select>

接着我们为 PayOrderMapper 接口中添加查询方法,并且为映射器 PayOrderMapper.xml 中添加对应的 SQL 语句。

因为 user_order 表与 pay_order 表是通过 order_id 进行关联的,因此 pay_order 表的查询语句中,查询条件也为 order_id,PayOrderMapper 接口中的方法如下:

PayOrderDO selectPayOrderByOrderId(@Param("orderId") Integer orderId);

映射器 PayOrderMapper.xml 中对应的 SQL 语句和映射规则:

<resultMap id="BaseResultMap" type="com.wyz.entity.PayOrderDO">
  <id property="payOrderId" column="pay_order_id" jdbcType="INTEGER"/>
  <result property="orderId" column="order_id" jdbcType="INTEGER"/>
  <result property="payOrderNo" column="pay_order_no" jdbcType="VARCHAR"/>
  <result property="payAmount" column="pay_amount" jdbcType="DECIMAL"/>
  <result property="payChannel" column="pay_channel" jdbcType="INTEGER"/>
  <result property="payStatus" column="pay_status" jdbcType="INTEGER"/>
  <result property="createDate" column="create_date" jdbcType="DATE"/>
  <result property="finishDate" column="finish_date" jdbcType="DATE"/>
</resultMap>

<select id="selectPayOrderByOrderId" resultMap="BaseResultMap">
  select * from pay_order
  where order_id = #{orderId, jdbcType=INTEGER}
</select>

最后来定义映射关系“userOrderContainPayOrderNestMap”,如下:

<resultMap id="userOrderContainPayOrderNestMap" type="com.wyz.entity.UserOrderDO">
  <id property="orderId" column="order_id" jdbcType="INTEGER"/>
  <result property="userId" column="user_id" jdbcType="INTEGER"/>
  <result property="orderNo" column="order_no" jdbcType="VARCHAR"/>
  <result property="orderPrice" column="order_price" jdbcType="DECIMAL"/>
  <result property="orderStatus" column="order_status" jdbcType="INTEGER"/>
  <result property="createDate" column="create_date" jdbcType="DATE"/>
  <result property="payDate" column="pay_date" jdbcType="DATE"/>
  <association
    property="payOrder"
    javaType="com.wyz.entity.PayOrderDO"
    select="com.wyz.mapper.PayOrderMapper.selectPayOrderByOrderId"
    column="{orderId=order_id}"/>
</resultMap>

注意这里与一对一关联查询的区别在于 association 元素的配置以及 association 元素中没有声明 pay_order 表与 Java 对象 PayOrderDO 的映射配置。

property 属性与 javaType 属性我们已经在前面见到过了,select 属性声明了查询 pay_order 表数据的 SQL 语句,即我们前面定义的PayorderMapper#selectPayOrderByOrderIdcolumn 属性声明了查询 pay_order 表的 SQL 语句的入参与与 user_order 表中的映射关系,即PayorderMapper#selectPayOrderByOrderId方法的入参为 user_order 表中的字段 order_id,如果有多个入参,使用英文逗号分隔,例如:{orderId=order_id, payOrderId=pay_order_id},这里只是为了举例说明多参数的场景。

一对多查询

使用 resultMap 元素实现一对多关联查询

在 resultMap 元素中实现一对多关联查询,需要使用到子元素 collection 元素。

我们为 UserOrderMapper 接口中添加查询方法,并且为映射器 UserOrderMapper.xml 中添加对应的 SQL 语句。UserOrderMapper 接口中的方法:

UserOrderDO selectUserOrderAndOrderItemsByOrderNo(@Param("orderNo") String orderNo);

映射器 UserOrderMapper.xml 中对应的 SQL 语句:

<select id="selectUserOrderAndOrderItemsByOrderNo" resultMap="userOrderContainOrderItemMap">
  select uo.order_id,
         uo.user_id,
         uo.order_no,
         uo.order_price,
         uo.order_status,
         uo.create_date,
         uo.pay_date,
         oi.item_id         as oi_item_id,
         oi.order_id        as oi_order_id,
         oi.commodity_id    as oi_commodity_id,
         oi.commodity_price as oi_commodity_price,
         oi.commodity_count as oi_commodity_count
  from user_order uo, order_item oi
  where uo.order_id = oi.order_id
    and uo.order_no = #{orderNo,jdbcType=VARCHAR}
</select>

接着我们来定义映射规则“userOrderContainOrderItemMap”,代码如下:

<resultMap id="userOrderContainOrderItemMap" type="com.wyz.entity.UserOrderDO">
  <id property="orderId" column="order_id" jdbcType="INTEGER"/>
  <result property="userId" column="user_id" jdbcType="INTEGER"/>
  <result property="orderNo" column="order_no" jdbcType="VARCHAR"/>
  <result property="orderPrice" column="order_price" jdbcType="DECIMAL"/>
  <result property="orderStatus" column="order_status" jdbcType="INTEGER"/>
  <result property="createDate" column="create_date" jdbcType="DATE"/>
  <result property="payDate" column="pay_date" jdbcType="DATE"/>  
  <collection property="orderItems" javaType="java.util.ArrayList" ofType="com.wyz.entity.OrderItemDO" columnPrefix="oi_">
    <id property="itemId" column="item_id" jdbcType="INTEGER"/>
    <result property="orderId" column="order_id" jdbcType="INTEGER"/>
    <result property="commodityId" column="commodity_id" jdbcType="INTEGER"/>
    <result property="commodityPrice" column="commodity_price" jdbcType="DECIMAL"/>
    <result property="commodityCount" column="commodity_count" jdbcType="INTEGER"/>
  </collection>
</resultMap>

注意,这里的联表查询的结果是笛卡尔积的形式,在我们的测试场景中,表结构简单,数据量较少,没有直观上的性能问题,不过一旦遇到表结复杂,数据量非常大的场景,如果 SQL 语句设计的再不合理,那么对性能的影响可能是灾难级的。此时与其坚守着一条查询语句查询出所有结果,倒不如将复杂的查询语句分拆成多步查询的方式来的划算。

使用 resultMap 元素实现一对多嵌套查询

我们可以将上面的联表的查询的 SQL 语句拆分成多步查询的方式。

我们为 OrderItemMapper 接口中添加查询方法,并且为映射器 OrderItemmapper.xml 中添加对应的 SQL 语句。OrderItemMapper 接口中的方法:

List<OrderItemDO> selectOrderItemByOrderId(@Param("orderId") Integer orderId);

映射器 OrderItemmapper.xml 中对应的 SQL 语句和映射规则:

<resultMap id="BaseResultMap" type="com.wyz.entity.OrderItemDO">
  <id property="itemId" column="item_id" jdbcType="INTEGER"/>
  <result property="orderId" column="order_id" jdbcType="INTEGER"/>
  <result property="commodityId" column="commodity_id" jdbcType="INTEGER"/>
  <result property="commodityPrice" column="commodity_price" jdbcType="DECIMAL"/>
  <result property="commodityCount" column="commodity_count" jdbcType="INTEGER"/>
</resultMap>

<select id="selectOrderItemByOrderId" resultMap="BaseResultMap">
  select * from order_item where order_id = #{orderId, jdbcType=INTEGER}
</select>

接着我们为 UserOrderMapper 接口中添加查询方法,并且为映射器 UserOrderMapper.xml 中添加对应的 SQL 语句。UserOrderMapper 接口中的方法:

UserOrderDO selectUserOrderAndOrderItemsByOrderNoNest(@Param("orderNo") String orderNo);

映射器 UserOrderMapper.xml 中对应的 SQL 语句:

<select id="selectUserOrderAndOrderItemsByOrderNoNest" resultMap="userOrderContainOrderItemNestMap">
  select * from user_order where order_no = #{orderNo, jdbcType=VARCHAR}
</select>

最后我们来定义映射器 UserOrderMapper.xml 中的映射规则“userOrderContainOrderItemNestMap”,如下:

<resultMap id="userOrderContainOrderItemNestMap" type="com.wyz.entity.UserOrderDO">
  <id property="orderId" column="order_id" jdbcType="INTEGER"/>
  <result property="userId" column="user_id" jdbcType="INTEGER"/>
  <result property="orderNo" column="order_no" jdbcType="VARCHAR"/>
  <result property="orderPrice" column="order_price" jdbcType="DECIMAL"/>
  <result property="orderStatus" column="order_status" jdbcType="INTEGER"/>
  <result property="createDate" column="create_date" jdbcType="DATE"/>
  <result property="payDate" column="pay_date" jdbcType="DATE"/>
  <collection property="orderItems"
    javaType="java.util.ArrayList"
    ofType="com.wyz.entity.OrderItemDO"
    select="com.wyz.mapper.OrderItemMapper.selectOrderItemByOrderId"
    column="{orderId=order_id}"/>
</resultMap>

这样我们就在 MyBatis 中实现了一对多的嵌套查询。

多对多查询

多对多的查询与一对多查询本质上并没有什么区别。比如说我们可以通过用户 ID 查询出所有用的订单以及订单明细信息,这就是种多对多的场景。

我们为 UserOrderMapper 接口中添加查询方法,并且为映射器 UserOrderMapper.xml 中添加对应的 SQL 语句。UserOrderMapper 接口中的方法:

List<UserOrderDO> selectUserOrderByUserIdNest(@Param("userId")Integer userId);

映射器 UserOrderMapper.xml 中对应的 SQL 语句:

<select id="selectUserOrderByUserIdNest" resultMap="userOrderContainOrderItemNestMap">
  select * from user_order where user_id = #{userId,jdbcType=INTEGER}
</select>

我们不需要要修改映射规则“userOrderContainOrderItemNestMap”就可以实现多对多的查询。除了这种方式之外,还可以将多个一对多的映射规则组合起来,就形成了多对多查询。


尾图(二维码).png

  • 14
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

技术范王有志

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值