Migrating from 0.61.0 to 1.0.0
This guide provides instructions on how to migrate from Exposed version 0.61.0 to the version 1.0.0. Version 1.0.0 introduces R2DBC support on top of the existing JDBC support. Most of the changes in this release were made to accommodate reactive database access while preserving existing functionality.
Import versioning and package renaming
Updated imports
All dependencies have been updated to follow the import path pattern of org.jetbrains.exposed.v1.packageName.*.
This means that imports from exposed-core, for example, which were previously located under org.jetbrains.exposed.sql.*, are now located under org.jetbrains.exposed.v1.core.*. The table below shows example changes:
0.61.0 | 1.0.0 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Check Breaking Changes - 1.0.0-beta-1 for more details about the changes in imports.
Moved imports
The major design change for allowing R2DBC functionality involved extracting some of the classes and interfaces from exposed-core and moving them to exposed-jdbc, so that new R2DBC variants could also be created in exposed-r2dbc. The table below shows example changes:
0.61.0 | 1.0.0 |
|---|---|
|
|
|
|
|
|
|
|
Additionally, top-level query and statement functions that required an R2DBC variant have been moved out of exposed-core. This also applies to certain class methods that perform metadata query checks, namely the exists() method from the classes Table, Schema, and Sequence. The table below shows example changes:
0.61.0 | 1.0.0 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
SqlExpressionBuilder method imports
The interface ISqlExpressionBuilder (and all its methods) has been deprecated, along with its implementation objects, SqlExpressionBuilder and UpsertSqlExpressionBuilder. All methods previously restricted to this interface should now be replaced with their new equivalent top-level functions. This will require the addition of new import statements if org.jetbrains.exposed.v1.core.* is not already present:
This means that it is no longer necessary to use a scope function with SqlExpressionBuilder as the receiver, so builder methods like Op.build() and Expression.build() have also been deprecated:
IDE auto-import assistance
The above changes to the import paths will present as multiple unresolved errors in your IDE and may be tedious to resolve and add manually.
In IntelliJ IDEA, a shortcut to resolving these import errors may be to rely on the automatic addition of import statements by temporarily enabling the Add unambiguous imports option in Settings | Editor | General | Auto Import. With that option checked, the deletion of any unresolved import statements should trigger the automatic addition of the correct paths, which can then be confirmed manually.
Implicit imports and naming conflicts
Prior to version 1.0.0, it was possible to create custom extension functions and class methods with identical names to existing query and statement functions, like selectAll() and insert(). This is still possible with version 1.0.0. However, due to the import paths of these Exposed functions, using wildcard imports may lead to unexpected invocation behavior if such custom functions are also being used. It is recommended to explicitly import these custom functions in the event that renaming is not a feasible option.
Migration dependencies
Prior to version 1.0.0, MigrationUtils was available with a dependency on the exposed-migration artifact. In order to enable the use of its utility methods with both JDBC and R2DBC support, this artifact is now replaced by exposed-migration-core and driver-specific artifacts have been introduced (with suffixes such as -jdbc and -r2dbc):
This means that the import path of MigrationUtils has also been updated to follow the pattern of the other package changes:
Transactions
The class Transaction remains in exposed-core but it is now abstract and all its driver-specific properties and methods are now accessible from the new open classes JdbcTransaction and R2dbcTransaction. The following shows some examples of the ownership changes:
0.61.0 | 1.0.0 |
|---|---|
|
|
|
|
|
|
|
|
Custom functions
Any custom transaction-scoped extension functions will most likely require the receiver to be changed from Transaction to JdbcTransaction, as may any functions that used to accept a Transaction as an argument:
transaction() signature changed
The order of declared parameters for the functions transaction() and inTopLevelTransaction() has changed, with the db parameter of type Database now being the first parameter.
The transactionIsolation parameter has additionally been provided a default argument based on the value set by the database's transaction manager, which defaults to the value configured on Database.connect().
The default argument for the readOnly parameter is no longer false, but instead also defaults to the value derived from the database's transaction manager configuration.
Transaction managers
The TransactionManager interface has undergone a similar redesign, except that the interface remaining in exposed-core has been renamed to TransactionManagerApi. This interface contains only the properties and methods common to both JDBC and R2DBC drivers.
With version 1.0.0, you can still call companion object methods on TransactionManager because new implementations have been introduced to both exposed-jdbc and exposed-r2dbc. The return type of some of these methods may have changed to reflect the exact Transaction implementation:
This also means that the type of the manager parameter in all Database.connect() methods has changed to accept a function that still takes a Database instance, but instead now returns a TransactionManagerApi. The default argument for a connection manager is no longer ThreadLocalTransactionManager, which has now been deprecated. The newly implemented TransactionManager is now passed as the default argument instead.
As part of the redesign, the underlying database transaction management logic and switching of active transactions has been changed to no longer rely on ThreadLocal. This means that the property TransactionManager.threadLocal, as well as the methods TransactionManagerApi.bindTransactionToThread() and TransactionManager.resetCurrent() have all been removed.
The association between a Database instance and its TransactionManager has also been streamlined to ensure a stricter, more reliable relationship. For this reason, the following changes have been made:
The signature of
TransactionManager.managerFor()has changed to accept only a non-nullabledatabaseargument and its return type is a non-nullableTransactionManager.The top-level property
transactionManagerno longer accepts a nullableDatabaseas its receiver.TransactionManager.isInitialized()has also been removed.
Prior to version 1.0.0, TransactionManager.defaultDatabase was used to retrieve the Database instance that had been set as the default to use for all transactions or, if a default was not set, the last instance created. With 1.0.0, this functionality has been split for simplicity:
TransactionManager.defaultDatabase: To get and set the default database to use when opening a transaction. This value will remain null unless explicitly set at some point.TransactionManager.primaryDatabase: To get the database that will be used when opening the next transaction. This value will either be retrieved fromdefaultDatabaseor the lastDatabaseinstance registered, if any.
Prior to version 1.0.0, the property TransactionManager.manager resolved its value from thread local and returned an uninitialized stand-in manager if none was found. With 1.0.0, the manager instance retrieved is resolved based on the current active transaction, if any; otherwise it gets the manager of the default set database or the last registered database instance. If no manager can be resolved, this property now throws an exception.
JDBC suspend functions deprecated
The original top-level suspend transaction functions, namely newSuspendedTransaction(), withSuspendTransaction(), and suspendedTransactionAsync(), have been moved out of exposed-core and into exposed-jdbc. They have also been deprecated in favor of either switching to R2DBC operations or using newly introduced JDBC functions, suspendTransaction() and inTopLevelSuspendTransaction().
These new functions should be used if you want to continue using a blocking JDBC driver, but with the ability to call suspend functions alongside your database transaction operations. The behavior of these functions aligns fully with the behavior of the standard transaction() functions, for example where it pertains to nesting logic and exception handling. Unlike the original suspend transaction functions, suspendTransaction() does not accept a CoroutineContext argument. Instead, a coroutine builder function, like withContext() or async() should be used to wrap the suspendTransaction() block.
Statement builders and executables
The abstract class Statement remains in exposed-core, along with all its original implementations, like InsertStatement. But from version 1.0.0, these statement classes no longer hold any logic pertaining to their specific execution in the database nor store any knowledge of the transaction they are created in. These classes are now only responsible for SQL syntax building and parameter binding.
The original extracted logic is now owned by a newly introduced interface, BlockingExecutable, in exposed-jdbc. The R2DBC variant is SuspendExecutable in exposed-r2dbc. Each core statement implementation now has an associated executable implementation, like InsertBlockingExecutable, which stores the former in its statement property. The following shows some examples of the ownership changes:
0.61.0 | 1.0.0 |
|---|---|
|
|
|
|
|
|
|
|
If a statement implementation originally held a protected transaction property, this is now also owned by the executable implementation.
Custom statements
This separation of responsibility means that any custom Statement implementation now requires an associated BlockingExecutable implementation to be sent to the database. This BlockingExecutable can be custom, or you can use an existing class if it provides sufficient execution logic, as shown in the following example:
exec() parameter type changed
Prior to version 1.0.0, it was possible to create a Statement instance, using a built-in or custom implementation, and send it to the database by passing it as an argument to exec().
In version 1.0.0, since statements are no longer responsible for execution, the same exec() method only accepts a BlockingExecutable as its argument:
This signature change does not affect the method's use with a Query argument, as Query implements BlockingExecutable directly. But changes to how Exposed processes query results does affect the type of the lambda block's argument.
Prior to version 1.0.0, the java.sql.ResultSet retrieved from the database was passed directly as the argument. In version 1.0.0, this result is wrapped by a common ResultApi object and requires casting to a JdbcResult if you want to process the underlying ResultSet directly using the original exec():
You can avoid this casting and continue to use the original pre-version 1.0.0 code in your lambda by replacing exec() with execQuery(). The latter automatically performs the necessary casting and unwrapping under-the-hood, so you can continue to use ResultSet directly as before:
See result wrappers for more details behind the changes made to how Exposed handles query results.
ReturningStatement return type changed
Prior to version 1.0.0, table extension functions that returned values for specified columns, like insertReturning() and updateReturning(), returned a value of type ReturningStatement. This return type ensured that such statements would be sent to the database only at the moment a terminal operation attempted to iterate over the results.
In version 1.0.0, since all execution logic has been removed from the core ReturningStatement, these functions instead return a value of type ReturningBlockingExecutable. Other than this return type change, the return value can be iterated over in the same way as before.
DeleteStatement companion methods deprecated
Prior to version 1.0.0, the companion object of DeleteStatement provided the .all() and .where() methods as alternatives to calling Table.deleteAll() or Table.deleteWhere().
Following version 1.0.0's removal of statement execution logic from exposed-core, these companion methods have now been deprecated. It is recommended to use the table extension functions directly or combine a DeleteStatement constructor with DeleteBlockingExecutable.
SqlExpressionBuilder lambda blocks
As mentioned in the imports section above, SqlExpressionBuilder object has been deprecated.
Prior to version 1.0.0, many higher-order functions used this object as a parameter receiver to allow access to its methods without needing import statements for every single method used. Since all these object methods have been replaced with top-level functions, such receivers are now redundant and have been removed.
All expression builder methods passed to these function parameters will now be unresolved unless either a statement such as import org.jetbrains.exposed.v1.core.* is already present, or the prompted import statements are added:
Queries
While the AbstractQuery class remains in exposed-core, its Query implementation is now in exposed-jdbc, with the R2DBC variant located in exposed-r2dbc. This restructuring was necessary to allow for the required differences in the underlying implementations of each class, with the JDBC Query ultimately implementing Iterable, and the R2DBC Query implementing Flow.
CommentPosition ownership changed
Certain Query properties, like where and having (as well as their associated adjustment methods), have been moved down from the subclass into superclass AbstractQuery so that they remain common to both drivers.
This also includes thecomments property and its related enum class CommentPosition, which is now only accessible from AbstractQuery:
Result wrappers
Two new exposed-core interfaces, ResultApi and RowApi, have been introduced to represent commonalities between the results of query execution with JDBC (java.sql.ResultSet) and with R2DBC (io.r2dbc.spi.Result). These are both implemented by new driver-specific wrapper classes, JdbcResult and R2dbcResult.
readObject() parameter type changed
Since driver-specific results, like java.sql.ResultSet, are no longer supported in exposed-core, they are instead wrapped by common interfaces, like RowApi.
The IColumnType interface has a method readObject() for performing any special read or conversion logic when accessing an object at a specific index in the result. The signature of this method has changed to use RowApi instead of ResultSet, which still allows access to either the underlying JDBC ResultSet or R2DBC Row via getObject():
Instead of switching to getObject() as above, the original code called on a ResultSet could still be used by accessing the underlying wrapped result via RowApi.origin:
ResultRow.create() parameter type changed
As mentioned in the section on readObject(), all usage of java.sql.ResultSet has been removed from exposed-core. The companion method to create an Exposed ResultRow directly from the result of a query or statement execution now accepts a RowApi as an argument.
execute() return type changed
Calling execute() directly on a Query no longer returns a ResultSet. It instead returns a ResultApi, which must be cast if you need to access the original wrapped result type:
StatementResult.Object property type changed
The Object type from the exposed-core sealed class StatementResult no longer has a property of type java.sql.ResultSet. Its property is now of type ResultApi.
DatabaseDialect and VendorDialect
Any method from the exposed-core interface DatabaseDialect that required driver-specific metadata queries has been extracted. If moved, the method is now accessible from the new abstract class DatabaseDialectMetadata found in exposed-jdbc. This also applies to such methods and properties from the core VendorDialect implementations. The following shows some examples of the ownership changes:
0.61.0 | 1.0.0 |
|---|---|
|
|
|
|
|
|
|
|
These methods would have previously been most commonly invoked on the top-level property currentDialect. To follow a similar pattern, a related property, currentDialectMetadata, has been added to replace the original call:
H2 version 1.x.x
Support for H2 versions earlier than 2.0.202 (namely 1.4.200 and earlier) has now been fully phased out. In addition, H2Dialect.H2MajorVersion.One is now deprecated and H2Dialect-specific properties, like majorVersion and isSecondVersion, now throw an exception if H2 version 1.x.x is detected.
Moving forward, new features will no longer be tested on H2 version 1.x.x, so support for those versions will not be guaranteed. Depending on the built-in support from these older H2 versions, Exposed API may still mostly be compatible, but may now throw syntax or unsupported exceptions when generating certain SQL clauses.
H2 DATETIME data type
Starting from H2 version 2.4.240, DATETIME(9) data type is no longer accepted unless using certain compatibility modes. This was the data type that datetime() columns were mapped to for the following H2 modes: Regular, MySQL, and MariaDB.
Moving forward, datetime() columns for these modes will instead map to TIMESTAMP(9) for any Database instance using version 2.4.240+. Older versions of H2 will show no change in the data type mapping.
Custom dialects
In the same way as how there are database-specific implementations for further extensibility in exposed-core, like H2Dialect, the new class also comes with open implementations for metadata extensions, like H2DialectMetadata. These new classes should be extended to hold any custom overrides for the metadata methods that have been moved.
Any custom database dialect implementation can be registered as before via Database.registerDialect(). With version 1.0.0, an additional call to Database.registerDialectMetadata() should be used to ensure that the newly associated metadata implementation is also registered.
resolveRefOptionFromJdbc() removed
Given the original intention behind this method, DatabaseDialect.resolveRefOptionFromJdbc() has been removed and replaced by a driver-agnostic variant resolveReferenceOption() found under org.jetbrains.exposed.v1.core.statements.api.ExposedDatabaseMetadata. Driver-specific variants have been introduced both in exposed-jdbc and exposed-r2dbc.
Property ENABLE_UPDATE_DELETE_LIMIT removed
This property in the companion object of SQLiteDialect() has been removed and replaced entirely by DatabaseDialectMetadata.supportsLimitWithUpdateOrDelete().
Additional core class restructuring
Some other public classes have been restructured to remove any driver-specific logic from exposed-core.
Database
A new abstract class, DatabaseApi, has been added to exposed-core to hold all properties and methods relevant to both JDBC and R2DBC databases and their future connections. The original Database class and its companion object have been moved to exposed-jdbc, and a new class R2dbcDatabase has been introduced to exposed-r2dbc.
PreparedStatementApi
The PreparedStatementApi interface no longer holds methods that perform any statement execution logic.
With version 1.0.0, these are now only accessible from the new driver-specific interface implementation, JdbcPreparedStatementApi. The following shows some examples of the ownership changes:
0.61.0 | 1.0.0 |
|---|---|
|
|
|
|
|
|
|
|
set() deprecated
The original operator fun set(index: Int, value: Any) has been deprecated. It should be replaced by a new variant that accepts a third argument for the column type associated with the value being bound to the statement. This new set() method will require an override if the interface is implemented directly.
setArray() deprecated
The original setArray(index: Int, type: String, array: Array<*>) has been deprecated. It should be replaced by a new variant that accepts the actual ArrayColumnType associated with the array value being bound to the statement as the second argument, instead of a string representation of the type. This new setArray() method will require an override if the interface is implemented directly.