Appearance
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指定子查询的参数,注意此时参数名称变了:javaList<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)]