直接修改中间表
简述
首先,中间表被对象模型隐藏,没有与之直接对应的实体类型。此问题已在查询中间表一文中被阐述,此处不作重复。
另外,在Save指令章节中,我们介绍了save指令。它能对比数据库现有数据和用户要保存的新数据,如果某个基于中间表的关联属性发生了变化,中间表就会被修改。
诚然,Save指令功能非常强大,对中间表的修改只是其中很小的一个功能点。然而,有时候,我们只需要简单地对中间表进行插入或删除操作,我们不需要Save指令那么强大的功能。
Jimmer允许开发人员用更简单的方式,直接对中间表的数据进行插入和删除。
插入关联
- Java
- Kotlin
sqlClient
.getAssociations(BookProps.AUTHORS)
.save(12L, 3L);
sqlClient
.getAssociations(Book::authors)
.save(12L, 3L)
生成的SQL如下
insert into
BOOK_AUTHOR_MAPPING(
/* highlight-next-line */
BOOK_ID, AUTHOR_ID
)
values (?, ?)
这个例子演示如何使用关联属性Book.authors
操作中间表。
同理我们也可以通过关联属性Authors.books
达到相同目的:
- Java
- Kotlin
sqlClient
.getAssociations(AuthorProps.BOOKS)
.save(3L, 12L);
sqlClient
.getAssociations(Author::books)
.save(3L, 12L)
注意,和上一个例子相比,因为关联的方向不同,所以save
方法的参数顺序不同。
生成的SQL如下
insert into
BOOK_AUTHOR_MAPPING(
/* highlight-next-line */
AUTHOR_ID, BOOK_ID
)
values (?, ?)
不难发现,insert语句列的顺序也不同。
逆变换
对于双向关联,可以使用reverse
在正反关联之间切换
- Java
- Kotlin
Associations bookToAuthor = sqlClient
.getAssociations(BookProps.AUTHORS);
Associations authorToBook = sqlClient
.getAssociations(AuthorProps.BOOKS);
Associations authorToBook2 =
bookToAuthor.reverse();
Associations bookToAuthor2 =
authorToBook.reverse();
val bookToAuthor =
sqlClient.getAssociations(
Book::authors
)
val authorToBook =
sqlClient.getAssociations(
Author::books
)
val authorToBook2 =
bookToAuthor.reverse()
val bookToAuthor2 =
authorToBook.reverse()
这段代码中:
bookToAuthor
和bookToAuthor2
完全等价authorToBook
和authorToBook2
完全等价
批量插入
- Java
- Kotlin
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)
)
);
sqlClient
.getAssociations(
Book::authors
)
.saveAll(
listOf(
Tuple2(10L, 1L),
Tuple2(11L, 1L),
Tuple2(12L, 1L),
Tuple2(10L, 3L),
Tuple2(11L, 3L),
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提供一种 快捷写法
- Java
- Kotlin
sqlClient
.getAssociations(BookProps.AUTHORS)
// 保存两个id集合的笛卡尔乘积
.saveAll(
Arrays.asList(
10L, 11L, 12L
),
Arrays.asList(
1L, 3L
)
);
sqlClient
.getAssociations(
Book::authors
)
// 保存两个id集合的笛卡尔乘积
.saveAll(
listOf(10L, 11L, 12L),
listOf(1L, 3L)
)
上面的saveAll
方法接受两个集合参数,把这两个集合形成的笛卡尔乘积插入中间表,所以,功能和上一个例子的相同。
检查存在性
如果向中间表插入已经存在的id元组,数据库会被报错,因为违背了唯一性约束。
为了解决这个问题,可以进行存在性检查。
- Java
- Kotlin
sqlClient
.getAssociations(BookProps.AUTHORS)
.saveAllCommand(
Arrays.asList(
10L, 11L, 12L
),
Arrays.asList(
1L, 3L
)
)
.checkExistence()
.execute();
sqlClient
.getAssociations(
Book::authors
)
.saveAll(
listOf(10L, 11L, 12L),
listOf(1L, 3L),
checkExistence = true
)
这里checkExistence()
表示先检查要插入的数据有哪些已经存在,然后只插入不存在的数据。
生成的SQL语句如下
-
检查哪些待插入数据已经存在数据
select
BOOK_ID, AUTHOR_ID
from BOOK_AUTHOR_MAPPING
where
(BOOK_ID, AUTHOR_ID) in(
(?, ?),
(?, ?),
(?, ?)
) -
利用上一步的查询结果,可以计算得到真正需要插入的数据。如果没有需要插入的数据,跳过本步骤;否则,执行本步骤。
这里,假设判断后真正需要插入的数据还剩两条,生成的SQL为:
insert into BOOK_AUTHOR_MAPPING(
BOOK_ID, AUTHOR_ID
) values
(?, ?),
(?, ?)
删除关联
- Java
- Kotlin
sqlClient
.getAssociations(BookProps.AUTHORS)
.delete(12L, 3L);
sqlClient
.getAssociations(
Book::authors
)
.delete(12L, 3L)
生成的SQL如下
delete from BOOK_AUTHOR_MAPPING
where
(BOOK_ID, AUTHOR_ID) in (
(?, ?)
)
批量删除
和批量插入类似,有两种批量删除的写法
- Java
- Kotlin
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(
Book::authors
)
.deleteAll(
listOf(
Tuple2(10L, 1L),
Tuple2(11L, 1L),
Tuple2(12L, 1L),
Tuple2(10L, 3L),
Tuple2(11L, 3L),
Tuple2(12L, 3L)
)
)
或
- Java
- Kotlin
sqlClient
.getAssociations(BookProps.AUTHORS)
.deleteAll(
Arrays.asList(10L, 11L, 12L),
Arrays.asList(1L, 3L)
);
sqlClient
.getAssociations(
Book::authors
)
.deleteAll(
listOf(10L, 11L, 12L),
listOf(1L, 3L)
)
生成的SQL如下
delete from BOOK_AUTHOR_MAPPING
where
(BOOK_ID, AUTHOR_ID) in (
(?, ?),
(?, ?)
)
注意事项
请不要滥用此功能,此功能仅适用于对中间表进行简单插入和删除的场合,即推荐仅在面临最简单的增量操作时才使用。
我们来看一个例子,如果需要和数据库现有数据做比对,该功能明显不是最优选择,代码会很繁琐,如下
- Java
- Kotlin
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
);
}
fun setAuthors(bookId: Long, authorIds: Collection<Long>) {
// Get old author ids
val oldAuthorIds = sqlClient.queries.forList(Book::authors) {
where(table.source.id eq bookId)
select(table.target.id)
}.execute().toSet()
// Delete `oldAuthorIds - authorIds`
sqlClient
.getAssociations(Book::authors)
.deleteAll(listOf(bookId), oldAuthorIds - authorIds)
// Insert `authorIds - oldAuthorIds`
sqlClient
.getAssociations(Book::authors)
.saveAll(listOf(bookId), authorIds - oldAuthorIds)
}
很明显,这样处理很繁琐。
当业务场景不再是简单的添加或删除时,Save指令才是更好的选择,如下
- Java
- Kotlin
public void setAuthors(long bookId, Collection<Long> authorIds) {
sqlClient.save(
Immutables.createBook(book -> {
book.setId(bookId);
book.setAuthorIds(authorIds);
})
);
}
fun setAuthors(bookId: Long, authorIds: Collection<Long>) {
sqlClient.save(
Book {
id = bookId
authorIds = authorIds
}
)
}