HHH-17506 Add documentation for ON CONFLICT clause
This commit is contained in:
parent
bb4ed4b000
commit
81892127b3
|
@ -276,6 +276,11 @@ It's usually better to just use `persist()`.
|
||||||
On the other hand, you might consider using it to set up test data.
|
On the other hand, you might consider using it to set up test data.
|
||||||
====
|
====
|
||||||
|
|
||||||
|
[NOTE]
|
||||||
|
====
|
||||||
|
`insert` statements are not available in JPQL.
|
||||||
|
====
|
||||||
|
|
||||||
The BNF for an `insert` statement is:
|
The BNF for an `insert` statement is:
|
||||||
|
|
||||||
[[hql-insert-bnf-example]]
|
[[hql-insert-bnf-example]]
|
||||||
|
@ -331,9 +336,37 @@ It's not available for entities whose id generator is implemented in Java, nor f
|
||||||
The same two options are available for a `@Version` attribute.
|
The same two options are available for a `@Version` attribute.
|
||||||
When no version is explicitly specified, the version for a new entity instance is used.
|
When no version is explicitly specified, the version for a new entity instance is used.
|
||||||
|
|
||||||
|
To implement "upsert" semantics i.e. insert-or-update, the `on conflict` clause can be used.
|
||||||
|
Reacting on conflicts can be either based on the name or the list of attribute paths of a unique constraint.
|
||||||
|
Using the unique constraint name as conflict target requires either native database support,
|
||||||
|
which at the time of writing is only available in PostgreSQL, or that the statement is a single row insert.
|
||||||
|
A single row insert can be ensured by specifying only a single values tuple in case of an insert-values statement,
|
||||||
|
or using `fetch first 1 rows only` in case of an insert-select statement.
|
||||||
|
|
||||||
|
Possible conflict actions are to ignore the conflict or update conflicting objects/rows.
|
||||||
|
|
||||||
|
[[hql-insert-conflict-example]]
|
||||||
|
====
|
||||||
|
[source, SQL, indent=0]
|
||||||
|
----
|
||||||
|
include::{example-dir-hql}/../query/hql/InsertConflictTests.java[tags=hql-insert-conflict-example]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
The special alias `excluded` is available in the `update set` clause of the `conflict clause`
|
||||||
|
and refers to the values that failed insertion due to a unique constraint conflict.
|
||||||
|
|
||||||
[NOTE]
|
[NOTE]
|
||||||
====
|
====
|
||||||
`insert ... select` statements are not available in JPQL.
|
The MySQL/MariaDB implementation leverages the native `on duplicate key` clause
|
||||||
|
which does not support specifying an explicit column list or constraint name.
|
||||||
|
Beware that this implementation might produce different results than on other databases
|
||||||
|
if a table has more than a single unique constraint.
|
||||||
|
|
||||||
|
Another quirk of this implementation is that the MySQL/MariaDB JDBC driver returns surprising update counts.
|
||||||
|
For every row that is inserted, the update count is incremented by 1, but for rows that are updated,
|
||||||
|
the update count is incremented by 2.
|
||||||
|
To learn more about this, refer to the https://dev.mysql.com/doc/refman/8.0/en/insert-on-duplicate.html[MySQL documentation].
|
||||||
====
|
====
|
||||||
|
|
||||||
[[hql-literals]]
|
[[hql-literals]]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
insertStatement
|
insertStatement
|
||||||
: "INSERT" "INTO"? targetEntity targetFields (queryExpression | valuesList)
|
: "INSERT" "INTO"? targetEntity targetFields (queryExpression | valuesList) conflictClause?
|
||||||
|
|
||||||
targetEntity
|
targetEntity
|
||||||
: entityName variable?
|
: entityName variable?
|
||||||
|
@ -12,3 +12,14 @@ valuesList
|
||||||
|
|
||||||
values
|
values
|
||||||
: "(" expression ("," expression)* ")"
|
: "(" expression ("," expression)* ")"
|
||||||
|
|
||||||
|
conflictClause
|
||||||
|
: "on conflict" conflictTarget? conflictAction
|
||||||
|
|
||||||
|
conflictTarget
|
||||||
|
: "on constraint" identifier
|
||||||
|
| "(" simplePath ("," simplePath)* ")";
|
||||||
|
|
||||||
|
conflictAction
|
||||||
|
: "do nothing"
|
||||||
|
| "do update" setClause whereClause?
|
|
@ -1966,7 +1966,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
||||||
// To allow meaningful usage, we simply ignore the constraint column names in this emulation.
|
// To allow meaningful usage, we simply ignore the constraint column names in this emulation.
|
||||||
// A possible problem with this is when the constraint column names contain the primary key columns,
|
// A possible problem with this is when the constraint column names contain the primary key columns,
|
||||||
// but the insert fails due to a unique constraint violation. This emulation will not cause a failure to be
|
// but the insert fails due to a unique constraint violation. This emulation will not cause a failure to be
|
||||||
// propagated, but instead will run the respective conflict action. todo: document this
|
// propagated, but instead will run the respective conflict action.
|
||||||
final String constraintName = conflictClause.getConstraintName();
|
final String constraintName = conflictClause.getConstraintName();
|
||||||
if ( constraintName != null ) {
|
if ( constraintName != null ) {
|
||||||
throw new IllegalQueryOperationException( "Dialect does not support constraint name in conflict clause" );
|
throw new IllegalQueryOperationException( "Dialect does not support constraint name in conflict clause" );
|
||||||
|
|
|
@ -18,7 +18,6 @@ import org.hibernate.testing.orm.domain.StandardDomainModel;
|
||||||
import org.hibernate.testing.orm.domain.contacts.Contact;
|
import org.hibernate.testing.orm.domain.contacts.Contact;
|
||||||
import org.hibernate.testing.orm.domain.contacts.Contact.Name;
|
import org.hibernate.testing.orm.domain.contacts.Contact.Name;
|
||||||
import org.hibernate.testing.orm.domain.gambit.BasicEntity;
|
import org.hibernate.testing.orm.domain.gambit.BasicEntity;
|
||||||
import org.hibernate.testing.orm.junit.DialectFeatureCheck;
|
|
||||||
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
|
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
|
||||||
import org.hibernate.testing.orm.junit.DomainModel;
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
import org.hibernate.testing.orm.junit.JiraKey;
|
import org.hibernate.testing.orm.junit.JiraKey;
|
||||||
|
@ -90,12 +89,14 @@ public class InsertConflictTests {
|
||||||
public void testOnConflictDoUpdate(SessionFactoryScope scope) {
|
public void testOnConflictDoUpdate(SessionFactoryScope scope) {
|
||||||
scope.inTransaction(
|
scope.inTransaction(
|
||||||
session -> {
|
session -> {
|
||||||
|
//tag::hql-insert-conflict-example[]
|
||||||
int updated = session.createMutationQuery(
|
int updated = session.createMutationQuery(
|
||||||
"insert into BasicEntity (id, data) " +
|
"insert into BasicEntity (id, data) " +
|
||||||
"values (1, 'John') " +
|
"values (1, 'John') " +
|
||||||
"on conflict(id) do update " +
|
"on conflict(id) do update " +
|
||||||
"set data = excluded.data"
|
"set data = excluded.data"
|
||||||
).executeUpdate();
|
).executeUpdate();
|
||||||
|
//end::hql-insert-conflict-example[]
|
||||||
if ( scope.getSessionFactory().getJdbcServices().getDialect() instanceof MySQLDialect ) {
|
if ( scope.getSessionFactory().getJdbcServices().getDialect() instanceof MySQLDialect ) {
|
||||||
// Strange MySQL returns 2 if the conflict action updates a row
|
// Strange MySQL returns 2 if the conflict action updates a row
|
||||||
// Also see https://dev.mysql.com/doc/refman/8.0/en/insert-on-duplicate.html
|
// Also see https://dev.mysql.com/doc/refman/8.0/en/insert-on-duplicate.html
|
||||||
|
|
Loading…
Reference in New Issue