Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Make using Java's ServiceLoader optional on Database.connect() #2293

Merged
merged 2 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading