CRUD operations
CRUD stands for Create Read Update Delete, which are four basic operations for a database to support. This section shows how to perform SQL CRUD operations using Kotlin DSL.
Create
Exposed provides several functions to insert rows into a table:
Insert a single row
To create a new table row, use the .insert()
function. If the same row already exists in the table, it throws an exception.
The example corresponds to the following SQL statement:
Insert and get ID
To add a new row and return its ID, use .insertAndGetId()
. If the same row already exists in the table, it throws an exception.
Insert from select
For the INSERT INTO ... SELECT
SQL clause, use the .insert()
function with a Query
parameter:
By default, it will try to insert into all non auto-increment Table
columns in the order they are defined in the Table
instance. If you want to specify columns or change the order, provide a list of columns as the second parameter:
Insert and ignore
To allow insert statements to be executed without throwing any errors, use .insertIgnore()
. This may be useful, for example, when insertion conflicts are possible:
If .insert()
was used instead of .insertIgnore()
, this would throw a constraint violation exception. Instead, this new row is ignored and discarded.
Insert and ignore and get ID
.insertIgnoreAndGetId()
adds a new row and returns its ID. If the same row already exists in the table, it ignores it and doesn't throw an exception.
Batch insert
.batchInsert()
allows mapping a list of entities into table rows in a single SQL statement. It is more efficient than using the insert
query for each row as it initiates only one statement.
The following example uses a simple list:
Here is an example that uses a list of data class instances:
If you don't need to get the newly generated values, such as the auto-incremented ID, set the shouldReturnGeneratedValues
parameter to false
. This increases the performance of batch inserts by batching them in chunks, instead of always waiting for the database to synchronize the newly inserted object state.
If you want to check if rewriteBatchedInserts
and batchInsert
are working correctly, you need to enable JDBC logging for your driver. This is necessary, as Exposed will always show the non-rewritten multiple inserts. For more information, see how to enable logging in PostgresSQL.
Read
Retrieve a record
The .select()
function allows you to select specific columns or/and expressions.
If you want to select only distinct value then use .withDistinct()
function:
Some SQL dialects, such as PostgreSQL and H2, also support the DISTINCT ON
clause. You can use this clause with the .withDistinctOn()
function:
Retrieve all records
To retrieve all records from a table, use the .selectAll()
method:
Query
inherits Iterable
so it is possible to traverse it using .map()
or .forEach()
:
Retrieve the count of modified rows
Some databases return a count of the number of rows inserted, updated, or deleted by the CRUD operation. For .insert()
, .upsert()
, and .replace()
, this value can be accessed using the statement class property insertedCount
:
Return data from modified rows
Some databases allow the return of additional data every time a row is either inserted, updated, or deleted. This can be accomplished by using one of the following functions:
Each of them take a list of the required table columns as an argument. If not provided, all table columns will be returned by default:
Update
Update a record
To update a record, use the .update()
function. By default, it returns the number of updated rows.
To update a column value using an expression, such as an increment, you can use either the .update()
function or the update
setter:
Insert or update
Insert or update (Upsert) is a database operation that either inserts a new row or updates an existing row if a duplicate constraint already exists. The supported functionality of .upsert()
is dependent on the specific database being used. For example, MySQL's INSERT ... ON DUPLICATE KEY UPDATE
statement automatically assesses the primary key and unique indices for a duplicate value, so using the function in Exposed would look like this:
If none of the optional arguments are provided to .upsert()
, and an onUpdate
block is omitted, the statements in the body
block will be used for both the insert and update parts of the operation. This means that, for example, if a table mapping has columns with default values and these columns are omitted from the body
block, the default values will be used for insertion as well as for the update operation.
Using another example, PostgreSQL allows more control over which key constraint columns to check for conflict, whether different values should be used for an update, and whether the update statement should have a WHERE
clause:
If the update operation should be identical to the insert operation except for a few columns, then onUpdateExclude
should be provided as an argument with the specific columns to exclude. This parameter could also be used for the reverse case when only a small subset of columns should be updated but duplicating the insert values is tedious:
If a specific database supports user-defined key columns and none are provided, the table's primary key is used. If there is no defined primary key, the first unique index is used. If there are no unique indices, each database handles this case differently, so it is strongly advised that keys are defined to avoid unexpected results.
Replace
The .replace()
method acts in a similar manner to an .upsert()
. The only difference is that if an insertion would violate a unique constraint, the existing row is deleted before the new row is inserted.
Unlike .upsert()
, none of the supporting databases allows a WHERE
clause. Also, the constraints used to assess a violation are limited to the primary key and unique indexes, so there is no parameter for a custom key set.
The values specified in the statement block will be used for the insert statement, and any omitted columns are set to their default values, if applicable.
In the example above, if the original row was inserted with a user-defined rating
and .replace()
was executed with a block that omitted the rating
column, the newly inserted row would store the default rating value. This is because the old row was completely deleted first.
The REPLACE INTO ... SELECT
SQL clause can be used by instead providing a query to .replace()
:
By default, it will try to insert into all non auto-increment Table
columns in the order they are defined in the Table
instance. If the columns need to be specified or the order should be changed, provide a list of columns as the second parameter:
Delete
Delete with a condition
To delete records and return the count of deleted rows, use the .deleteWhere()
function.
Any SqlExpressionBuilder
comparison operators or extension functions used in the op
parameter lambda block will require inclusion of an import statement:
Delete and ignore
To delete records while ignoring any possible errors that occur during the process, use the .deleteIgnoreWhere()
function. The function will return the count of deleted rows.
Delete all
To delete all rows in a table and return the count of deleted rows, use the .deleteAll()
function.
Join delete
To delete records from a table in a join relation, use the .delete()
function with a Join
as its receiver. Provide the specific table from which records should be deleted as the argument to the parameter targetTable
.