跳到主要内容

直接修改中间表

简述

首先,中间表被对象模型隐藏,没有与之直接对应的实体类型。此问题已在查询中间表一文中被阐述,此处不作重复。

另外,在Save指令章节中,我们介绍了save指令。它能对比数据库现有数据和用户要保存的新数据,如果某个基于中间表的关联属性发生了变化,中间表就会被修改。

诚然,Save指令功能非常强大,对中间表的修改只是其中很小的一个功能点。然而,有时候,我们只需要简单地对中间表进行插入或删除操作,我们不需要Save指令那么强大的功能。

Jimmer允许开发人员用更简单的方式,直接对中间表的数据进行插入和删除。

插入关联

sqlClient
.getAssociations(BookProps.AUTHORS)
.save(12L, 3L);

生成的SQL如下

insert into 
BOOK_AUTHOR_MAPPING(
/* highlight-next-line */
BOOK_ID, AUTHOR_ID
)
values (?, ?)

这个例子演示如何使用关联属性Book.authors操作中间表。

同理我们也可以通过关联属性Authors.books达到相同目的:

sqlClient
.getAssociations(AuthorProps.BOOKS)
.save(3L, 12L);

注意,和上一个例子相比,因为关联的方向不同,所以save方法的参数顺序不同。

生成的SQL如下

insert into 
BOOK_AUTHOR_MAPPING(
/* highlight-next-line */
AUTHOR_ID, BOOK_ID
)
values (?, ?)

不难发现,insert语句列的顺序也不同。

逆变换

对于双向关联,可以使用reverse在正反关联之间切换

Associations bookToAuthor = sqlClient
.getAssociations(BookProps.AUTHORS);
Associations authorToBook = sqlClient
.getAssociations(AuthorProps.BOOKS);
Associations authorToBook2 =
bookToAuthor.reverse();
Associations bookToAuthor2 =
authorToBook.reverse();

这段代码中:

  • bookToAuthorbookToAuthor2完全等价
  • authorToBookauthorToBook2完全等价

批量插入

sqlClient
.getAssociations(BookProps.AUTHORS)
.saveAll(
Arrays.asList(
new Tuple2<>(10L, 1L),
new Tuple2<>(11L, 1L),
new Tuple2<>(12L, 1L),
new Tuple2<>(10L, 3L),
new Tuple2<>(11L, 3L),
new Tuple2<>(12L, 3L)
)
);

这里有3本书,两个作者,共6种组合方式。saveAll方法把这6种组合全部插入到中间表中,生成的SQL如下

insert into BOOK_AUTHOR_MAPPING(
BOOK_ID, AUTHOR_ID
) values
/* highlight-start */
(?, ?),
(?, ?),
(?, ?),
(?, ?),
(?, ?),
(?, ?)
/* highlight-end */

上面这种写法很繁琐,3 * 2 = 6还可以接受,但7 * 9 = 63呢?难道构建63个元组吗?

jimmer-sql提供一种快捷写法

sqlClient
.getAssociations(BookProps.AUTHORS)
// 保存两个id集合的笛卡尔乘积
.saveAll(
Arrays.asList(
10L, 11L, 12L
),
Arrays.asList(
1L, 3L
)
);

上面的saveAll方法接受两个集合参数,把这两个集合形成的笛卡尔乘积插入中间表,所以,功能和上一个例子的相同。

检查存在性

如果向中间表插入已经存在的id元组,数据库会被报错,因为违背了唯一性约束。

为了解决这个问题,可以进行存在性检查。

sqlClient
.getAssociations(BookProps.AUTHORS)
.saveAllCommand(
Arrays.asList(
10L, 11L, 12L
),
Arrays.asList(
1L, 3L
)
)
.checkExistence()
.execute();

这里checkExistence()表示先检查要插入的数据有哪些已经存在,然后只插入不存在的数据。

生成的SQL语句如下

  1. 检查哪些待插入数据已经存在数据

    select 
    BOOK_ID, AUTHOR_ID
    from BOOK_AUTHOR_MAPPING
    where
    (BOOK_ID, AUTHOR_ID) in(
    (?, ?),
    (?, ?),
    (?, ?)
    )
  2. 利用上一步的查询结果,可以计算得到真正需要插入的数据。如果没有需要插入的数据,跳过本步骤;否则,执行本步骤。

    这里,假设判断后真正需要插入的数据还剩两条,生成的SQL为:

    insert into BOOK_AUTHOR_MAPPING(
    BOOK_ID, AUTHOR_ID
    ) values
    (?, ?),
    (?, ?)

删除关联

sqlClient
.getAssociations(BookProps.AUTHORS)
.delete(12L, 3L);

生成的SQL如下

delete from BOOK_AUTHOR_MAPPING 
where
(BOOK_ID, AUTHOR_ID) in (
(?, ?)
)

批量删除

和批量插入类似,有两种批量删除的写法

sqlClient
.getAssociations(
BookTableEx.claBookProps.AUTHORS)
.deleteAll(
Arrays.asList(
new Tuple2<>(10L, 1L),
new Tuple2<>(11L, 1L),
new Tuple2<>(12L, 1L),
new Tuple2<>(10L, 3L),
new Tuple2<>(11L, 3L),
new Tuple2<>(12L, 3L),
)
);

sqlClient
.getAssociations(BookProps.AUTHORS)
.deleteAll(
Arrays.asList(10L, 11L, 12L),
Arrays.asList(1L, 3L)
);

生成的SQL如下

delete from BOOK_AUTHOR_MAPPING 
where
(BOOK_ID, AUTHOR_ID) in (
(?, ?),
(?, ?)
)

注意事项

警告

请不要滥用此功能,此功能仅适用于对中间表进行简单插入和删除的场合,即推荐仅在面临最简单的增量操作时才使用。

我们来看一个例子,如果需要和数据库现有数据做比对,该功能明显不是最优选择,代码会很繁琐,如下

不恰当用法示范
public void setAuthors(long bookId, Collection<Long> authorIds) {

// Get old author ids
AssociationTable<Book, BookTableEx, Author, AuthorTableEx> table =
AssociationTable.of(BookTableEx.class, BookTableEx::authors);
List<Long> oldAuthorIds = sqlClient
.createAssociationQuery(table)
.where(table.source().id().eq(1L))
.select(table.target().id())
.execute();

// Delete `oldAuthorIds - authorIds`
List<Long> detachedAuthorIds = new ArrayList<>(oldAuthorIds);
detachedAuthorIds.removeAll(authorIds);
sqlClient.getAssociations(BookProps.AUTHORS).deleteAll(
Collections.singleton(bookId),
detachedAuthorIds
);

// Insert `authorIds - oldAuthorIds`
List<Long> attachedAuthorIds = new ArrayList<>(authorIds);
attachedAuthorIds.removeAll(oldAuthorIds);
sqlClient.getAssociations(BookProps.AUTHORS).saveAll(
Collections.singleton(bookId),
attachedAuthorIds
);
}

很明显,这样处理很繁琐。

提示

当业务场景不再是简单的添加或删除时,Save指令才是更好的选择,如下

更好的用法示范
public void setAuthors(long bookId, Collection<Long> authorIds) {
sqlClient.save(
Objects.createBook(book -> {
book.setId(bookId);
book.setAuthorIds(authorIds);
})
);
}