2

I'm trying to create a very simple transaction manager like this:

object PersistenceManager {
    private val dataSource: DataSource by lazy {
        val config = ConfigFactory.load()
        hikari(config.getConfig("postgres"))
    }

    private fun hikari(appConfig: Config): DataSource {
        // init datasource
    }

    fun <T> transaction(statement: Connection.() -> T): T {
        val connection = dataSource.connection
        try {
            return connection.statement()
        } catch (e: Exception) {
            connection.rollback()
            throw e
        } finally {
            connection?.close()
        }
    }
}

class BrandsDB {

    private val query = "select name from brands order by name"

    fun Connection.getAll(): List<String> {
        val ps = this.prepareStatement(query)
        val rs = ps.executeQuery()
        return JdbcMapperFactory.newInstance()
            .newMapper(String::class.java)!!.stream(rs).toList()
    }
}

class BrandsService(private val brandsDB: BrandsDB) {

    fun getBrands(): List<String> {
        return transaction {
            brandsDB.getAll() // I'd like to do something like this but since
                              // getAll() method belongs to Connecion, I can't
        }
    }
}

So the idea behind all this is that I can have multiple queries in a single transaction block which I can rollback if anything goes wrong (should I have inserts or updates in those queries). I'd also like to avoid passing the connection to the brandsDB.getAll() method, but have it get the connection in an "implicit" way.

I know I could extract getAll() method to its own file or make BrandsDB class an object, but that'd make it possible to call the method anywhere in a static way, which I don't like. I'd also wouldn't like to put any DB related code in the BrandsService, only business logic should go there.

Would this be possible?

carcaret
  • 3,238
  • 2
  • 19
  • 37

1 Answers1

1

Connection is an interface (not class!) in Java, so you may create your own interface that extends it and delegates to it.


interface OurConnection : Connection {
   fun getAll() : SomeType
}

fun <T> PersistenceManager.extendedTransaction(action: OurConnection.() -> T) : T {
   //call the original method
   return PersistenceManager.transaction {
      object : OurConnection, Connection by this {
         override fun getAll() = TODO("implement me")
      }.action()
   }
}

I use the delegated implementation, to implicitly delegate Connection methods in my interface. It is the Connection by this line, where this is the lambda receiver object from the PersistenceManager.transaction function call.

Eugene Petrenko
  • 4,874
  • 27
  • 36