跳到主要内容

合并冲突连接

使用方法

中,我们列举了两个场景,本文讨论是否如何解决这两个场景的问题。

备注

这个问题有两种解决方啊,本文演示第一种解决办法

  • 解决场景1的问题

    List<Book> findBooks(
    @Nullable String name,
    @Nullable String storeName,
    @Nullable String storeWebsite
    ) {
    BookTable table = Tables.BOOK_TABLE;

    return sqlClient
    .createQuery(table)
    .whereIf(
    name != null,
    () -> book.name().like(name)
    )
    .whereIf(
    storeName != null,
    () -> table
    .store()
    .name()
    .like(storeName)
    )
    .whereIf(
    storeWebsite != null,
    () -> table
    .store()
    .website()
    .like(storeWebsite)
    )
    .select(book)
    .execute();
    }

    这是一个典型的动态查询,三个查询参数都允许为null。

    • 指定name,但storeNamestoreWebsite仍然为null。

      这时,❶和❷两处的代码都不会执行,最终生成的SQL不会包含任何join。

      select 
      tb_1_.ID,
      tb_1_.NAME,
      tb_1_.EDITION,
      tb_1_.PRICE,
      tb_1_.STORE_ID
      from BOOK as tb_1_
      where tb_1_.NAME = ?
    • 指定namestoreName, 但storeWebsite仍然为null。

      这时,❶处的连接生效但❷处的代码不会被执行,最终生成的SQL如下。

      select 
      tb_1_.ID,
      tb_1_.NAME,
      tb_1_.EDITION,
      tb_1_.PRICE,
      tb_1_.STORE_ID
      from BOOK as tb_1_
      /* highlight-start */
      inner join BOOK_STORE as tb_2_
      on tb_1_.STORE_ID = tb_2_.ID
      /* highlight-end */
      where
      tb_1_.NAME = ?
      and
      tb_2_.NAME = ?
    • 指定所有参数,name, storeNamestoreWebsite都非null。

      这时,❶、❷两处的连接都生效,这种情况叫连接冲突。

      提示

      这种冲突并不会导致任何问题,冲突的join对象会被合并成一个join对象。

      因此,在最终生成的SQL,不会导致重复的join操作。

      select 
      tb_1_.ID,
      tb_1_.NAME,
      tb_1_.EDITION,
      tb_1_.PRICE,
      tb_1_.STORE_ID
      from BOOK as tb_1_
      /* highlight-start */
      inner join BOOK_STORE as tb_2_ on
      tb_1_.STORE_ID = tb_2_.ID
      /* highlight-end */
      where
      tb_1_.NAME = ?
      and
      tb_2_.NAME = ?
      and
      tb_2_.WEBSITE = ?
  • 解决场景2的问题

    List<Long> findDistinctIds(
    @Nullable Long aId,
    @Nullable Long bId,
    @Nullable Long cId,
    @Nullable Long dId,
    @Nullable Long eId
    ) {
    ATable table = Tables.A_TABLE;

    return sqlClient
    .createQuery(table)
    .whereIf(
    aId != null,
    () -> table.id().like(aId)
    )
    .whereIf(
    bId != null,
    () -> table.asTableEx().bs().id().like(bId)
    )
    .whereIf(
    cId != null,
    () -> table.asTableEx().bs().cs().id().like(cId)
    )
    .whereIf(
    dId != null,
    () -> table.asTableEx().bs().cs().ds().id().like(dId)
    )
    .whereIf(
    eId != null,
    () -> table.asTableEx().bs().cs().ds().es().id().like(eId)
    )
    .select(book.id())
    .distinct()
    .execute();
    }
    信息
    • 有了前面的基础,这里不再需要罗列不同的参数组合下会生成和何种SQL。明白无论如何最终SQL都不会包含重复的连接操作即可。

    • 这里的asTableEx是后续文档分页安全性要介绍的概念。这里,请读者先忽略它。

合并规则

假设有三个表连接路径,分别是

  • a -> b -> c -> d -> e -> f -> g
  • a -> b -> c -> h -> i -> j
  • a -> x -> y -> z -> a-> b -> c -> d

为了消除冲突,Jimmer会先把这些路径中的节点合并成一棵树

-+-a
|
+----+-b
| |
| \----+-c
| |
| +----+-d
| | |
| | \----+-e
| | |
| | \----+-f
| | |
| | \------g
| |
| \----+-h
| |
| \----+-i
| |
| \------j
|
\----+-x
|
\----+-y
|
\----+-z
|
\----+-a
|
\----+-b
|
\----+-c
|
\------d

然后根据这棵树来生成最终SQL中的join子句。

另外一个需要说明的规则,就是连接方式。创建join对象的方法具备参数,以指定连接方式,比如,左连接:

book.store(JoinType.LEFT)

连接方式合并规则如下:

  • 如果发生冲突的join节点的连接方式全部一样,合并后连接方式不变。
  • 否则,合并后一定是内连接。