org.jooq.UpdatableRecord Maven / Gradle / Ivy
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* Apache-2.0 license and offer limited warranties, support, maintenance, and
* commercial database integrations.
*
* For more information, please visit: https://www.jooq.org/legal/licensing
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq;
// ...
// ...
// ...
import static org.jooq.SQLDialect.CUBRID;
// ...
import static org.jooq.SQLDialect.DERBY;
import static org.jooq.SQLDialect.FIREBIRD;
import static org.jooq.SQLDialect.H2;
// ...
import static org.jooq.SQLDialect.HSQLDB;
// ...
import static org.jooq.SQLDialect.MARIADB;
// ...
import static org.jooq.SQLDialect.MYSQL;
// ...
import static org.jooq.SQLDialect.POSTGRES;
import static org.jooq.SQLDialect.SQLITE;
// ...
// ...
// ...
import static org.jooq.SQLDialect.YUGABYTEDB;
import java.sql.Statement;
import java.util.Collection;
import org.jooq.conf.Settings;
import org.jooq.exception.DataAccessException;
import org.jooq.exception.DataChangedException;
import org.jooq.exception.NoDataFoundException;
import org.jooq.exception.TooManyRowsException;
import org.jetbrains.annotations.Blocking;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A common interface for records that can be stored back to the database again.
*
* Any {@link Record} can be updatable, if
*
*
* - it represents a record from a table or view - a {@link TableRecord}
* - its underlying table or view has a "main unique key", i.e. a primary key
* or at least one unique key
*
*
* The "main unique key" is used by jOOQ to perform the various operations that
* can be performed on an UpdatableRecord
:
*
*
* - {@link #delete()} : Deleting the record
* - {@link #refresh()} : Refreshing the records attributes (or loading it for
* the first time)
* - {@link #store()} : Storing the record to the database. This executes
* either an
INSERT
or an UPDATE
statement
* - {@link #merge()} : Merging a record to the database. This executes an
*
INSERT … ON DUPLICATE KEY UPDATE
statement.
*
*
* UpdatableRecords
are {@link Attachable}, which means that they
* hold an underlying {@link Configuration} that they can be detached from. They
* can also be instantiated without any underlying {@link Configuration}, in
* case of which they have to be attached first, in order to be refreshed,
* stored, or deleted.
*
* @param The record type
* @author Lukas Eder
*/
public interface UpdatableRecord> extends TableRecord {
/**
* A Record copy holding values for the {@link Table#getPrimaryKey()}.
*
* The returned record consists exactly of those fields as returned by the
* table's primary key: {@link UniqueKey#getFields()}.
*
* Generated subtypes may covariantly override this method to add more
* record type information. For instance, they may return {@link Record1},
* {@link Record2}, ...
*/
@NotNull
Record key();
/**
* Store this record back to the database.
*
* Depending on the state of the primary key's value, an {@link #insert()}
* or an {@link #update()} statement is executed.
*
*
Statement type
*
*
* - If this record was created by client code, an
INSERT
* statement is executed
* - If this record was loaded by jOOQ and the primary key value was
* changed, an
INSERT
statement is executed (unless
* {@link Settings#isUpdatablePrimaryKeys()} is set). jOOQ expects that
* primary key values will never change due to the principle of
* normalisation in RDBMS. So if client code changes primary key values,
* this is interpreted by jOOQ as client code wanting to duplicate this
* record.
* - If this record was loaded by jOOQ, and the primary key value was not
* changed, an
UPDATE
statement is executed.
*
*
* In either statement type, only those fields are inserted/updated, which
* had been explicitly set by client code, in order to allow for
* DEFAULT
values to be applied by the underlying RDBMS. If no
* fields were modified, neither an UPDATE
nor an
* INSERT
will be executed.
*
*
Automatic value generation
*
* Use {@link #insert()} or {@link #update()} to explicitly force either
* statement type.
*
*
* - IDENTITY columns
*
* If there is an IDENTITY
column defined on the record's
* underlying table (see {@link Table#getIdentity()}), then the
* auto-generated IDENTITY
value is refreshed automatically on
* INSERT
's. Refreshing is done using
* {@link Statement#getGeneratedKeys()}, where this is supported by the JDBC
* driver. See also {@link InsertQuery#getReturnedRecord()} for more details
*
* - VERSION and TIMESTAMP columns
*
* jOOQ can auto-generate "version" and "timestamp" values that can be used
* for optimistic locking. If this is an {@link UpdatableRecord} and if this
* record returns fields for either {@link Table#getRecordVersion()} or
* {@link Table#getRecordTimestamp()}, then these values are set onto the
* INSERT
or UPDATE
statement being executed. On
* execution success, the generated values are set to this record. Use the
* code-generation configuration to specify naming patterns for
* auto-generated "version" and "timestamp" columns.
*
* Should you want to circumvent jOOQ-generated updates to these columns,
* you can render an INSERT
or UPDATE
statement
* manually using the various {@link DSLContext#insertInto(Table)},
* {@link DSLContext#update(Table)} methods.
*
*
*
Optimistic locking
*
* If an UPDATE
statement is executed and
* {@link Settings#isExecuteWithOptimisticLocking()} is set to
* true
, then this record will first be compared with the
* latest state in the database. There are two modes of operation for
* optimistic locking:
*
* - With VERSION and/or TIMESTAMP columns configured
*
* This is the preferred way of using optimistic locking in jOOQ. If this is
* an {@link UpdatableRecord} and if this record returns fields for either
* {@link Table#getRecordVersion()} or {@link Table#getRecordTimestamp()},
* then these values are compared to the corresponding value in the database
* in the WHERE
clause of the executed DELETE
* statement.
* - Without any specific column configurations
*
* In order to compare this record with the latest state, the database
* record will be locked pessimistically using a
* SELECT … FOR UPDATE
statement. Not all databases support
* the FOR UPDATE
clause natively. Namely, the following
* databases will show slightly different behaviour:
*
* - {@link SQLDialect#SQLSERVER}: jOOQ will try to lock the database
* record using
WITH (ROWLOCK, UPDLOCK)
hints.
* - {@link SQLDialect#SQLITE}: No pessimistic locking is possible. Client
* code must assure that no race-conditions can occur between jOOQ's
* checking of database record state and the actual
UPDATE
*
*
* See {@link SelectQuery#setForUpdate(boolean)} for more details
*
*
*
Statement examples
*
* Possible statements are
*
*
* INSERT INTO [table] ([modified fields, including keys])
* VALUES ([modified values, including keys])
*
* UPDATE [table]
* SET [modified fields = modified values, excluding keys]
* WHERE [key fields = key values]
* AND [version/timestamp fields = version/timestamp values]
*
*
*
Statement execution enforcement
*
* If you want to control statement re-execution, regardless if the values
* in this record were changed, you can explicitly set the changed flags for
* all values with {@link #changed(boolean)} or for single values with
* {@link #changed(Field, boolean)}, prior to storing. Consider also setting
* the flags {@link Settings#getUpdateUnchangedRecords()} and/or
* {@link Settings#isInsertUnchangedRecords()} appropriately to control if
* the record should be "touched" without any changes (UPDATE
)
* or inserted with default values (INSERT
).
*
* This is the same as calling record.store(record.fields())
*
* @return 1
if the record was stored to the database. 0
*
if storing was not necessary.
* @throws DataAccessException if something went wrong executing the query
* @throws DataChangedException If optimistic locking is enabled and the
* record has already been changed/deleted in the database
* @see #insert()
* @see #update()
*/
@Support
int store() throws DataAccessException, DataChangedException;
/**
* Store parts of this record to the database.
*
* @return 1
if the record was stored to the database. 0
*
if storing was not necessary.
* @throws DataAccessException if something went wrong executing the query
* @throws DataChangedException If optimistic locking is enabled and the
* record has already been changed/deleted in the database
* @see #store()
* @see #insert(Field...)
* @see #update(Field...)
*/
@Support
int store(Field>... fields) throws DataAccessException, DataChangedException;
/**
* Store parts of this record to the database.
*
* @return 1
if the record was stored to the database. 0
*
if storing was not necessary.
* @throws DataAccessException if something went wrong executing the query
* @throws DataChangedException If optimistic locking is enabled and the
* record has already been changed/deleted in the database
* @see #store()
* @see #insert(Field...)
* @see #update(Field...)
*/
@Support
int store(Collection extends Field>> fields) throws DataAccessException, DataChangedException;
/**
* Store this record back to the database using an INSERT
* statement.
*
* This is the same as {@link #store()}, except that an INSERT
* statement (or no statement) will always be executed.
*
* If you want to enforce re-insertion this record's values, regardless if
* the values in this record were changed, you can explicitly set the
* changed flags for all values with {@link #changed(boolean)} or for single
* values with {@link #changed(Field, boolean)}, prior to insertion.
*
* This is the same as calling record.insert(record.fields())
*
* @return 1
if the record was stored to the database. 0
*
if storing was not necessary and
* {@link Settings#isInsertUnchangedRecords()} is set to false.
* @throws DataAccessException if something went wrong executing the query
* @see #store()
*/
@Support
@Override
int insert() throws DataAccessException;
/**
* Store parts of this record to the database using an INSERT
* statement.
*
* @return 1
if the record was stored to the database. 0
*
if storing was not necessary.
* @throws DataAccessException if something went wrong executing the query
* @see #insert()
*/
@Support
@Override
int insert(Field>... fields) throws DataAccessException;
/**
* Store parts of this record to the database using an INSERT
* statement.
*
* @return 1
if the record was stored to the database. 0
*
if storing was not necessary.
* @throws DataAccessException if something went wrong executing the query
* @see #insert()
*/
@Support
@Override
int insert(Collection extends Field>> fields) throws DataAccessException;
/**
* Store this record back to the database using an UPDATE
* statement.
*
* This is the same as {@link #store()}, except that an UPDATE
* statement (or no statement) will always be executed.
*
* If you want to enforce statement execution, regardless if the values in
* this record were changed, you can explicitly set the changed flags for
* all values with {@link #changed(boolean)} or for single values with
* {@link #changed(Field, boolean)}, prior to updating, or alternatively,
* use {@link Settings#getUpdateUnchangedRecords()}.
*
* This is the same as calling record.update(record.fields())
*
* @return 1
if the record was stored to the database. 0
*
if storing was not necessary.
* @throws DataAccessException if something went wrong executing the query
* @throws DataChangedException If optimistic locking is enabled and the
* record has already been changed/deleted in the database
* @see #store()
*/
@Support
int update() throws DataAccessException, DataChangedException;
/**
* Store parts of this record to the database using an UPDATE
* statement.
*
* @return 1
if the record was stored to the database. 0
*
if storing was not necessary.
* @throws DataAccessException if something went wrong executing the query
* @throws DataChangedException If optimistic locking is enabled and the
* record has already been changed/deleted in the database
* @see #update()
*/
@Support
int update(Field>... fields) throws DataAccessException, DataChangedException;
/**
* Store parts of this record to the database using an UPDATE
* statement.
*
* @return 1
if the record was stored to the database. 0
*
if storing was not necessary.
* @throws DataAccessException if something went wrong executing the query
* @throws DataChangedException If optimistic locking is enabled and the
* record has already been changed/deleted in the database
* @see #update()
*/
@Support
int update(Collection extends Field>> fields) throws DataAccessException, DataChangedException;
/**
* Store this record back to the database using a MERGE
* statement.
*
* Unlike {@link #store()}, the statement produced by this operation does
* not depend on whether the record has been previously fetched from the
* database or created afresh. It implements the semantics of an
* INSERT … ON DUPLICATE KEY UPDATE
statement, which will
* update the row regardless of which (unique) key value is already present.
* See {@link InsertOnDuplicateStep#onDuplicateKeyUpdate()}.
*
* When optimistic locking is active for this record, then this operation
* will execute {@link #insert()} or {@link #update()} explicitly, depending
* on whether the lock values are present already in the record.
*
* If you want to enforce statement execution, regardless if the values in
* this record were changed, you can explicitly set the changed flags for
* all values with {@link #changed(boolean)} or for single values with
* {@link #changed(Field, boolean)}, prior to insertion.
*
* This is the same as calling record.merge(record.fields())
*
* @return 1
if the record was merged to the database. 0
*
if merging was not necessary.
* @throws DataAccessException if something went wrong executing the query
* @see #store()
* @see InsertOnDuplicateStep#onDuplicateKeyUpdate()
*/
@Support({ CUBRID, DERBY, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES, SQLITE, YUGABYTEDB })
int merge() throws DataAccessException;
/**
* Store parts of this record to the database using a MERGE
* statement.
*
* @return 1
if the record was merged to the database. 0
*
if merging was not necessary.
* @throws DataAccessException if something went wrong executing the query
* @see #merge()
*/
@Support({ CUBRID, DERBY, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES, SQLITE, YUGABYTEDB })
int merge(Field>... fields) throws DataAccessException;
/**
* Store parts of this record to the database using a MERGE
* statement.
*
* @return 1
if the record was merged to the database. 0
*
if merging was not necessary.
* @throws DataAccessException if something went wrong executing the query
* @see #merge()
*/
@Support({ CUBRID, DERBY, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES, SQLITE, YUGABYTEDB })
int merge(Collection extends Field>> fields) throws DataAccessException;
/**
* Deletes this record from the database, based on the value of the primary
* key or main unique key.
*
*
Optimistic locking
*
* If a DELETE
statement is executed and
* {@link Settings#isExecuteWithOptimisticLocking()} is set to
* true
, then this record will first be compared with the
* latest state in the database. There are two modes of operation for
* optimistic locking:
*
* - With VERSION and/or TIMESTAMP columns configured
*
* This is the preferred way of using optimistic locking in jOOQ. If this is
* an {@link UpdatableRecord} and if this record returns fields for either
* {@link Table#getRecordVersion()} or {@link Table#getRecordTimestamp()},
* then these values are compared to the corresponding value in the database
* in the WHERE
clause of the executed DELETE
* statement.
* - Without any specific column configurations
*
* In order to compare this record with the latest state, the database
* record will be locked pessimistically using a
* SELECT … FOR UPDATE
statement. Not all databases support
* the FOR UPDATE
clause natively. Namely, the following
* databases will show slightly different behaviour:
*
* - {@link SQLDialect#SQLSERVER}: jOOQ will try to lock the database
* record using
WITH (ROWLOCK, UPDLOCK)
hints.
* - {@link SQLDialect#SQLITE}: No pessimistic locking is possible. Client
* code must assure that no race-conditions can occur between jOOQ's
* checking of database record state and the actual
DELETE
*
*
* See {@link SelectQuery#setForUpdate(boolean)} for more details
*
* Statement examples
*
* The executed statement is
* DELETE FROM [table]
* WHERE [key fields = key values]
* AND [version/timestamp fields = version/timestamp values]
*
* This is in fact the same as calling
* delete(getTable().getPrimaryKey().getFieldsArray())
*
* @return 1
if the record was deleted from the database.
* 0
if deletion was not necessary.
* @throws DataAccessException if something went wrong executing the query
* @throws DataChangedException If optimistic locking is enabled and the
* record has already been changed/deleted in the database
*/
@Support
int delete() throws DataAccessException, DataChangedException;
/**
* Refresh this record from the database.
*
* A successful refresh results in the following:
*
* - {@link #valuesRow()} will have been restored to the respective values
* from the database
* - {@link #original()} will match this record
* - {@link #changed()} will be
false
*
*
* Refreshing can trigger any of the following actions:
*
* - Executing a new
SELECT
statement, if this is an
* {@link UpdatableRecord}.
* - Failing, otherwise
*
*
* This is the same as calling record.refresh(record.fields())
*
* @throws DataAccessException This exception is thrown if something went
* wrong executing the refresh SELECT
statement
* @throws NoDataFoundException If the record does not exist anymore in the
* database
*/
@Support
void refresh() throws DataAccessException;
/**
* Refresh parts of this record from the database.
*
* A successful refresh results in the following:
*
* - {@link #valuesRow()} will have been restored to the respective values
* from the database
* - {@link #original()} will match this record
* - {@link #changed()} will be
false
*
*
* Refreshing can trigger any of the following actions:
*
* - Executing a new
SELECT
statement, if this is an
* {@link UpdatableRecord}.
* - Failing, otherwise
*
*
* This is the same as calling record.refresh(record.fields())
*
* @throws DataAccessException This exception is thrown if something went
* wrong executing the refresh SELECT
statement
* @throws NoDataFoundException If the record does not exist anymore in the
* database
*/
@Support
void refresh(Field>... fields) throws DataAccessException, NoDataFoundException;
/**
* Refresh parts of this record from the database.
*
* A successful refresh results in the following:
*
* - {@link #valuesRow()} will have been restored to the respective values
* from the database
* - {@link #original()} will match this record
* - {@link #changed()} will be
false
*
*
* Refreshing can trigger any of the following actions:
*
* - Executing a new
SELECT
statement, if this is an
* {@link UpdatableRecord}.
* - Failing, otherwise
*
*
* This is the same as calling record.refresh(record.fields())
*
* @throws DataAccessException This exception is thrown if something went
* wrong executing the refresh SELECT
statement
* @throws NoDataFoundException If the record does not exist anymore in the
* database
*/
@Support
void refresh(Collection extends Field>> fields) throws DataAccessException, NoDataFoundException;
/**
* Duplicate this record (in memory) and reset all fields from the primary
* key or main unique key, such that a subsequent call to {@link #store()}
* will result in an INSERT
statement.
*
* @return A new record, distinct from this
record.
*/
@NotNull
R copy();
/**
* Fetch a child record of this record, given a foreign key.
*
* This returns a child record referencing this record through a given
* foreign key, as if fetching from {@link #children(ForeignKey)}.. If no
* child record was found, this returns null
.
*
* A separate roundtrip is created by this operation. It is
* often much better to include parent records using ordinary
* JOIN
mechanisms in a single query, or using nested records,
* or the MULTISET
or MULTISET_AGG
operators, see
* https://www.jooq.org/doc/latest/manual/sql-building/column-expressions/nested-records/,
* or the https://www.jooq.org/doc/latest/manual/sql-building/column-expressions/multiset-value-constructor/.
*
* @throws DataAccessException if something went wrong executing the query
* @throws TooManyRowsException if the query returned more than one record
* @see ForeignKey#fetchChildren(java.util.Collection)
* @see ForeignKey#fetchChildren(Record)
* @see ForeignKey#fetchChildren(Record...)
*/
@Nullable
@Support
@Blocking
> O fetchChild(ForeignKey key) throws TooManyRowsException, DataAccessException;
/**
* Fetch child records of this record, given a foreign key.
*
* This returns child records referencing this record through a given
* foreign key, as if fetching from {@link #children(ForeignKey)}.
*
* A separate roundtrip is created by this operation. It is
* often much better to include parent records using ordinary
* JOIN
mechanisms in a single query, or using nested records,
* or the MULTISET
or MULTISET_AGG
operators, see
* https://www.jooq.org/doc/latest/manual/sql-building/column-expressions/nested-records/,
* or the https://www.jooq.org/doc/latest/manual/sql-building/column-expressions/multiset-value-constructor/.
*
* @throws DataAccessException if something went wrong executing the query
* @see ForeignKey#fetchChildren(java.util.Collection)
* @see ForeignKey#fetchChildren(Record)
* @see ForeignKey#fetchChildren(Record...)
*/
@NotNull
@Support
@Blocking
> Result fetchChildren(ForeignKey key) throws DataAccessException;
/**
* Get a table expression representing the children of this record, given a
* foreign key.
*/
@NotNull
@Support
> Table children(ForeignKey key);
}