Upgrade Kotlin to 2.1.0 and implement database layer
Updated the Kotlin multiplatform plugin to version 2.1.0 and added the necessary dependency for the Kotlin-simple-logging library. Introduced new components, `Database.kt` and `Migrations.kt`, to manage database connections and migrations, along with adjustments to build configuration artifacts.
This commit is contained in:
4
.idea/artifacts/template_js_1_0_0_SNAPSHOT.xml
generated
4
.idea/artifacts/template_js_1_0_0_SNAPSHOT.xml
generated
@@ -1,6 +1,8 @@
|
|||||||
<component name="ArtifactManager">
|
<component name="ArtifactManager">
|
||||||
<artifact type="jar" name="template-js-1.0.0-SNAPSHOT">
|
<artifact type="jar" name="template-js-1.0.0-SNAPSHOT">
|
||||||
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
<root id="archive" name="template-js-1.0.0-SNAPSHOT.jar" />
|
<root id="archive" name="template-js-1.0.0-SNAPSHOT.jar">
|
||||||
|
<element id="module-output" name="template.jsMain" />
|
||||||
|
</root>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</component>
|
||||||
4
.idea/artifacts/template_jvm_1_0_0_SNAPSHOT.xml
generated
4
.idea/artifacts/template_jvm_1_0_0_SNAPSHOT.xml
generated
@@ -1,6 +1,8 @@
|
|||||||
<component name="ArtifactManager">
|
<component name="ArtifactManager">
|
||||||
<artifact type="jar" name="template-jvm-1.0.0-SNAPSHOT">
|
<artifact type="jar" name="template-jvm-1.0.0-SNAPSHOT">
|
||||||
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
<root id="archive" name="template-jvm-1.0.0-SNAPSHOT.jar" />
|
<root id="archive" name="template-jvm-1.0.0-SNAPSHOT.jar">
|
||||||
|
<element id="module-output" name="template.jvmMain" />
|
||||||
|
</root>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</component>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType
|
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("multiplatform") version "2.0.21"
|
kotlin("multiplatform") version "2.1.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "nl.astraeus"
|
group = "nl.astraeus"
|
||||||
@@ -19,9 +19,7 @@ repositories {
|
|||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvmToolchain(17)
|
jvmToolchain(17)
|
||||||
jvm {
|
jvm()
|
||||||
withJava()
|
|
||||||
}
|
|
||||||
js {
|
js {
|
||||||
binaries.executable()
|
binaries.executable()
|
||||||
browser {
|
browser {
|
||||||
@@ -33,6 +31,7 @@ kotlin {
|
|||||||
sourceSets {
|
sourceSets {
|
||||||
val commonMain by getting {
|
val commonMain by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
|
api("nl.astraeus:kotlin-simple-logging:1.1.1")
|
||||||
api("nl.astraeus:kotlin-css-generator:1.0.10")
|
api("nl.astraeus:kotlin-css-generator:1.0.10")
|
||||||
|
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0")
|
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0")
|
||||||
|
|||||||
37
src/jvmMain/kotlin/tmpl/Main.kt
Normal file
37
src/jvmMain/kotlin/tmpl/Main.kt
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package tmpl
|
||||||
|
|
||||||
|
import com.zaxxer.hikari.HikariConfig
|
||||||
|
import nl.astraeus.logger.Logger
|
||||||
|
import tmpl.db.Database
|
||||||
|
|
||||||
|
val log = Logger()
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
Thread.currentThread().uncaughtExceptionHandler = Thread.UncaughtExceptionHandler { t, e ->
|
||||||
|
log.warn(e) {
|
||||||
|
e.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Runtime.getRuntime().addShutdownHook(
|
||||||
|
object : Thread() {
|
||||||
|
override fun run() {
|
||||||
|
Database.vacuumDatabase()
|
||||||
|
Database.closeDatabase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Class.forName("nl.astraeus.jdbc.Driver")
|
||||||
|
Database.initialize(HikariConfig().apply {
|
||||||
|
driverClassName = "nl.astraeus.jdbc.Driver"
|
||||||
|
jdbcUrl = "jdbc:stat:webServerPort=6001:jdbc:sqlite:data/${REPO_NAME}.db"
|
||||||
|
username = "sa"
|
||||||
|
password = ""
|
||||||
|
maximumPoolSize = 25
|
||||||
|
isAutoCommit = false
|
||||||
|
|
||||||
|
validate()
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
101
src/jvmMain/kotlin/tmpl/db/Database.kt
Normal file
101
src/jvmMain/kotlin/tmpl/db/Database.kt
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package tmpl.db
|
||||||
|
|
||||||
|
import com.zaxxer.hikari.HikariConfig
|
||||||
|
import com.zaxxer.hikari.HikariDataSource
|
||||||
|
import java.sql.Connection
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
import kotlin.collections.set
|
||||||
|
import kotlin.use
|
||||||
|
|
||||||
|
private val currentConnection = ThreadLocal<Connection>()
|
||||||
|
|
||||||
|
fun <T> transaction(
|
||||||
|
block: (Connection) -> T
|
||||||
|
): T {
|
||||||
|
val hasConnection = currentConnection.get() != null
|
||||||
|
var oldConnection: Connection? = null
|
||||||
|
|
||||||
|
if (!hasConnection) {
|
||||||
|
currentConnection.set(Database.getConnection())
|
||||||
|
/*
|
||||||
|
} else if (scope == TxScope.REQUIRES_NEW) {
|
||||||
|
oldConnection = currentConnection.get()
|
||||||
|
|
||||||
|
currentConnection.set(Database.getConnection())
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
val connection = currentConnection.get()
|
||||||
|
|
||||||
|
try {
|
||||||
|
val result = block(connection)
|
||||||
|
|
||||||
|
connection.commit()
|
||||||
|
|
||||||
|
return result
|
||||||
|
} finally {
|
||||||
|
if (!hasConnection) {
|
||||||
|
currentConnection.set(oldConnection)
|
||||||
|
connection.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Database {
|
||||||
|
|
||||||
|
var ds: HikariDataSource? = null
|
||||||
|
|
||||||
|
fun initialize(config: HikariConfig) {
|
||||||
|
val properties = Properties()
|
||||||
|
properties["journal_mode"] = "WAL"
|
||||||
|
|
||||||
|
config.dataSourceProperties = properties
|
||||||
|
config.addDataSourceProperty("cachePrepStmts", "true")
|
||||||
|
config.addDataSourceProperty("prepStmtCacheSize", "250")
|
||||||
|
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048")
|
||||||
|
|
||||||
|
ds = HikariDataSource(config)
|
||||||
|
Migrations.databaseVersionTableCreated = AtomicBoolean(false)
|
||||||
|
Migrations.updateDatabaseIfNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getConnection() = ds?.connection ?: error("Database has not been initialized!")
|
||||||
|
|
||||||
|
fun vacuumDatabase() {
|
||||||
|
getConnection().use {
|
||||||
|
it.autoCommit = true
|
||||||
|
|
||||||
|
it.prepareStatement("VACUUM").use { ps ->
|
||||||
|
ps.executeUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun closeDatabase() {
|
||||||
|
ds?.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
val properties = Properties()
|
||||||
|
properties["journal_mode"] = "WAL"
|
||||||
|
|
||||||
|
val config = HikariConfig().apply {
|
||||||
|
driverClassName = "nl.astraeus.jdbc.Driver"
|
||||||
|
jdbcUrl = "jdbc:stat:webServerPort=6001:jdbc:sqlite:data/daw3.db"
|
||||||
|
username = "sa"
|
||||||
|
password = ""
|
||||||
|
maximumPoolSize = 25
|
||||||
|
isAutoCommit = false
|
||||||
|
dataSourceProperties = properties
|
||||||
|
validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
config.addDataSourceProperty("cachePrepStmts", "true")
|
||||||
|
config.addDataSourceProperty("prepStmtCacheSize", "250")
|
||||||
|
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048")
|
||||||
|
|
||||||
|
ds = HikariDataSource(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
105
src/jvmMain/kotlin/tmpl/db/Migrations.kt
Normal file
105
src/jvmMain/kotlin/tmpl/db/Migrations.kt
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package tmpl.db
|
||||||
|
|
||||||
|
import tmpl.log
|
||||||
|
import java.sql.Connection
|
||||||
|
import java.sql.SQLException
|
||||||
|
import java.sql.Timestamp
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
|
sealed class Migration {
|
||||||
|
class Query(
|
||||||
|
val query: String
|
||||||
|
) : Migration() {
|
||||||
|
override fun toString(): String {
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Code(
|
||||||
|
val code: (Connection) -> Unit
|
||||||
|
) : Migration() {
|
||||||
|
override fun toString(): String {
|
||||||
|
return code.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val DATABASE_MIGRATIONS = arrayOf<Migration>(
|
||||||
|
Migration.Query(
|
||||||
|
"""
|
||||||
|
CREATE TABLE DATABASE_VERSION (
|
||||||
|
ID INTEGER PRIMARY KEY,
|
||||||
|
QUERY TEXT,
|
||||||
|
EXECUTED TIMESTAMP
|
||||||
|
)
|
||||||
|
""".trimIndent()
|
||||||
|
),
|
||||||
|
Migration.Query("SELECT sqlite_version()"),
|
||||||
|
)
|
||||||
|
|
||||||
|
object Migrations {
|
||||||
|
var databaseVersionTableCreated = AtomicBoolean(false)
|
||||||
|
|
||||||
|
fun updateDatabaseIfNeeded() {
|
||||||
|
try {
|
||||||
|
transaction { con ->
|
||||||
|
con.prepareStatement(
|
||||||
|
"""
|
||||||
|
SELECT MAX(ID) FROM DATABASE_VERSION
|
||||||
|
""".trimIndent()
|
||||||
|
).use { ps ->
|
||||||
|
ps.executeQuery().use { rs ->
|
||||||
|
databaseVersionTableCreated.compareAndSet(false, true)
|
||||||
|
|
||||||
|
if(rs.next()) {
|
||||||
|
val maxId = rs.getInt(1)
|
||||||
|
|
||||||
|
for (index in maxId + 1..<DATABASE_MIGRATIONS.size) {
|
||||||
|
executeMigration(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: SQLException) {
|
||||||
|
if (databaseVersionTableCreated.compareAndSet(false, true)) {
|
||||||
|
executeMigration(0)
|
||||||
|
updateDatabaseIfNeeded()
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun executeMigration(index: Int) {
|
||||||
|
transaction { con ->
|
||||||
|
log.debug {
|
||||||
|
"Executing migration $index - [${DATABASE_MIGRATIONS[index]}]"
|
||||||
|
}
|
||||||
|
val description = when(
|
||||||
|
val migration = DATABASE_MIGRATIONS[index]
|
||||||
|
) {
|
||||||
|
is Migration.Query -> {
|
||||||
|
con.prepareStatement(migration.query).use { ps ->
|
||||||
|
ps.execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
migration.query
|
||||||
|
}
|
||||||
|
is Migration.Code -> {
|
||||||
|
migration.code(con)
|
||||||
|
|
||||||
|
migration.code.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
con.prepareStatement("INSERT INTO DATABASE_VERSION VALUES (?, ?, ?)").use { ps ->
|
||||||
|
ps.setInt(1, index)
|
||||||
|
ps.setString(2, description)
|
||||||
|
ps.setTimestamp(3, Timestamp(System.currentTimeMillis()))
|
||||||
|
|
||||||
|
ps.execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user