Skip to content

MyBatis-关系映射

1. 数据库表关系

在数据库中,存在以下三种关联关系:

  • 一对一关系:表 A 中的行在表 B 中至多有一个匹配,反之亦然。 例如,一个人至多只有一张有效护照。
  • 一对多关系:表A中的行在表B中有零个或多个匹配行,但是表B中的行在表A中只有一个匹配行。例如,一个人可以有多个订单,但是一个订单只会有一个买家。
  • 多对多关系:表A中的行在表B中有零个或多个匹配行,反之亦然。例如,一个学生可以有多个老师,一个老师也可以有多个学生。

2. MyBatis中的关联查询

在MyBatis中,实现关联查询有两种方式:

  • 通过连表(join),一次性查出所有的数据
  • 分步查询

现在以顾客和订单表为例,来演示对一关系和对多关系查询,首先准备表结构(以PostgreSQL数据库为例):

sql
CREATE TABLE t_order (
    id SERIAL PRIMARY KEY,
    goodsName VARCHAR not null,
    qty INTEGER not null,
    price money not null,
    amount money not null,
    customer_id INTEGER not null
);

create table t_customer(
    id serial primary key,
    name varchar not null,
    address varchar not null
);

顾客可以有多个订单,但是一个订单只会有一个顾客,两个表通过订单表中的customer_id列关联。

准备实体(需lombok支持)如下:

java
@Data
public class OrderBase {
    private int id;
    private String goodsName;
    private int qty;
    private BigDecimal price;
    private BigDecimal amount;
    private int customerId;
}
java
@Data
@ToString(callSuper = true)
public class Order extends OrderBase{
    private CustomerBase customerBase;
}
java
@Data
public class CustomerBase {
    private int id;
    private String name;
    private String address;
}
java
@Data
@ToString(callSuper = true)
public class Customer extends CustomerBase{
    private List<OrderBase> orderList;
}

2.1 连表查询

2.1.1 *对一关系

当我们查询订单时,也想同时查询该订单的顾客,可以通过如下方式实现:

xml
<resultMap id="OrderWithCustomer" type="org.example.entity.Order">
    <id column="id" property="id"/>
    <result column="goodsName" property="goodsName"/>
    <result column="qty" property="qty"/>
    <result property="price" column="price"/>
    <result property="amount" column="amount"/>
    <result property="customerId" column="customer_id"/>
    <association property="customerBase" javaType="org.example.entity.CustomerBase">
        <id column="customer_id" property="id"/>
        <result column="customer_name" property="name"/>
        <result column="customer_address" property="address"/>
    </association>
</resultMap>
<select id="getOrderById" resultMap="OrderWithCustomer">
    select
        o.*,
        c.id as customer_id,
        c.name as customer_name,
        c.address as customer_address
    from t_order o
    left join t_customer c
        on o.customer_id = c.id
    where o.id=#{id}
</select>

resultMap中,通过association指定唯一关系。

2.1.2 *对多关系

当我们查询顾客信息时,也想同时查出该顾客所有的订单,可以通过如下方式实现:

xml
<resultMap id="CustomerWithOrder" type="org.example.entity.Customer">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <result property="address" column="address"/>
    <collection property="orderList" ofType="org.example.entity.OrderBase">
        <id property="id" column="order_id"/>
        <result property="goodsName" column="goodsName"/>
        <result property="qty" column="qty"/>
        <result property="price" column="price"/>
        <result property="amount" column="amount"/>
        <result property="customerId" column="customer_id"/>
    </collection>
</resultMap>
<select id="getCustomer" resultMap="CustomerWithOrder">
    select
        c.id,
        c.name,
        c.address,
        o.id as order_id,
        o.goodsName as goodsName,
        o.qty as qty,
        o.price as price,
        o.amount as amount,
        o.customer_id as customer_id
    from t_customer c
    left join t_order o on c.id = o.customer_id
    where c.id = #{id}
</select>

resultMap中,通过collection指定对多关系。

2.2 分步查询

2.2.1 *对一关系

我们可以通过两个查询语句来分步查出所有数据:

xml
<resultMap id="OrderWithCustomerStep" type="org.example.entity.Order">
    <id column="id" property="id"/>
    <result column="goodsName" property="goodsName"/>
    <result column="qty" property="qty"/>
    <result property="price" column="price"/>
    <result property="amount" column="amount"/>
    <result property="customerId" column="customer_id"/>
    <association
            property="customerBase"
            select="org.example.mapper.CustomerMapper.getCustomerBaseById"
            column="{id=id}"
    >
    </association>
</resultMap>
<select id="getOrderByIdStep" resultMap="OrderWithCustomerStep">
    select * from t_order where id = #{id}
</select>

注意项:

  • select中指定第一次查询,我们只查出订单数据;
  • resultMap中,我们通过association中的select指定子查询,其值为全限定的查询名,通过column指定子查询的参数,格式是column="{prop1=col1,prop2=col2}",其中prop是参数名,col是第一次查询返回的列名。

2.2.2 *对多关系

xml
<resultMap id="CustomerWithOrderStep" type="org.example.entity.Customer">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <result property="address" column="address"/>
    <collection
            property="orderList"
            select="org.example.mapper.OrderMapper.getOrderListByCustomerId"
            column="{customerId=id}">
    </collection>
</resultMap>
<select id="getCustomerByIdStep" resultMap="CustomerWithOrderStep">
    select * from t_customer where id = #{id}
</select>

注意项:

  • select中指定第一次查询,我们只查出顾客数据;

  • resultMap中,我们通过association中的select指定子查询,注意此时是通过顾客ID查出订单数据,然后通过column指定子查询的参数,注意此时参数名称变了:

    java
    List<OrderBase> getOrderListByCustomerId(@Param("customerId") int customerId);

2.2.3 分步查询之懒加载

懒加载是指只有在需要时,才发起后续查询,而不是一开始就把所有数据查询出来。

我们可以通过MyBatis配置开启懒加载:

xml
<settings>
  	<!-- 打印日志 -->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
  	<!-- 开启懒加载 -->
    <setting name="lazyLoadingEnabled" value="true"/>
</settings>

然后测试程序如下,最开始只使用了顾客的名字,不涉及顾客的订单,所以最开始并没有查询订单数据,等了3秒后,需要打印顾客订单时,才发起订单查询:

java
@Test
void test06() throws InterruptedException {
    Customer customerByIdStep = customerMapper.getCustomerByIdStep(1);
    System.out.println(customerByIdStep.getName());

    System.out.println("------------------------");
    Thread.sleep(3000);

    System.out.println(customerByIdStep.getOrderList());
}

日志结果如下:

txt
==>  Preparing: select * from t_customer where id = ?
==> Parameters: 1(Integer)
<==    Columns: id, name, address
<==        Row: 1, 张三, 广州
<==      Total: 1
张三
------------------------
==>  Preparing: select * from t_order where customer_id = ?
==> Parameters: 1(Integer)
<==    Columns: id, goodsname, qty, price, amount, customer_id
<==        Row: 1, 塑料袋, 1, $0.10, $0.10, 1
<==        Row: 2, 苹果, 1, $5.00, $5.00, 1
<==      Total: 2
[OrderBase(id=1, goodsName=塑料袋, qty=1, price=0.10, amount=0.10, customerId=1), OrderBase(id=2, goodsName=苹果, qty=1, price=5.00, amount=5.00, customerId=1)]

参考资料

  1. MyBatis开启懒加载:https://mybatis.org/mybatis-3/configuration.html#settings
  2. MyBatis关联查询:https://mybatis.org/mybatis-3/sqlmap-xml.html#result-maps
  3. 尚硅谷视频教程:https://www.bilibili.com/video/BV14WtLeDEit/?p=145