All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jooq.UpdatableRecord Maven / Gradle / Ivy

There is a newer version: 3.19.15
Show newest version
/*
 * 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
 *
 *  http://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
 * ASL 2.0 and offer limited warranties, support, maintenance, and commercial
 * database integrations.
 *
 * For more information, please visit: http://www.jooq.org/licenses
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
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 *

*

    *
  1. it represents a record from a table or view - a {@link TableRecord}
  2. *
  3. its underlying table or view has a "main unique key", i.e. a primary key * or at least one unique key
  4. *
*

* 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> 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> 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> 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> 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> 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); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy