中间表过滤器
两种场景
Jimmer支持中间表过滤器,有两种场景
1. 双边实体类型相同的多个关联
- Java
- Kotlin
@Entity
public interface Shop {
@Id
long id();
String name();
@ManyToMany
@JoinTable(
name = "SHOP_CUSTOMER_MAPPING",
joinColumnName = "SHOP_ID",
inverseJoinColumnName = "CUSTOMER_ID",
readonly = true
)
List<Customer> customers();
@ManyToMany
@JoinTable(
name = "SHOP_CUSTOMER_MAPPING",
joinColumnName = "SHOP_ID",
inverseJoinColumnName = "CUSTOMER_ID",
filter = @JoinTable.JoinTableFilter(
columnName = "MAPPING_TYPE",
type = String.class,
values = "VIP"
)
)
List<Customer> vipCustomers();
@ManyToMany
@JoinTable(
name = "SHOP_CUSTOMER_MAPPING",
joinColumnName = "SHOP_ID",
inverseJoinColumnName = "CUSTOMER_ID",
filter = @JoinTable.JoinTableFilter(
columnName = "MAPPING_TYPE",
type = String.class,
values = "ORDINARY"
)
)
List<Customer> ordinaryCustomers();
}
@Entity
interface Shop {
@Id
val id: Long
val name: String
@ManyToMany
@JoinTable(
name = "SHOP_CUSTOMER_MAPPING",
joinColumnName = "SHOP_ID",
inverseJoinColumnName = "CUSTOMER_ID",
readonly = true
)
val customers: List<Customer>
@ManyToMany
@JoinTable(
name = "SHOP_CUSTOMER_MAPPING",
joinColumnName = "SHOP_ID",
inverseJoinColumnName = "CUSTOMER_ID",
filter = JoinTable.JoinTableFilter(
columnName = "MAPPING_TYPE",
type = String.class,
values = "VIP"
)
)
val vipCustomers: List<Customer>
@ManyToMany
@JoinTable(
name = "SHOP_CUSTOMER_MAPPING",
joinColumnName = "SHOP_ID",
inverseJoinColumnName = "CUSTOMER_ID",
filter = JoinTable.JoinTableFilter(
columnName = "MAPPING_TYPE",
type = String.class,
values = "ORDINARY"
)
)
val ordinaryCustomers: List<Customer>
}
这个例子中,Shop.customers
,Shop.vipCustomers
和Shop.ordinaryCustomers
共享一张中间表
create table SHOP_CUSTOMER_MAPPING(
SHOP_ID bigint not null,
CUSTOMER_ID bigint not null,
MAPPING_TYPE varchar(8) not null
);
// 中间表主键是否包含过滤字段,需要具体问题具体分析。
// 在这个例子中,一个`Shop`和一个`Customer`之间的关联
// 不能既是"VIP"又是"ORDINARY",
// 所以,主键约束不包含`MAPPING_TYPE`
alter table SHOP_CUSTOMER_MAPPING
add constraint pk_SHOP_CUSTOMER_MAPPING
primary key(SHOP_ID, CUSTOMER_ID);
alter table SHOP_CUSTOMER_MAPPING
add constraint fk_SHOP_CUSTOMER_MAPPING__SHOP
foreign key(SHOP_ID)
references SHOP(ID);
alter table SHOP_CUSTOMER_MAPPING
add constraint fk_SHOP_CUSTOMER_MAPPING__CUSTOMER
foreign key(CUSTOMER_ID)
references CUSTOMER(ID);
alter table SHOP_CUSTOMER_MAPPING
add constraint fk_SHOP_CUSTOMER_MAPPING__MAPPING_TYPE
check(MAPPING_TYPE in ('VIP', 'ORDINARY'));
该表以如下方式存储数据
SHOP_ID | CUSTOMER_ID | MAPPING_TYPE |
---|---|---|
21 | 3176 | VIP |
21 | 1087 | ORDINARY |
21 | 9155 | ORDINARY |
40 | 8347 | VIP |
40 | 4365 | ORDINARY |
40 | 3478 | ORDINARY |
其中,Shop.vipCustomers
对应两条数据,Shop.ordinaryCustomers
对应4条数据,而Shop.customers
可以看到所有数据,即可。
Shop.customers = Shop.vipCustomers + Shop.ordinaryCustomers
可见,Shop.customers
并非原始关联,而是其他多个关联的并集。我们不妨称其为并集关联。
警告
并集关联不能被直接修改,因此必须声明readonly = true
。
其实,并集关联Shop.customers
还有另外一种等价的写法
- Java
- Kotlin
@ManyToMany
@JoinTable(
...省略其他参数...,
filter = @JoinTable.JoinTableFilter(
columnName = "MAPPING_TYPE",
type = String.class,
values = {"VIP", "ORDINARY"}
)
)
List<Customer> customers();
@ManyToMany
@JoinTable(
...省略其他参数...,
filter = @JoinTable.JoinTableFilter(
columnName = "MAPPING_TYPE",
type = String.class,
values = {"VIP", "ORDINARY"}
)
)
val customers: List<Customer>
最后,让我们看看这三个关联的JOIN行为的差异
-
Shop.customers
- Java
- Kotlin
ShopTable table = Tables.SHOP_TABLE;
List<Long> shopIds = sqlClient
.create(table)
.where(
table
.asTableEx()
.customers()
.name().eq("Smith")
)
.select(table.id())
.distinct()
.execute();val shopIds = sqlClient
.create(Shop::class) {
where(
table
.asTableEx()
.customers
.name eq "Smith"
)
select(table.id())
}
.distinct()
.execute();最终生成的SQL为
select distinct tb_1_.ID, tb_1_.NAME
from SHOP tb_1_
inner join SHOP_CUSTOMER_MAPPING tb_2_
on tb_1_.ID = tb_2_.SHOP_ID
inner join CUSTOMER tb_3_
on tb_2_.CUSTOMER_ID = tb_3_.ID
where tb_3_.NAME = ? /* Smith */ -
Shop.vipCustomers
- Java
- Kotlin
ShopTable table = Tables.SHOP_TABLE;
List<Long> shopIds = sqlClient
.create(table)
.where(
table
.asTableEx()
.vipCustomers()
.name().eq("Smith")
)
.select(table.id())
.distinct()
.execute();val shopIds = sqlClient
.create(Shop::class) {
where(
table
.asTableEx()
.vipCustomers
.name eq "Smith"
)
select(table.id())
}
.distinct()
.execute();最终生成的SQL为
select distinct tb_1_.ID, tb_1_.NAME
from SHOP tb_1_
inner join SHOP_CUSTOMER_MAPPING tb_2_
on tb_1_.ID = tb_2_.SHOP_ID
and tb_2_.MAPPING_TYPE = ? /* VIP */
and
tb_2_.type = ?
inner join CUSTOMER tb_3_
on tb_2_.CUSTOMER_ID = tb_3_.ID
where tb_3_.NAME = ? /* Smith */ -
Shop.ordinaryCustomers
- Java
- Kotlin
ShopTable table = Tables.SHOP_TABLE;
List<Long> shopIds = sqlClient
.create(table)
.where(
table
.asTableEx()
.ordinaryCustomers()
.name().eq("Smith")
)
.select(table.id())
.distinct()
.execute();val shopIds = sqlClient
.create(Shop::class) {
where(
table
.asTableEx()
.ordinaryCustomers
.name eq "Smith"
)
select(table.id())
}
.distinct()
.execute();最终生成的SQL为
select distinct tb_1_.ID, tb_1_.NAME
from SHOP tb_1_
inner join SHOP_CUSTOMER_MAPPING tb_2_
on tb_1_.ID = tb_2_.SHOP_ID
and tb_2_.MAPPING_TYPE = ? /* ORDINARY */
and
tb_2_.type = ?
inner join CUSTOMER tb_3_
on tb_2_.CUSTOMER_ID = tb_3_.ID
where tb_3_.NAME = ? /* Smith */