Skip to content

Commit

Permalink
fix: Make using Java's ServiceLoader optional on Database.connect() (#…
Browse files Browse the repository at this point in the history
…2293)

* fix class loader

* fix review issues
  • Loading branch information
makeevrserg authored Nov 26, 2024
1 parent 0e8dee6 commit a6fed1d
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,12 @@
but it does not immediately establish a connection with the database. The actual connection
to the database will be established later when a database operation is performed.
</note>
<note>
By default, Exposed uses a <code>ServiceLoader</code> to get an implementation of the <code>DatabaseConnectionAutoRegistration</code>
interface that represents a connection accessed by the <code>Database</code> instance.
This can be modified when calling the <code>Database.connect</code> method by providing an argument to <code>connectionAutoRegistration</code>
in the parameter list.
</note>
<p>
With this, you've added Exposed to your Kotlin project and configured a database connection.
You're now ready to define your data model and engage with the database using Exposed's DSL API.
Expand Down
13 changes: 10 additions & 3 deletions documentation-website/Writerside/topics/Working-with-Database.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,20 @@ val db = Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver")
<note>Executing this code more than once per database will create leaks in your application, hence it is recommended to store it for later use:
<code-block lang="kotlin">
object DbSettings {
val db by lazy {
Database.connect(/* setup connection */)
}
val db by lazy {
Database.connect(/* setup connection */)
}
}
</code-block>
</note>

<note>
By default, Exposed uses a <code>ServiceLoader</code> to get an implementation of the <code>DatabaseConnectionAutoRegistration</code>
interface that represents a connection accessed by the <code>Database</code> instance.
This can be modified when calling the <code>Database.connect</code> method by providing an argument to <code>connectionAutoRegistration</code>
in the parameter list.
</note>

### H2

In order to use H2, you need to add the H2 driver dependency:
Expand Down
16 changes: 8 additions & 8 deletions exposed-core/api/exposed-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -674,14 +674,14 @@ public final class org/jetbrains/exposed/sql/Database {
}

public final class org/jetbrains/exposed/sql/Database$Companion {
public final fun connect (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lorg/jetbrains/exposed/sql/DatabaseConfig;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/exposed/sql/Database;
public final fun connect (Ljavax/sql/DataSource;Lkotlin/jvm/functions/Function1;Lorg/jetbrains/exposed/sql/DatabaseConfig;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/exposed/sql/Database;
public final fun connect (Lkotlin/jvm/functions/Function0;Lorg/jetbrains/exposed/sql/DatabaseConfig;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/exposed/sql/Database;
public static synthetic fun connect$default (Lorg/jetbrains/exposed/sql/Database$Companion;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lorg/jetbrains/exposed/sql/DatabaseConfig;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/exposed/sql/Database;
public static synthetic fun connect$default (Lorg/jetbrains/exposed/sql/Database$Companion;Ljavax/sql/DataSource;Lkotlin/jvm/functions/Function1;Lorg/jetbrains/exposed/sql/DatabaseConfig;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/exposed/sql/Database;
public static synthetic fun connect$default (Lorg/jetbrains/exposed/sql/Database$Companion;Lkotlin/jvm/functions/Function0;Lorg/jetbrains/exposed/sql/DatabaseConfig;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/exposed/sql/Database;
public final fun connectPool (Ljavax/sql/ConnectionPoolDataSource;Lkotlin/jvm/functions/Function1;Lorg/jetbrains/exposed/sql/DatabaseConfig;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/exposed/sql/Database;
public static synthetic fun connectPool$default (Lorg/jetbrains/exposed/sql/Database$Companion;Ljavax/sql/ConnectionPoolDataSource;Lkotlin/jvm/functions/Function1;Lorg/jetbrains/exposed/sql/DatabaseConfig;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/exposed/sql/Database;
public final fun connect (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lorg/jetbrains/exposed/sql/DatabaseConfig;Lorg/jetbrains/exposed/sql/DatabaseConnectionAutoRegistration;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/exposed/sql/Database;
public final fun connect (Ljavax/sql/DataSource;Lkotlin/jvm/functions/Function1;Lorg/jetbrains/exposed/sql/DatabaseConfig;Lorg/jetbrains/exposed/sql/DatabaseConnectionAutoRegistration;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/exposed/sql/Database;
public final fun connect (Lkotlin/jvm/functions/Function0;Lorg/jetbrains/exposed/sql/DatabaseConfig;Lorg/jetbrains/exposed/sql/DatabaseConnectionAutoRegistration;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/exposed/sql/Database;
public static synthetic fun connect$default (Lorg/jetbrains/exposed/sql/Database$Companion;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lorg/jetbrains/exposed/sql/DatabaseConfig;Lorg/jetbrains/exposed/sql/DatabaseConnectionAutoRegistration;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/exposed/sql/Database;
public static synthetic fun connect$default (Lorg/jetbrains/exposed/sql/Database$Companion;Ljavax/sql/DataSource;Lkotlin/jvm/functions/Function1;Lorg/jetbrains/exposed/sql/DatabaseConfig;Lorg/jetbrains/exposed/sql/DatabaseConnectionAutoRegistration;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/exposed/sql/Database;
public static synthetic fun connect$default (Lorg/jetbrains/exposed/sql/Database$Companion;Lkotlin/jvm/functions/Function0;Lorg/jetbrains/exposed/sql/DatabaseConfig;Lorg/jetbrains/exposed/sql/DatabaseConnectionAutoRegistration;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/exposed/sql/Database;
public final fun connectPool (Ljavax/sql/ConnectionPoolDataSource;Lkotlin/jvm/functions/Function1;Lorg/jetbrains/exposed/sql/DatabaseConfig;Lorg/jetbrains/exposed/sql/DatabaseConnectionAutoRegistration;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/exposed/sql/Database;
public static synthetic fun connectPool$default (Lorg/jetbrains/exposed/sql/Database$Companion;Ljavax/sql/ConnectionPoolDataSource;Lkotlin/jvm/functions/Function1;Lorg/jetbrains/exposed/sql/DatabaseConfig;Lorg/jetbrains/exposed/sql/DatabaseConnectionAutoRegistration;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/exposed/sql/Database;
public final fun getDefaultIsolationLevel (Lorg/jetbrains/exposed/sql/Database;)I
public final fun getDialectName (Ljava/lang/String;)Ljava/lang/String;
public final fun registerDialect (Ljava/lang/String;Lkotlin/jvm/functions/Function0;)V
Expand Down
42 changes: 29 additions & 13 deletions exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Database.kt
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,11 @@ class Database private constructor(
companion object {
internal val dialects = ConcurrentHashMap<String, () -> DatabaseDialect>()

private val connectionInstanceImpl: DatabaseConnectionAutoRegistration =
ServiceLoader.load(DatabaseConnectionAutoRegistration::class.java, Database::class.java.classLoader).firstOrNull()
private val connectionInstanceImpl: DatabaseConnectionAutoRegistration by lazy {
ServiceLoader.load(DatabaseConnectionAutoRegistration::class.java, Database::class.java.classLoader)
.firstOrNull()
?: error("Can't load implementation for ${DatabaseConnectionAutoRegistration::class.simpleName}")
}

private val driverMapping = mutableMapOf(
"jdbc:h2" to "org.h2.Driver",
Expand Down Expand Up @@ -162,12 +164,13 @@ class Database private constructor(
private fun doConnect(
explicitVendor: String?,
config: DatabaseConfig?,
connectionAutoRegistration: DatabaseConnectionAutoRegistration,
getNewConnection: () -> Connection,
setupConnection: (Connection) -> Unit = {},
manager: (Database) -> TransactionManager = { ThreadLocalTransactionManager(it) }
): Database {
return Database(explicitVendor, config ?: DatabaseConfig.invoke()) {
connectionInstanceImpl(getNewConnection().apply { setupConnection(this) })
connectionAutoRegistration(getNewConnection().apply { setupConnection(this) })
}.apply {
TransactionManager.registerManager(this, manager(this))
}
Expand All @@ -180,6 +183,8 @@ class Database private constructor(
* but instead provides the details necessary to do so whenever a connection is required by a transaction.
*
* @param datasource The [DataSource] object to be used as a means of getting a connection.
* @param connectionAutoRegistration The connection provider for database. If not provided,
* a service loader will be used to locate and load a provider for [DatabaseConnectionAutoRegistration].
* @param setupConnection Any setup that should be applied to each new connection.
* @param databaseConfig Configuration parameters for this [Database] instance.
* @param manager The [TransactionManager] responsible for new transactions that use this [Database] instance.
Expand All @@ -188,14 +193,16 @@ class Database private constructor(
datasource: DataSource,
setupConnection: (Connection) -> Unit = {},
databaseConfig: DatabaseConfig? = null,
connectionAutoRegistration: DatabaseConnectionAutoRegistration = connectionInstanceImpl,
manager: (Database) -> TransactionManager = { ThreadLocalTransactionManager(it) }
): Database {
return doConnect(
explicitVendor = null,
config = databaseConfig,
getNewConnection = { datasource.connection!! },
setupConnection = setupConnection,
manager = manager
manager = manager,
connectionAutoRegistration = connectionAutoRegistration
).apply {
connectsViaDataSource = true
}
Expand Down Expand Up @@ -225,14 +232,16 @@ class Database private constructor(
datasource: ConnectionPoolDataSource,
setupConnection: (Connection) -> Unit = {},
databaseConfig: DatabaseConfig? = null,
connectionAutoRegistration: DatabaseConnectionAutoRegistration = connectionInstanceImpl,
manager: (Database) -> TransactionManager = { ThreadLocalTransactionManager(it) }
): Database {
return doConnect(
explicitVendor = null,
config = databaseConfig,
getNewConnection = { datasource.pooledConnection.connection!! },
setupConnection = setupConnection,
manager = manager
manager = manager,
connectionAutoRegistration = connectionAutoRegistration
)
}

Expand All @@ -243,19 +252,23 @@ class Database private constructor(
* but instead provides the details necessary to do so whenever a connection is required by a transaction.
*
* @param getNewConnection A function that returns a new connection.
* @param connectionAutoRegistration The connection provider for database. If not provided,
* a service loader will be used to locate and load a provider for [DatabaseConnectionAutoRegistration].
* @param databaseConfig Configuration parameters for this [Database] instance.
* @param manager The [TransactionManager] responsible for new transactions that use this [Database] instance.
*/
fun connect(
getNewConnection: () -> Connection,
databaseConfig: DatabaseConfig? = null,
connectionAutoRegistration: DatabaseConnectionAutoRegistration = connectionInstanceImpl,
manager: (Database) -> TransactionManager = { ThreadLocalTransactionManager(it) }
): Database {
return doConnect(
explicitVendor = null,
config = databaseConfig,
getNewConnection = getNewConnection,
manager = manager
manager = manager,
connectionAutoRegistration = connectionAutoRegistration
)
}

Expand All @@ -266,6 +279,8 @@ class Database private constructor(
* but instead provides the details necessary to do so whenever a connection is required by a transaction.
*
* @param url The URL that represents the database when getting a connection.
* @param connectionAutoRegistration The connection provider for database. If not provided,
* a service loader will be used to locate and load a provider for [DatabaseConnectionAutoRegistration].
* @param driver The JDBC driver class. If not provided, the specified [url] will be used to find
* a match from the existing driver mappings.
* @param user The database user that owns the new connections.
Expand All @@ -274,25 +289,26 @@ class Database private constructor(
* @param databaseConfig Configuration parameters for this [Database] instance.
* @param manager The [TransactionManager] responsible for new transactions that use this [Database] instance.
*/
@Suppress("UnusedParameter", "LongParameterList")
fun connect(
url: String,
driver: String = getDriver(url),
user: String = "",
password: String = "",
setupConnection: (Connection) -> Unit = {},
databaseConfig: DatabaseConfig? = null,
connectionAutoRegistration: DatabaseConnectionAutoRegistration = connectionInstanceImpl,
manager: (Database) -> TransactionManager = { ThreadLocalTransactionManager(it) }
): Database {
Class.forName(driver).getDeclaredConstructor().newInstance()
val dialectName = getDialectName(url) ?: error("Can't resolve dialect for connection: $url")
return doConnect(
dialectName,
databaseConfig,
{
DriverManager.getConnection(url, user, password)
},
setupConnection,
manager
explicitVendor = dialectName,
config = databaseConfig,
getNewConnection = { DriverManager.getConnection(url, user, password) },
setupConnection = setupConnection,
manager = manager,
connectionAutoRegistration = connectionAutoRegistration,
)
}

Expand Down

0 comments on commit a6fed1d

Please sign in to comment.