Dissociation Operations
When saving associated objects using the association mode AssociatedSaveMode.REPLACE
,
there is an important concept: dissociation operation.
Concept
Existing Database Structure | User's Expected Data Structure |
---|---|
|
|
-
For
Bread
, it exists in both old and new data structures, corresponding to an UPDATE operation -
For
Drinks
, it doesn't exist in the old data structure but exists in the new data, corresponding to an INSERT operation -
For
Meat
, it exists in the old data structure but not in the new data, corresponding to an operation called dissociation operation.
Dissociation applies to two types of associations:
-
Middle table associations
Obviously, middle table associations refer to associations like
Book.authors
andAuthor.books
in this tutorial.Their dissociation operation is very simple, just deleting the association in the middle table, without affecting the associated objects themselves.
-
Child table associations
Child table associations refer to inverse associations based on foreign keys (whether real or fake), such as
BookStore.books
in this tutorial.Their dissociation operation is relatively complex. Developers can control specific dissociation behavior through configuration.
Next, let's discuss these two types of dissociation operations.
Dissociating Middle Table Associations
We've already demonstrated this simplest form of dissociation when introducing the REPLACE save mode for associated objects.
Therefore, we won't repeat it here.
Dissociating Child Table Associations
Child table associations refer to inverse associations based on foreign keys (whether real or fake), such as BookStore.books
.
Their dissociation operation is relatively complex. Developers can configure the dissociation mode for properties corresponding to foreign keys (such as Book.store
here) to achieve different dissociation behaviors.
Dissociation Modes
There are 5 modes for child object dissociation operations
Mode | Description |
---|---|
NONE (Default) | Depends on global configuration jimmer.default-dissociate-action-checking
|
LAX | Dissociation operation performs no action.
|
CHECK | Does not support dissociation operations. Throws exception to prevent operation if current parent object in database has child objects that need to be dissociated. |
SET_NULL | Sets the foreign key of dissociated child objects to null. This mode requires that the child object's foreign key property is nullable; otherwise attempting this configuration will cause an exception. |
DELETE | Deletes the dissociated child objects. |
There are two ways to configure dissociation mode:
-
Static configuration using annotations on entities, which is global.
-
Dynamic configuration in code, which can override static configuration but only affects the current save command.
Taking SET_NULL
as an example:
-
Static entity-based configuration (chosen in most cases, used by the majority of business logic)
- Java
- Kotlin
Book.java@Entity
public interface Book {
@OnDissociate(DissociateAction.SET_NULL)
@Nullable
@ManyToOne
BookStore store();
...omitted other code...
}Book.kt@Entity
interface Book {
@OnDissociate(DissociateAction.SET_NULL)
@ManyToOne
val store: BookStore?
...omitted other code...
} -
Dynamic save command-based configuration (only for individual save commands, used by very few businesses with special requirements)
- Java
- Kotlin
sqlClient
.getEntities()
.saveCommand(book)
.setDissociateAction(
BookProps.STORE,
DissociateAction.SET_NULL
)
.execute();sqlClient.save(book) {
setDissociateAction(
Book::store,
DissociateAction.SET_NULL
)
}
Save Code
Next, let's explain various dissociation modes through saving data structures containing one-to-many association BookStore.books
.
Here's the save code:
- Java
- Kotlin
List<BookStore> stores = Arrays.asList(
Immutables.createBookStore(draft -> {
draft.setName("O'REILLY");
draft.addIntoBooks(book -> {
book.setName("Learning GraphQL");
book.setEdition(3);
book.setPrice(new BigDecimal("51.9"));
});
draft.addIntoBooks(book -> {
book.setName("Learning GraphQL");
book.setEdition(4);
book.setPrice(new BigDecimal("43.9"));
});
draft.addIntoBooks(book -> {
book.setName("Effective TypeScript");
book.setEdition(3);
book.setPrice(new BigDecimal("88.9"));
});
draft.addIntoBooks(book -> {
book.setName("Effective TypeScript");
book.setEdition(4);
book.setPrice(new BigDecimal("85.9"));
});
draft.addIntoBooks(book -> {
book.setName("Programming TypeScript");
book.setEdition(3);
book.setPrice(new BigDecimal("48.9"));
});
draft.addIntoBooks(book -> {
book.setName("Programming TypeScript");
book.setEdition(4);
book.setPrice(new BigDecimal("47.9"));
});
}),
Immutables.createBookStore(draft -> {
draft.setName("MANNING");
draft.addIntoBooks(book -> {
book.setName("GraphQL in Action");
book.setEdition(3);
book.setPrice(new BigDecimal("80.9"));
});
draft.addIntoBooks(book -> {
book.setName("GraphQL in Action");
book.setEdition(4);
book.setPrice(new BigDecimal("81.9"));
});
})
);
sqlClient.saveEntities(
stores,
// This parameter can be omitted, for the `save` method,
// AssociatedSaveMode defaults to REPLACE
AssociatedSaveMode.REPLACE
);
val stores = listOf(
BookStore {
name = "O'REILLY"
books().addBy {
name = "Learning GraphQL"
edition = 3
price = BigDecimal("51.9")
}
books().addBy {
name = "Learning GraphQL"
edition = 4
price = BigDecimal("32.9")
}
books().addBy {
name = "Effective TypeScript"
edition = 3
price = BigDecimal("88.9")
}
books().addBy {
name = "Effective TypeScript"
edition = 4
price = BigDecimal("85.9")
}
books().addBy {
name = "Programming TypeScript"
edition = 3
price = BigDecimal("48.9")
}
books().addBy {
name = "Programming TypeScript"
edition = 4
price = BigDecimal("47.9")
}
},
BookStore {
name = "MANNING"
books().addBy {
name = "GraphQL in Action"
edition = 3
price = BigDecimal("80.9")
}
books().addBy {
name = "GraphQL in Action"
edition = 4
price = BigDecimal("81.9")
}
}
)
sqlClient.saveEntities(
stores,
// This parameter can be omitted, for the `save` method,
// AssociatedSaveMode defaults to REPLACE
AssociatedSaveMode.REPLACE
)
Comparison between old and new objects:
Existing Database Data | Data User Wants to Save |
---|---|
|
|
|
|
Among them, the 8 associated objects that are no longer needed, i.e., the 8 objects that need to be dissociated, are highlighted.
So, how will Jimmer dissociate these associated objects?
In fact, different dissociation configurations will lead to different dissociation behaviors.
1. NONE (Default)
NONE's behavior is not fixed, but depends on the global configuration jimmer.default-dissociate-action-check
:
-
If
jimmer.default-dissociate-action-check
is true (default) or if the current association is based on a real foreign key (foreign key constraint exists in database, see Real vs Fake Foreign Keys), treated as CHECK. -
If
jimmer.default-dissociate-action-check
is false and the current association is based on a fake foreign key (no corresponding foreign key constraint in database, see Real vs Fake Foreign Keys), treated as LAX.
2. LAX
-
Dissociate mode
LAX
means the dissociation operation does nothing. -
Associated save mode
AssociatedSaveMode.REPLACE
means unnecessary associations need to be discarded.
Clearly, these two rules conflict. In this case, AssociatedSaveMode.REPLACE
takes precedence, LAX
is ignored, and it's ultimately treated as CHECK.
Therefore, we cannot demonstrate LAX
through this article. To understand how LAX
works, please see Delete Command.
3. CHECK
CHECK
mode does not allow dissociating associated objects. Jimmer will check if there are objects that need to be dissociated, and if they exist, throws an exception.
The dissociate mode can be set either through the entity definition or through the save command.
Here, we set the dissociate mode for Book.store
through entity configuration:
- Java
- Kotlin
@Entity
@KeyUniqueConstraint(noMoreUniqueConstraints = true)
public interface Book {
@OnDissociate(DissociateAction.CHECK)
@Nullable
@ManyToOne
BookStore store();
...omitted other code...
}
@Entity
@KeyUniqueConstraint(noMoreUniqueConstraints = true)
interface Book {
@OnDissociate(DissociateAction.CHECK)
@ManyToOne
val store: BookStore?
...omitted other code...
}
To demonstrate simpler SQL generation, let's assume sqlClient
's targetTransferable
feature is enabled, which was previously discussed, see here
Running the save code from before will generate three SQL statements:
-
Saving the root object is not the focus of this article, collapsed by default
- H2
- Mysql
- Postgres
merge into BOOK_STORE(
NAME
) key(NAME) values(
?
)
/* batch-0: [MANNING] */
/* batch-1: [AMAZON] */caution默认情况下,MySQL的批量操作不会被采用,而采用多条SQL。具体细节请参考MySQL的问题
-
insert into BOOK_STORE(
NAME
) values(
? /* MANNING */
) on duplicate key update
/* fake update to return all ids */ ID = last_insert_id(ID) -
insert into BOOK_STORE(
NAME
) values(
? /* AMAZON */
) on duplicate key update
/* fake update to return all ids */ ID = last_insert_id(ID)
insert into BOOK_STORE(
NAME
) values(
?
) on conflict(
NAME
) do update set
/* fake update to return all ids */ NAME = execluded.NAME
returning ID
/* batch-0: [MANNING] */
/* batch-1: [AMAZON] */- Assume
MANNING
exists with an id of2
- Assume
AMAZON
exists, after insertion, the database automatically assigns a new id of100
-
Saving associations and associated objects is also not the focus of this article, collapsed by default
- H2
- Mysql
- Postgres
merge into BOOK(
NAME, EDITION, PRICE, STORE_ID
) key(
NAME, EDITION
) values(
?, ?, ?, ?
)
/* batch-0: [Learning GraphQL, 3, 51.9, 1] */
/* batch-1: [Learning GraphQL, 4, 43.9, 1] */
/* batch-2: [Effective TypeScript, 3, 88.9, 1] */
/* batch-3: [Effective TypeScript, 4, 85.9, 1] */
/* batch-4: [Programming TypeScript, 3, 48.9, 1] */
/* batch-5: [Programming TypeScript, 4, 47.9, 1] */
/* batch-6: [GraphQL in Action, 3, 80.9, 2] */
/* batch-7: [GraphQL in Action, 4, 81.9, 2] */cautionBy default, MySQL batch operations are not used. For specific details, please refer to MySQL Issues
-
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
? /* Learning GraphQL */,
? /* 3 */,
? /* 51.9 */,
? /* 1 */
) on duplicate key udpate
/* fake update to return all ids */ ID = last_insert_id(ID),
PRICE = values(PRICE),
STORE_ID = values(STORE_ID) -
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
? /* Learning GraphQL */,
? /* 4 */,
? /* 43.9 */,
? /* 1 */
) on duplicate key udpate
/* fake update to return all ids */ ID = last_insert_id(ID),
PRICE = values(PRICE),
STORE_ID = values(STORE_ID) -
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
? /* Effective TypeScript */,
? /* 3 */,
? /* 88.9 */,
? /* 1 */
) on duplicate key udpate
/* fake update to return all ids */ ID = last_insert_id(ID),
PRICE = values(PRICE),
STORE_ID = values(STORE_ID) -
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
? /* Effective TypeScript */,
? /* 4 */,
? /* 85.9 */,
? /* 1 */
) on duplicate key udpate
/* fake update to return all ids */ ID = last_insert_id(ID),
PRICE = values(PRICE),
STORE_ID = values(STORE_ID) -
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
? /* Programming TypeScript */,
? /* 3 */,
? /* 48.9 */,
? /* 1 */
) on duplicate key udpate
/* fake update to return all ids */ ID = last_insert_id(ID),
PRICE = values(PRICE),
STORE_ID = values(STORE_ID) -
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
? /* Programming TypeScript */,
? /* 4 */,
? /* 47.9 */,
? /* 1 */
) on duplicate key udpate
/* fake update to return all ids */ ID = last_insert_id(ID),
PRICE = values(PRICE),
STORE_ID = values(STORE_ID) -
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
? /* GraphQL in Action */,
? /* 3 */,
? /* 80.9 */,
? /* 2 */
) on duplicate key udpate
/* fake update to return all ids */ ID = last_insert_id(ID),
PRICE = values(PRICE),
STORE_ID = values(STORE_ID) -
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
? /* GraphQL in Action */,
? /* 4 */,
? /* 81.9 */,
? /* 2 */
) on duplicate key udpate
/* fake update to return all ids */ ID = last_insert_id(ID),
PRICE = values(PRICE),
STORE_ID = values(STORE_ID)
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
?, ?, ?, ?
) on conflict(
NAME, EDITION
) do update set
PRICE = excluded.PRICE,
STORE_ID = excluded.STORE_ID
returning ID
/* batch-0: [Learning GraphQL, 3, 51.9, 1] */
/* batch-1: [Learning GraphQL, 4, 43.9, 1] */
/* batch-2: [Effective TypeScript, 3, 88.9, 1] */
/* batch-3: [Effective TypeScript, 4, 85.9, 1] */
/* batch-4: [Programming TypeScript, 3, 48.9, 1] */
/* batch-5: [Programming TypeScript, 4, 47.9, 1] */
/* batch-6: [GraphQL in Action, 3, 80.9, 2] */
/* batch-7: [GraphQL in Action, 4, 81.9, 2] */ -
Check if there are objects that need to be dissociated (if any exist, throws exception to prevent save command)
select
tb_1_.ID
from BOOK tb_1_
where
tb_1_.STORE_ID in (
? /* 1 */, ? /* 2 */
)
and
(tb_1_.STORE_ID, tb_1_.ID) not in (
(? /* 1 */, ? /* 3 */),
(? /* 1 */, ? /* 100 */),
(? /* 1 */, ? /* 6 */),
(? /* 1 */, ? /* 101 */),
(? /* 1 */, ? /* 9 */),
(? /* 1 */, ? /* 102 */),
(? /* 2 */, ? /* 12 */),
(? /* 2 */, ? /* 103 */)
)
limit ? /* 1 */After this SQL check, some
Book
objects that will be dissociated are found through the savedBookStore
objects'Book.authors
property. However, they do not agree to be dissociated and throw the following exception:Save error caused by the path: "<root>.books":
Cannot dissociate child objects
because the
dissociation action of the many-to-one property
"com.yourcommany.yourproject.model.Book.store"
is not configured as "set null" or "cascade".
There are two ways to resolve this issue:
Decorate the many-to-one property
"com.yourcommany.yourproject.model.Bookstore" by
@org.babyfish.jimmer.sql.OnDissociate whose argument
is `DissociateAction.SET_NULL` or `DissociateAction.DELETE`,
or use save command's runtime configuration to override it
4. SET_NULL
SET_NULL
mode achieves dissociation by setting the associated object's foreign key property to null.
SET_NULL
mode requires that the foreign key-based property (here Book.store
) must be nullable, otherwise it will cause a compilation error
The dissociate mode can be set either through the entity definition or through the save command.
Here, we set the dissociate mode for Book.store
through entity configuration:
- Java
- Kotlin
@Entity
@KeyUniqueConstraint(noMoreUniqueConstraints = true)
public interface Book {
@OnDissociate(DissociateAction.SET_NULL)
@Nullable
@ManyToOne
BookStore store();
...omitted other code...
}
@Entity
@KeyUniqueConstraint(noMoreUniqueConstraints = true)
interface Book {
@OnDissociate(DissociateAction.SET_NULL)
@ManyToOne
val store: BookStore?
...omitted other code...
}
To demonstrate simpler SQL generation, let's assume sqlClient
's targetTransferable
feature is enabled, which was previously discussed, see here
Running the save code from before will generate three SQL statements:
-
Saving the root object is not the focus of this article, collapsed by default
- H2
- Mysql
- Postgres
merge into BOOK_STORE(
NAME
) key(NAME) values(
?
)
/* batch-0: [MANNING] */
/* batch-1: [AMAZON] */caution默认情况下,MySQL的批量操作不会被采用,而采用多条SQL。具体细节请参考MySQL的问题
-
insert into BOOK_STORE(
NAME
) values(
? /* MANNING */
) on duplicate key update
/* fake update to return all ids */ ID = last_insert_id(ID) -
insert into BOOK_STORE(
NAME
) values(
? /* AMAZON */
) on duplicate key update
/* fake update to return all ids */ ID = last_insert_id(ID)
insert into BOOK_STORE(
NAME
) values(
?
) on conflict(
NAME
) do update set
/* fake update to return all ids */ NAME = execluded.NAME
returning ID
/* batch-0: [MANNING] */
/* batch-1: [AMAZON] */- Assume
MANNING
exists with an id of2
- Assume
AMAZON
exists, after insertion, the database automatically assigns a new id of100
-
Saving associations and associated objects is also not the focus of this article, collapsed by default
- H2
- Mysql
- Postgres
merge into BOOK(
NAME, EDITION, PRICE, STORE_ID
) key(
NAME, EDITION
) values(
?, ?, ?, ?
)
/* batch-0: [Learning GraphQL, 3, 51.9, 1] */
/* batch-1: [Learning GraphQL, 4, 43.9, 1] */
/* batch-2: [Effective TypeScript, 3, 88.9, 1] */
/* batch-3: [Effective TypeScript, 4, 85.9, 1] */
/* batch-4: [Programming TypeScript, 3, 48.9, 1] */
/* batch-5: [Programming TypeScript, 4, 47.9, 1] */
/* batch-6: [GraphQL in Action, 3, 80.9, 2] */
/* batch-7: [GraphQL in Action, 4, 81.9, 2] */cautionBy default, MySQL batch operations are not used. For specific details, please refer to MySQL Issues
-
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
? /* Learning GraphQL */,
? /* 3 */,
? /* 51.9 */,
? /* 1 */
) on duplicate key udpate
/* fake update to return all ids */ ID = last_insert_id(ID),
PRICE = values(PRICE),
STORE_ID = values(STORE_ID) -
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
? /* Learning GraphQL */,
? /* 4 */,
? /* 43.9 */,
? /* 1 */
) on duplicate key udpate
/* fake update to return all ids */ ID = last_insert_id(ID),
PRICE = values(PRICE),
STORE_ID = values(STORE_ID) -
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
? /* Effective TypeScript */,
? /* 3 */,
? /* 88.9 */,
? /* 1 */
) on duplicate key udpate
/* fake update to return all ids */ ID = last_insert_id(ID),
PRICE = values(PRICE),
STORE_ID = values(STORE_ID) -
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
? /* Effective TypeScript */,
? /* 4 */,
? /* 85.9 */,
? /* 1 */
) on duplicate key udpate
/* fake update to return all ids */ ID = last_insert_id(ID),
PRICE = values(PRICE),
STORE_ID = values(STORE_ID) -
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
? /* Programming TypeScript */,
? /* 3 */,
? /* 48.9 */,
? /* 1 */
) on duplicate key udpate
/* fake update to return all ids */ ID = last_insert_id(ID),
PRICE = values(PRICE),
STORE_ID = values(STORE_ID) -
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
? /* Programming TypeScript */,
? /* 4 */,
? /* 47.9 */,
? /* 1 */
) on duplicate key udpate
/* fake update to return all ids */ ID = last_insert_id(ID),
PRICE = values(PRICE),
STORE_ID = values(STORE_ID) -
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
? /* GraphQL in Action */,
? /* 3 */,
? /* 80.9 */,
? /* 2 */
) on duplicate key udpate
/* fake update to return all ids */ ID = last_insert_id(ID),
PRICE = values(PRICE),
STORE_ID = values(STORE_ID) -
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
? /* GraphQL in Action */,
? /* 4 */,
? /* 81.9 */,
? /* 2 */
) on duplicate key udpate
/* fake update to return all ids */ ID = last_insert_id(ID),
PRICE = values(PRICE),
STORE_ID = values(STORE_ID)
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
?, ?, ?, ?
) on conflict(
NAME, EDITION
) do update set
PRICE = excluded.PRICE,
STORE_ID = excluded.STORE_ID
returning ID
/* batch-0: [Learning GraphQL, 3, 51.9, 1] */
/* batch-1: [Learning GraphQL, 4, 43.9, 1] */
/* batch-2: [Effective TypeScript, 3, 88.9, 1] */
/* batch-3: [Effective TypeScript, 4, 85.9, 1] */
/* batch-4: [Programming TypeScript, 3, 48.9, 1] */
/* batch-5: [Programming TypeScript, 4, 47.9, 1] */
/* batch-6: [GraphQL in Action, 3, 80.9, 2] */
/* batch-7: [GraphQL in Action, 4, 81.9, 2] */ -
Set foreign keys to null for all objects being dissociated
- H2
- MySQL
- Postgres
update BOOK
set STORE_ID = null
where
STORE_ID = ?
and
not (
ID = any(?)
)
/* batch-0: [1, [3, 100, 6, 101, 9, 102]] */
/* batch-1: [2, [12, 103]] */update BOOK
set STORE_ID = null
where
STORE_ID in (
? /* 1 */, ? /* 2 */
)
and
(STORE_ID, ID) not in (
(? /* 1 */, ? /* 3 */),
(? /* 1 */, ? /* 100 */),
(? /* 1 */, ? /* 6 */),
(? /* 1 */, ? /* 101 */),
(? /* 1 */, ? /* 9 */),
(? /* 1 */, ? /* 102 */),
(? /* 2 */, ? /* 12 */),
(? /* 2 */, ? /* 103 */)
)update BOOK
set STORE_ID = null
where
STORE_ID = ?
and
not (
ID = any(?)
)
/* batch-0: [1, [3, 100, 6, 101, 9, 102]] */
/* batch-1: [2, [12, 103]] */We can see significant differences between databases:
H2
andPostgres
use batched simple SQLMySQL
uses a single complex SQL
infoThis is because
H2
andPostgres
support the=any(array)
syntax, while MySQL does not.
5. DELETE
DELETE
mode achieves dissociation by deleting the associated objects.
Here when saving BookStore
, we perform dissociation on the BookStore.books
association, where DELETE
mode indicates we want to delete unnecessary Book
objects.
However, since the Book
entity has a deeper association Book.authors
, we need to consider how to dissociate Author
objects before deleting Book
objects.
In fact, if objects that need to be deleted due to dissociation have deeper associations, it evolves into a recursive deletion problem. This behavior is detailed in Delete Command, which we won't discuss here.
Therefore, to simplify the discussion, we'll temporarily ignore the Author
entity and assume the current system only has BookStore
and Book
entity types.
The dissociate mode can be set either through the entity definition or through the save command.
Here, we set the dissociate mode for Book.store
through entity configuration:
- Java
- Kotlin
@Entity
@KeyUniqueConstraint(noMoreUniqueConstraints = true)
public interface Book {
@OnDissociate(DissociateAction.DELETE)
@Nullable
@ManyToOne
BookStore store();
...omitted other code...
}
@Entity
@KeyUniqueConstraint(noMoreUniqueConstraints = true)
interface Book {
@OnDissociate(DissociateAction.DELETE)
@ManyToOne
val store: BookStore?
...omitted other code...
}
To demonstrate simpler SQL generation, let's assume sqlClient
's targetTransferable
feature is enabled, which was previously discussed, see here
Running the save code from before will generate three SQL statements:
-
Saving the root object is not the focus of this article, collapsed by default
- H2
- Mysql
- Postgres
merge into BOOK_STORE(
NAME
) key(NAME) values(
?
)
/* batch-0: [MANNING] */
/* batch-1: [AMAZON] */caution默认情况下,MySQL的批量操作不会被采用,而采用多条SQL。具体细节请参考MySQL的问题
-
insert into BOOK_STORE(
NAME
) values(
? /* MANNING */
) on duplicate key update
/* fake update to return all ids */ ID = last_insert_id(ID) -
insert into BOOK_STORE(
NAME
) values(
? /* AMAZON */
) on duplicate key update
/* fake update to return all ids */ ID = last_insert_id(ID)
insert into BOOK_STORE(
NAME
) values(
?
) on conflict(
NAME
) do update set
/* fake update to return all ids */ NAME = execluded.NAME
returning ID
/* batch-0: [MANNING] */
/* batch-1: [AMAZON] */- Assume
MANNING
exists with an id of2
- Assume
AMAZON
exists, after insertion, the database automatically assigns a new id of100
-
Saving associations and associated objects is also not the focus of this article, collapsed by default
- H2
- Mysql
- Postgres
merge into BOOK(
NAME, EDITION, PRICE, STORE_ID
) key(
NAME, EDITION
) values(
?, ?, ?, ?
)
/* batch-0: [Learning GraphQL, 3, 51.9, 1] */
/* batch-1: [Learning GraphQL, 4, 43.9, 1] */
/* batch-2: [Effective TypeScript, 3, 88.9, 1] */
/* batch-3: [Effective TypeScript, 4, 85.9, 1] */
/* batch-4: [Programming TypeScript, 3, 48.9, 1] */
/* batch-5: [Programming TypeScript, 4, 47.9, 1] */
/* batch-6: [GraphQL in Action, 3, 80.9, 2] */
/* batch-7: [GraphQL in Action, 4, 81.9, 2] */cautionBy default, MySQL batch operations are not used. For specific details, please refer to MySQL Issues
-
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
? /* Learning GraphQL */,
? /* 3 */,
? /* 51.9 */,
? /* 1 */
) on duplicate key udpate
/* fake update to return all ids */ ID = last_insert_id(ID),
PRICE = values(PRICE),
STORE_ID = values(STORE_ID) -
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
? /* Learning GraphQL */,
? /* 4 */,
? /* 43.9 */,
? /* 1 */
) on duplicate key udpate
/* fake update to return all ids */ ID = last_insert_id(ID),
PRICE = values(PRICE),
STORE_ID = values(STORE_ID) -
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
? /* Effective TypeScript */,
? /* 3 */,
? /* 88.9 */,
? /* 1 */
) on duplicate key udpate
/* fake update to return all ids */ ID = last_insert_id(ID),
PRICE = values(PRICE),
STORE_ID = values(STORE_ID) -
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
? /* Effective TypeScript */,
? /* 4 */,
? /* 85.9 */,
? /* 1 */
) on duplicate key udpate
/* fake update to return all ids */ ID = last_insert_id(ID),
PRICE = values(PRICE),
STORE_ID = values(STORE_ID) -
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
? /* Programming TypeScript */,
? /* 3 */,
? /* 48.9 */,
? /* 1 */
) on duplicate key udpate
/* fake update to return all ids */ ID = last_insert_id(ID),
PRICE = values(PRICE),
STORE_ID = values(STORE_ID) -
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
? /* Programming TypeScript */,
? /* 4 */,
? /* 47.9 */,
? /* 1 */
) on duplicate key udpate
/* fake update to return all ids */ ID = last_insert_id(ID),
PRICE = values(PRICE),
STORE_ID = values(STORE_ID) -
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
? /* GraphQL in Action */,
? /* 3 */,
? /* 80.9 */,
? /* 2 */
) on duplicate key udpate
/* fake update to return all ids */ ID = last_insert_id(ID),
PRICE = values(PRICE),
STORE_ID = values(STORE_ID) -
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
? /* GraphQL in Action */,
? /* 4 */,
? /* 81.9 */,
? /* 2 */
) on duplicate key udpate
/* fake update to return all ids */ ID = last_insert_id(ID),
PRICE = values(PRICE),
STORE_ID = values(STORE_ID)
insert into BOOK(
NAME, EDITION, PRICE, STORE_ID
) values(
?, ?, ?, ?
) on conflict(
NAME, EDITION
) do update set
PRICE = excluded.PRICE,
STORE_ID = excluded.STORE_ID
returning ID
/* batch-0: [Learning GraphQL, 3, 51.9, 1] */
/* batch-1: [Learning GraphQL, 4, 43.9, 1] */
/* batch-2: [Effective TypeScript, 3, 88.9, 1] */
/* batch-3: [Effective TypeScript, 4, 85.9, 1] */
/* batch-4: [Programming TypeScript, 3, 48.9, 1] */
/* batch-5: [Programming TypeScript, 4, 47.9, 1] */
/* batch-6: [GraphQL in Action, 3, 80.9, 2] */
/* batch-7: [GraphQL in Action, 4, 81.9, 2] */ -
Delete all objects that need to be dissociated
- H2
- MySQL
- Postgres
delete from BOOK
where
STORE_ID = ?
and
not (
ID = any(?)
)
/* batch-0: [1, [3, 100, 6, 101, 9, 102]] */
/* batch-1: [2, [12, 103]] */delete from BOOK
where
STORE_ID in (
? /* 1 */, ? /* 2 */
)
and
(STORE_ID, ID) not in (
(? /* 1 */, ? /* 3 */),
(? /* 1 */, ? /* 100 */),
(? /* 1 */, ? /* 6 */),
(? /* 1 */, ? /* 101 */),
(? /* 1 */, ? /* 9 */),
(? /* 1 */, ? /* 102 */),
(? /* 2 */, ? /* 12 */),
(? /* 2 */, ? /* 103 */)
)delete from BOOK
where
STORE_ID = ?
and
not (
ID = any(?)
)
/* batch-0: [1, [3, 100, 6, 101, 9, 102]] */
/* batch-1: [2, [12, 103]] */We can see significant differences between databases:
H2
andPostgres
use batched simple SQLMySQL
uses a single complex SQL
infoThis is because
H2
andPostgres
support the=any(array)
syntax, while MySQL does not.