實現(xiàn)自己的 JDBC 驅(qū)動

本文可能是全網(wǎng)最全面最詳細(xì)的 JDBC 驅(qū)動開發(fā)教程雷酪,轉(zhuǎn)載請注明出處

要自己實現(xiàn)一個 JDBC 驅(qū)動無疑是較為困難的淑仆,在此之前我查閱了很多資料,也查到了許多正在提問的帖子哥力,最后都是無疾而終蔗怠,提問者沒有得到想要的回答。而我由于剛好需要做這方面的開發(fā)省骂,也只能硬啃 JDBC 驅(qū)動的文檔了蟀淮,寫這篇算是一個資料的整理,同時我也已經(jīng)完成了一個通用的 JDBC 生成工具钞澳,文末可以下載怠惶。


首先還是來讀文檔,JDBC 的文檔可謂是非常詳細(xì)了轧粟,直接翻到 6.3 JDBC 4.0 API Compliance策治,它很明確的告訴了你應(yīng)該做什么:

1. 支持自動加載的,繼承自 java.sql.Driver 的類
2. 支持“僅向前”的結(jié)果集
3. 支持“只讀的”結(jié)果集并發(fā)類型
4. 支持批量更新
5. 實現(xiàn)以下接口
  1) java.sql.Driver
  2) java.sql.DatabaseMetaData 
  3) java.sql.ParameterMetaData 
  4) java.sql.ResultSetMetaData 
  5) java.sql.Wrapper
  6) javax.sql.DataSource
  7) java.sql.Connection
  8) java.sql.Statement
  9) java.sql.PreparedStatement
  10) java.sql.CallableStatement
  11) java.sql.ResultSet

好的兰吟,看到這里我們會遇到官方文檔挖的第一個坑通惫,其實并非這么多東西都要實現(xiàn)的,實際開發(fā)中混蔼,只實現(xiàn)以下內(nèi)容也可以:

  1) java.sql.Driver
  2) java.sql.DatabaseMetaData 
  3) java.sql.ResultSetMetaData 
  4) java.sql.Connection
  5) java.sql.Statement
  6) java.sql.PreparedStatement
  7) java.sql.ResultSet

只要有這些東西履腋,就足以支撐起一個完整的 JDBC 驅(qū)動。

那么接下來就是實現(xiàn)了,不得不說遵湖,整個 JDBC 的協(xié)議真的又臭又長悔政,所有的類加起來一共要實現(xiàn) 584 個接口函數(shù),而且里面有一大半是不起任何作用的延旧。

限于篇幅谋国,在這里我不可能把所有的函數(shù)予以列出,只看最重要的那些迁沫,其他的請大家自行發(fā)揮芦瘾。


那先來看 Driver:

class MyDriver: Driver {

    companion object {
        init {
            try {
                DriverManager.registerDriver(MyDriver())
            } catch (e: Exception) {
                throw RuntimeException("Can't register $DRIVER_NAME", e)
            }
        }
    }

    override fun acceptsURL(url: String) = url.toLowerCase().startsWith(JDBC_URL)

    override fun connect(url: String, info: Properties?): Connection {
        if (!acceptsURL(url)) throw SQLException("Invalid URL: $url")
        val props = MyDriverUtil.parseMergeProperties(url.replace("jdbc:", ""), info)
        return MyConnection(props)
    }

    ... ...
}

使用 companion objectinit 塊來完成驅(qū)動的自動加載,需要特別注意的是集畅,千萬不要不帶 companion object近弟,如果不帶的話,init 塊實質(zhì)上是類的構(gòu)造函數(shù)挺智,而不是靜態(tài)初始化塊藐吮。

acceptsURL 方法指出了什么樣的 URL 可以被驅(qū)動接受,比如說我們經(jīng)常在使用 mysql 驅(qū)動時看到其 JDBC URL 為 jdbc:mysql://逃贝,就是在 acceptsURL 里接受了這樣的前綴谣辞。

connect 方法用于獲取一個連接,它是在 DriverManager.getConnection 時自動被調(diào)用的沐扳,返回一個非空的 Connection 對象泥从。

這里有個函數(shù),MyDriverUtil.parseMergeProperties 用于將 url 的參數(shù)和 info: Properties 所攜帶的參數(shù)進(jìn)行拼接沪摄,該函數(shù)實現(xiàn)如下:

fun parseMergeProperties(url: String, prop: Properties?) = mutableMapOf<String, String>().apply {
    val uri = URI(url)
    this[PROP_HOST] = uri.host
    this[PROP_PORT] = (if (uri.port == -1) DEFAULT_PORT else uri.port).toString()
    this[PROP_PATH] = uri.path.replaceFirst("/", "")
    if (uri.query != null) {
        this += uri.query.split("&").map { p -> p.split("=").let { i -> Pair(i[0], i[1]) } }.toMap()
    }
    if (prop != null) {
        this += prop.map { e -> Pair(e.key.toString(), e.value.toString()) }.toMap()
    }
}.toMap()

在這段代碼中出現(xiàn)了 MyConnection 這個對象躯嫉,我們下面就來看看如何實現(xiàn)它。


實現(xiàn) Connection

class MyConnection(props: Map<String, String>) : Connection {

    val io = MyIO(props)
    private var isClosed = false
    private val autoCommit = true

    override fun prepareStatement(sql: String) = MyPreparedStatement(sql, this)
    override fun prepareStatement(sql: String, resultSetType: Int, resultSetConcurrency: Int) = MyPreparedStatement(sql, this)
    override fun prepareStatement(sql: String, resultSetType: Int, resultSetConcurrency: Int, resultSetHoldability: Int) = MyPreparedStatement(sql, this)
    override fun prepareStatement(sql: String, autoGeneratedKeys: Int) = MyPreparedStatement(sql, this)
    override fun prepareStatement(sql: String, columnIndexes: IntArray?) = MyPreparedStatement(sql, this)
    override fun prepareStatement(sql: String, columnNames: Array<out String>?) = MyPreparedStatement(sql, this)
    override fun getAutoCommit() = autoCommit
    override fun getWarnings(): SQLWarning? = null
    override fun getCatalog(): String? {
        checkConnection()
        return null
    }
    override fun isValid(timeout: Int) = isClosed
    override fun close() {
        isClosed = true
    }
    override fun isClosed() = isClosed
    override fun isReadOnly() = false
    override fun createStatement() = MyStatement(this)
    override fun createStatement(resultSetType: Int, resultSetConcurrency: Int) = MyStatement(this)
    override fun createStatement(resultSetType: Int, resultSetConcurrency: Int, resultSetHoldability: Int) = MyStatement(this)
    override fun getMetaData() = MyDatabaseMetaData()
    override fun getTransactionIsolation() = Connection.TRANSACTION_NONE
    private fun checkConnection() {
        if (isClosed()) throw SQLException("Connection is closed")
    }

    ... ...

}

這里需要實現(xiàn)的東西就比較多了杨拐,需要實現(xiàn)所有返回 StatementPreparedStatement 的函數(shù)祈餐,也需要實現(xiàn) isClosedclose 這類改變或檢查狀態(tài)的函數(shù)。

這里有一個 MyIO 的對象哄陶,是一個自定義的類帆阳,用于完成真正的數(shù)據(jù)獲取工作,在一會的代碼中就會用到它了屋吨。


在實現(xiàn) Statement 之前蜒谤,要先做一點準(zhǔn)備工作,有一些公用的東西需要被抽象出來至扰。

abstract class MyAbsStatement {
    internal var isClosed = false
    internal var connection: MyConnection
    internal lateinit var resultSet: ResultSet
    protected lateinit var sql: String

    constructor(sql: String, conn: MyConnection) {
        this.sql = sql
        this.connection = conn
    }
    constructor(conn: MyConnection) {
        this.connection = conn
    }

    open fun executeForResultSet(sql: String): Boolean {
        if (isClosed) throw SQLException("This statement is closed.")
        try {
            resultSet = connection.io.internalExecuteQuery(sql)
            return true
        } catch (th: Throwable) {
            throw SQLException(th)
        }
    }

    open fun executeForResult(sql: String): Int {
        if (isClosed) throw SQLException("This statement is closed.")
        try {
            return connection.io.internalExecuteUpdate(sql)
        } catch (th: Throwable) {
            throw SQLException(th)
        }
    }
}

有了這個類之后鳍徽,我們可以繼承它,并且實現(xiàn) Statement 接口:

class MyStatement(conn: MyConnection) : MyAbsStatement(conn), Statement {

    private val batchOps = mutableListOf<String>()

    override fun execute(sql: String, autoGeneratedKeys: Int) = execute(sql)
    override fun execute(sql: String, columnIndexes: IntArray?) = execute(sql)
    override fun execute(sql: String, columnNames: Array<out String>?) = execute(sql)

    override fun clearBatch() {
        batchOps.clear()
    }

    override fun getResultSetType() = ResultSet.TYPE_FORWARD_ONLY
    override fun isCloseOnCompletion() = false
    override fun <T : Any> unwrap(iface: Class<T>): T? = null
    override fun getMaxRows() = 0
    override fun getConnection() = this.connection
    override fun getWarnings(): SQLWarning? = null

    override fun executeQuery(sql: String): ResultSet {
        this.execute(sql)
        return this.getResultSet()
    }

    override fun close() {
        isClosed = true
    }

    override fun isClosed() = this.isClosed
    override fun getMaxFieldSize() = 0
    override fun isWrapperFor(iface: Class<*>) = false
    override fun getUpdateCount() = -1
    override fun getFetchSize() = 0

    override fun executeBatch() = IntArray(batchOps.size).apply {
        this@MyStatement.batchOps.forEachIndexed { index, sql ->
            try {
                this@MyStatement.execute(sql)
                this[index] = SUCCESS_NO_INFO
            } catch (th: Throwable) {
                throw BatchUpdateException(th)
            }
        }
    }

    override fun getQueryTimeout() = 0
    override fun isPoolable() = false

    override fun addBatch(sql: String) {
        batchOps.add(sql)
    }

    override fun getGeneratedKeys(): ResultSet? = null
    override fun getResultSetConcurrency() = ResultSet.CONCUR_READ_ONLY
    override fun getResultSet() = this.resultSet
    override fun execute(sql: String) = executeForResultSet(sql)
    override fun executeUpdate(sql: String) = executeForResult(sql)
    override fun executeUpdate(sql: String, autoGeneratedKeys: Int) = executeUpdate(sql)
    override fun executeUpdate(sql: String, columnIndexes: IntArray?) = executeUpdate(sql)
    override fun executeUpdate(sql: String, columnNames: Array<out String>?) = executeUpdate(sql)
    override fun getFetchDirection() = 0
    override fun getResultSetHoldability() = ResultSet.CLOSE_CURSORS_AT_COMMIT
    override fun getMoreResults() = false
    override fun getMoreResults(current: Int) = false

    ... ...
}

這里主要實現(xiàn) execute 相關(guān)的方法敢课,這個時候定義在抽象類里的 executeForResultexecuteForResultSet 就有了用武之地阶祭,它們可以將所有的請求一并接管起來绷杜。

同樣需要注意的,是在 JDBC 文檔內(nèi)所述的濒募,必須支持批量更新接剩,在 Statement 內(nèi)需要予以支持。


Statement 類似的萨咳,下面來實現(xiàn) PreparedStatement,與 Statement 不同的地方在于疫稿,PreparedStatement 需要用戶自己處理替換問號占位符的操作培他。

先給出這個操作的代碼:

private fun replaceSQL() {
    var idx = 1
    while (sql.indexOf("?") > 1) {
        try {
            val p = parameters[idx]
            sql = sql.replaceFirst("?", if (p == null) "null" else "'$p'")
        } catch (e: IndexOutOfBoundsException) {
            throw SQLException("Can't find defined parameter for position: $idx")
        }
        idx++
    }
}

然后來看看實現(xiàn) PreparedStatement 需要做些什么:


class MyPreparedStatement(sql: String, conn: MyConnection) : MyAbsStatement(sql, conn), PreparedStatement {

    private val parameters = mutableMapOf<Int, String?>()

    override fun execute(): Boolean {
        replaceSQL()
        return super.executeForResultSet(sql)
    }

    override fun execute(sql: String): Boolean {
        this.sql = sql
        return this.execute()
    }

    override fun execute(sql: String, autoGeneratedKeys: Int) = execute(sql)
    override fun execute(sql: String, columnIndexes: IntArray?) = execute(sql)
    override fun execute(sql: String, columnNames: Array<out String>?) = execute(sql)
    override fun getResultSetType() = ResultSet.TYPE_FORWARD_ONLY

    override fun clearParameters() {
        parameters.clear()
    }

    override fun getConnection() = this.connection
    override fun getWarnings(): SQLWarning? = null
    override fun getParameterMetaData(): ParameterMetaData? = null

    override fun executeQuery(): ResultSet {
        this.execute()
        return this.resultSet
    }

    override fun executeQuery(sql: String): ResultSet {
        execute(sql)
        return this.resultSet
    }

    override fun executeUpdate(): Int {
        replaceSQL()
        return executeForResult(sql)
    }

    override fun executeUpdate(sql: String): Int {
        replaceSQL()
        return executeForResult(sql)
    }

    override fun executeUpdate(sql: String, autoGeneratedKeys: Int) = executeUpdate(sql)
    override fun executeUpdate(sql: String, columnIndexes: IntArray?) = executeUpdate(sql)
    override fun executeUpdate(sql: String, columnNames: Array<out String>?) = executeUpdate(sql)

    override fun close() {
        isClosed = true
    }

    override fun isCloseOnCompletion() = false
    override fun getMaxRows() = 0
    override fun isClosed() = isClosed
    override fun getMaxFieldSize() = 0
    override fun getUpdateCount() = 0
    override fun getFetchSize() = 0
    override fun executeBatch(): IntArray? = null
    override fun getQueryTimeout() = 0
    override fun isPoolable() = false
    override fun getGeneratedKeys(): ResultSet? = null
    override fun getResultSetConcurrency() = ResultSet.CONCUR_READ_ONLY
    override fun getResultSet() = this.resultSet
    override fun getMetaData() = MyResultSetMetaData()
    override fun getFetchDirection() = ResultSet.FETCH_FORWARD
    override fun getResultSetHoldability() = ResultSet.CLOSE_CURSORS_AT_COMMIT
    override fun getMoreResults() = false
    override fun getMoreResults(current: Int) = false

     override fun setFloat(parameterIndex: Int, x: Float) {
          pushIntoParameters(parameterIndex, x.toString())
      }
    override fun setLong(parameterIndex: Int, x: Long) {
          pushIntoParameters(parameterIndex, x.toString())
    }
    override fun setDouble(parameterIndex: Int, x: Double) {
          pushIntoParameters(parameterIndex, x.toString())
    }
    override fun setInt(parameterIndex: Int, x: Int) {
          pushIntoParameters(parameterIndex, x.toString())
    }
    override fun setString(parameterIndex: Int, x: String?) {
        pushIntoParameters(parameterIndex, x)
    }
    override fun setTimestamp(parameterIndex: Int, x: Timestamp?) {
          pushIntoParameters(parameterIndex, x.toString())
    }

    private fun pushIntoParameters(index: Int, value: String?) {
        if (index <= 0) throw SQLException("Invalid position for parameter ($index)")
        this.parameters[index] = value
    }

    ... ...
}

可以清楚的看到,在這里主要是用 Map 來保存需要替換的值遗座,然后在執(zhí)行的時候?qū)⒄鎸嵉膮?shù)替換進(jìn)問號中舀凛。然后對于執(zhí)行 SQL 的地方,依然調(diào)用了抽象類里的 executeForResultexecuteForResultSet 方法途蒋。


好了猛遍,現(xiàn)在我們已經(jīng)完成了 StatementPreparedStatement,你可能要問了号坡,能不能跑起代碼看看效果呀懊烤?答案是否定的,因為還沒有做好完整的準(zhǔn)備宽堆,我們還需要一些東西腌紧,下面這個也很關(guān)鍵,是 ResultSet畜隶。

其實這也是 JDBC 坑的一個地方壁肋,通常情況下我們可能會希望寫一點代碼就運行起來看看效果,但是寫 JDBC 驅(qū)動時偏偏不能籽慢,還是要先完整實現(xiàn)才可以浸遗。

一個標(biāo)準(zhǔn)的 ResultSet 實現(xiàn)如下:


class MyResultSet : ResultSet {

    private var isClosed = false
    private var position = -1
    private lateinit var fields: List<String>
    private lateinit var result: List<List<String>>

    constructor(jsonString: String) {
        MyResultSetUtil.jsonToResultData(jsonString) { f, l ->
            fields = f
            result = l
        }
    }

    constructor(fields: List<String>, list: List<List<String>>) {
        this.fields = fields
        this.result = list
    }

    override fun findColumn(columnLabel: String) = fields.indexOf(columnLabel)
    override fun getStatement(): Statement? = null
    override fun getWarnings(): SQLWarning? = null

    override fun beforeFirst() {
        checkIfClosed()
        position = -1
    }

    override fun close() {
        isClosed = true
    }

    override fun isFirst(): Boolean {
        checkIfClosed()
        return position == 0
    }

    override fun isLast(): Boolean {
        checkIfClosed()
        return position == result.size - 1
    }

    override fun last(): Boolean {
        position = result.size - 1
        return result.isNotEmpty()
    }

    override fun isAfterLast(): Boolean {
        checkIfClosed()
        return position >= result.size
    }

    override fun relative(rows: Int): Boolean {
        checkIfClosed()
        return if (rows + position in 1 until result.size) {
            position += rows
            true
        } else {
            false
        }
    }

    override fun absolute(row: Int): Boolean {
        checkIfClosed()
        return if (row in 1 until result.size) {
            position = row
            true
        } else {
            false
        }
    }

    override fun next(): Boolean {
        checkIfClosed()
        return if (position < result.size - 1) {
            position++
            true
        } else {
            false
        }
    }

    override fun first(): Boolean {
        checkIfClosed()
        position = 0
        return result.isNotEmpty()
    }

    override fun afterLast() {
        checkIfClosed()
        position = result.size
    }

    override fun previous(): Boolean {
        checkIfClosed()
        return if (position > 1) {
            position--
            true
        } else {
            false
        }
    }

    override fun isBeforeFirst(): Boolean {
        checkIfClosed()
        return position < 0
    }

    override fun getFloat(columnIndex: Int) = result[position][columnIndex].toFloat()
    override fun getFloat(columnLabel: String) = result[position][findColumn(columnLabel)].toFloat()
    override fun wasNull() = false
    override fun getRow() = position + 1
    override fun getType() = ResultSet.TYPE_SCROLL_INSENSITIVE
    override fun getString(columnIndex: Int) = result[position][columnIndex]
    override fun getString(columnLabel: String) = result[position][findColumn(columnLabel)]
    override fun getLong(columnIndex: Int) = result[position][columnIndex].toLong()
    override fun getLong(columnLabel: String) = result[position][findColumn(columnLabel)].toLong()
    override fun getTimestamp(columnIndex: Int): Timestamp = Timestamp.valueOf(result[position][columnIndex])
    override fun getTimestamp(columnLabel: String): Timestamp = Timestamp.valueOf(result[position][findColumn(columnLabel)])
    override fun getDouble(columnIndex: Int) = result[position][columnIndex].toDouble()
    override fun getDouble(columnLabel: String) = result[position][findColumn(columnLabel)].toDouble()
    override fun getInt(columnIndex: Int) = result[position][columnIndex].toInt()
    override fun getInt(columnLabel: String) = result[position][findColumn(columnLabel)].toInt()

    override fun isClosed() = isClosed
    override fun getFetchSize() = result.size
    override fun getConcurrency() = ResultSet.CONCUR_READ_ONLY

    override fun clearWarnings() {
        checkIfClosed()
    }

    override fun getFetchDirection() = ResultSet.TYPE_SCROLL_INSENSITIVE

    private fun checkIfClosed() {
        if (isClosed()) throw SQLException()
    }
    
    ... ...
}

這個看起來就有點復(fù)雜了,主要是對游標(biāo)的移動和獲取值的操作箱亿,同樣的跛锌,這里也有一個自定義的函數(shù) MyResultSetUtil.jsonToResultData,用于將 json 字符串轉(zhuǎn)換為二維數(shù)組届惋。這也就意味著我們在這里已經(jīng)決定了數(shù)據(jù)的傳遞方式察净,以是 json 作為媒介的。

轉(zhuǎn)換函數(shù)的實現(xiàn)如下:

fun jsonToResultData(jsonString: String, callback:(fields: List<String>, data: List<List<String>>) -> Unit) {
    val fields = getFields(jsonString)
    val data = mutableListOf<List<String>>()
    JSONArray(jsonString).forEach { _, obj -> data.add(fields.map { obj.get(it).toString() }) }
    callback(fields, data)
}

  private fun getFields(jsonString: String) = try {
    JSONArray(jsonString).run { if (length() > 0) getJSONObject(0).keySet().toList() else listOf() }
} catch (th: Throwable) {
    throw SQLException("Cannot get result fields.")
}

最后是補全驅(qū)動所需的另兩個文件盼樟,分別是 DatabaseMetaDataResultSetMetaData

其實這兩個 MetaData 都可以什么都不填氢卡,因為基本上用不到,只是 JDBC 標(biāo)準(zhǔn)說必須實現(xiàn)晨缴,所以才予以實現(xiàn)译秦,通常的處理方法是將其中所有的方法都標(biāo)記為“不支持”:

throw SQLFeatureNotSupportedException()

像這樣就可以了。


好了,是不是現(xiàn)在就想跑起代碼來看看效果筑悴?我們還有最后一步们拙,還記得上面提到的 IO 對象不,現(xiàn)在來實現(xiàn)這個對象阁吝,以完成對數(shù)據(jù)的請求砚婆。當(dāng)然了,在這里我們使用的是寫死的假數(shù)據(jù):

object MyTestRequset {
    var LOCAL_TEST = false

    private val SAMPLEDATA = """[{"id":1, "name":"test1", "age":10},{"id":2, "name":"test2", "age":20},{"id":3, "name":"test3", "age":30},{"id":4, "name":"test4", "age":40},{"id":5, "name":"test5", "age":50}]"""

    @TestOnly
    fun localTestInternalRequest(sql: String) = if (sql.contains("select ")) SAMPLEDATA else "1"
}

class MyIO(private val prop: Map<String, String>) {
    fun internalExecuteQuery(sql: String) = try {
        MyResultSet(internalRequest(sql))
    } catch (th: Throwable) {
        println("internalExecuteQuery error: $th")
        null
    } ?: throw SQLException("cannot parse ResultSet")

    fun internalExecuteUpdate(sql: String) = try {
        internalRequest(sql).toInt()
    } catch (th: Throwable) {
        println("internalExecuteUpdate error: $th")
        -1
    }

    private fun internalRequest(sql: String): String {
        if (MyTestRequset.LOCAL_TEST) return MyTestRequset.localTestInternalRequest(sql)
        TODO("獲取數(shù)據(jù)的真實代碼寫在此處")
    }
}


好了突勇,現(xiàn)在我們的代碼已經(jīng)完整了装盯,可以運行看看效果,在此寫一個 Testcase 來跑一下:

class Test {
    @Test
    fun doTest() {
        MyTestRequset.LOCAL_TEST = true
        Class.forName("com.sample.MyDriver")
        DriverManager.getConnection("jdbc:myurl://0.0.0.0/sampledb", Properties().apply { setProperty(PROP_SCHEMA, "http") }).use { conn ->
            conn.prepareStatement("select * from Data").use { stmt ->
                stmt.executeQuery().use { result ->
                    while (result.next()) {
                        println(result.getString("name"))
                    }
                }
            }
            conn.prepareStatement("insert into Data(name) values (?)").use { stmt ->
                stmt.setString(1, "23333")
                println(stmt.executeUpdate())
            }
        }
    }
}

能順利跑通就說明我們的驅(qū)動已經(jīng)正常工作了甲馋。同樣的埂奈,符合 JDBC 標(biāo)準(zhǔn)的驅(qū)動也可以被 myBatis 等框架加載并使用。


好了定躏,下面是大招账磺,還記得上面的 MyIO 里有一個 TODO 嗎?我們完全可以把對數(shù)據(jù)庫的請求代理掉痊远,讓它成為一個遠(yuǎn)程的數(shù)據(jù)請求垮抗,代碼如下:

private fun internalRequest(sql: String): String {
    var ret: String? = null
    http {
        url = "${if (prop.containsKey(PROP_SCHEMA)) prop[PROP_SCHEMA] else "http"}://${prop[PROP_HOST]}:${prop[PROP_PORT]}/${prop[PROP_PATH]}"
        method = HttpMethod.POST
        if (prop.containsKey(PROP_USER)) authenticatorUser = prop[PROP_USER]
        if (prop.containsKey(PROP_PASSWORD)) authenticatorPassword = prop[PROP_PASSWORD]
        postParam = mutableMapOf("sql" to sql)
        onSuccess { code, text, _ ->
            if (code != 200) throw SQLException("Remote execute SQL failed: $code")
            ret = text
        }
    }
    return ret ?: throw SQLException("Remote SQL result is null.")
}

同時,只需要使用 Ktor 寫幾行代碼碧聪,跑起服務(wù)器借宵,這一切都順理成章了(還不會 Ktor 的小伙伴可以看我的 Ktor 從入門到放棄 系列)。

服務(wù)端代碼:

fun Routing.ISCRouting() {
    post("/sampledb") {
        val sql = call.requestParameters()["sql"] ?: ""
        call.respondText { doRequestDb(sql) }
    }
}

doRequestDb 的過程中矾削,就可以做各種騷操作了壤玫,如分庫分表,權(quán)限控制等哼凯,在此就不贅述了欲间,大家可以發(fā)揮自己的想象力。


最后断部,最上面提到的那個生成 JDBC 驅(qū)動代碼的工具猎贴,可以從我的 Github 下載 EasyJDBC 并編譯,然后愉快的開發(fā)吧蝴光。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末她渴,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蔑祟,更是在濱河造成了極大的恐慌趁耗,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疆虚,死亡現(xiàn)場離奇詭異苛败,居然都是意外死亡满葛,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門罢屈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嘀韧,“玉大人,你說我怎么就攤上這事缠捌〕” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵曼月,是天一觀的道長谊却。 經(jīng)常有香客問我,道長十嘿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任岳锁,我火速辦了婚禮绩衷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘激率。我一直安慰自己咳燕,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布乒躺。 她就那樣靜靜地躺著招盲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嘉冒。 梳的紋絲不亂的頭發(fā)上曹货,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機與錄音讳推,去河邊找鬼顶籽。 笑死,一個胖子當(dāng)著我的面吹牛银觅,可吹牛的內(nèi)容都是我干的礼饱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼究驴,長吁一口氣:“原來是場噩夢啊……” “哼镊绪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起洒忧,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤蝴韭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后熙侍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體万皿,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡摧找,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了牢硅。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蹬耘。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖减余,靈堂內(nèi)的尸體忽然破棺而出综苔,到底是詐尸還是另有隱情,我是刑警寧澤位岔,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布如筛,位于F島的核電站,受9級特大地震影響抒抬,放射性物質(zhì)發(fā)生泄漏杨刨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一擦剑、第九天 我趴在偏房一處隱蔽的房頂上張望妖胀。 院中可真熱鬧,春花似錦惠勒、人聲如沸赚抡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涂臣。三九已至,卻和暖如春售担,著一層夾襖步出監(jiān)牢的瞬間赁遗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工族铆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吼和,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓骑素,卻偏偏與公主長得像炫乓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子献丑,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354