【Vertx构建异步响应式reactive mybatis,mybatis-vertx-adaptor】

Vertx构建异步响应式reactive mybatis,mybatis-vertx-adaptor

背景

基于Vertx构建高性能异步响应后后端服务,而Vertx提供的JdbcClient不太符合ORM,而常用的mybatis,却不支持异步的模式。故整合Vertx、Mybatis,写一套能支持Vertx Future的Mybatis框架。

在保持Vertx、Mybatis各种编程风格的情况下,能快速接入原有的项目中。

介绍

工程名:mybatis-vertx-adaptor

原理:按照Mybatis Mapper机制,重写一套VertxMapper机制以支持Future返回值,以返回值类型以此确定走原生Mapper机制 or VertxMapper机制。

用法:同Mybatis原本用法一致。

案例:

java 复制代码
package com.luomin.example.msva.mapper

import com.luomin.example.msva.entity.User
import com.luomin.mva.annotation.VertxMapper
import io.vertx.core.Future
import org.apache.ibatis.annotations.ResultMap
import org.apache.ibatis.annotations.Select

/**
 * description <br/>
 *
 * @author luomin <br/>
 * 2025/3/13 9:32 <br/>
 */
@VertxMapper
interface VertxUserMapper {

    @ResultMap("userResultMap")
    @Select("select * from user")
    fun listAll(): Future<List<User>>

    @ResultMap("userResultMap")
    @Select("select * from user where id = #{id}")
    fun getById(id: Long): Future<User>

    fun getById2(id: Long): Future<User>

    fun insert(user: User): Future<Void>

}

快速开始

同Mybatis官方一致,只在关键节点替换为Vertx实现类。

  1. 构建Configuration对象
  2. 构建Vertx对象
  3. 构建VertxSqlSessionFactory对象
  4. 构建VertxSqlSession对象
  5. 使用VertxSqlSession API或者获取VertxMapper对象然后进行API操作

工程结构

  • annotation:目前只有一个VertxMapper注解,同Mybatis的Mapper
  • binding:VertxMapper相关的核心实现
  • executor:Executor、StatementHandler、ParameterHandler、ResultSetHandler。Mybatis核心处理器的Vertx适配
  • extension:Kotlin开发的扩展方法
  • logging.jdbc:负责SQL日志打印
  • plugin:同Mybatis的plugin机制
  • session:Vertx的session相关核心实现
  • transaction:Vertx的事务相关核心实现
  • type:Vertx兼容Mybatis原生TypeHandler兼容
  • util:相关工具类

框架实现

SqlSession入口

这里需要自己写一套类似Mybatis原生的SqlSession API,因为我们需要返回值是Future。

实现类,使用 vertx-jdbc-client 实现SqlSession API。

VertxSqlSessionFactory
kotlin 复制代码
package com.luomin.mva.session

import com.luomin.mva.transaction.VertxTransactionFactory
import io.vertx.jdbcclient.JDBCPool
import org.apache.ibatis.session.Configuration

/**
 * description <br></br>
 * @see org.apache.ibatis.session.SqlSessionFactory
 *
 *
 * @author luomin <br></br>
 * 2025/1/23 17:29 <br></br>
 */
interface VertxSqlSessionFactory {

    fun openVertxSession(): VertxSqlSession

    fun openVertxSession(autoCommit: Boolean): VertxSqlSession

    var configuration: Configuration

    var transactionFactory: VertxTransactionFactory

    var jdbcPool: JDBCPool

}
DefaultVertxSqlSessionFactory
kotlin 复制代码
package com.luomin.mva.session.defaults


import com.luomin.mva.executor.VertxExecutor
import com.luomin.mva.extension.Configurations.newExecutor
import com.luomin.mva.session.VertxSqlSession
import com.luomin.mva.session.VertxSqlSessionFactory
import com.luomin.mva.transaction.VertxJdbcTransactionFactory
import com.luomin.mva.transaction.VertxTransactionFactory
import io.vertx.core.Vertx
import io.vertx.jdbcclient.JDBCPool
import org.apache.ibatis.session.Configuration

/**
 * description <br></br>
 *
 * @author luomin <br></br>
 * 2025/1/23 17:30 <br></br>
 * @see org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
 */
class DefaultVertxSqlSessionFactory : VertxSqlSessionFactory {


    constructor(configuration: Configuration, vertx: Vertx) : this(configuration, JDBCPool.pool(vertx, configuration.environment.dataSource))

    constructor(configuration: Configuration, jdbcPool: JDBCPool) : this(configuration, jdbcPool, VertxJdbcTransactionFactory(jdbcPool))

    constructor(configuration: Configuration, jdbcPool: JDBCPool, vertxTransactionFactory: VertxTransactionFactory) {
        this.configuration = configuration
        this.jdbcPool = jdbcPool
        this.transactionFactory = vertxTransactionFactory
    }

    override var transactionFactory: VertxTransactionFactory
    override var configuration: Configuration
    override var jdbcPool: JDBCPool

    override fun openVertxSession(): VertxSqlSession {
        return this.openVertxSession(true)
    }

    override fun openVertxSession(autoCommit: Boolean): VertxSqlSession {
        return this.openSessionFromJDBCPool(autoCommit)
    }

    private fun openSessionFromJDBCPool(autoCommit: Boolean): VertxSqlSession {
        checkNotNull(this.transactionFactory) { "transactionFactory is null" }
        val transaction = if (this.transactionFactory is VertxJdbcTransactionFactory) {
            this.transactionFactory.newTransaction(autoCommit)
        } else {
            this.transactionFactory.newTransaction(jdbcPool, autoCommit)
        }
        val executor = configuration.newExecutor(transaction)
        return createSqlSession(configuration, executor, autoCommit)
    }

    private fun createSqlSession(configuration: Configuration, executor: VertxExecutor, autoCommit: Boolean): VertxSqlSession {
        return DefaultVertxSqlSession(configuration, this, executor, autoCommit)
    }
}
VertxSqlSession
kotlin 复制代码
package com.luomin.mva.session

import io.vertx.core.Future
import io.vertx.sqlclient.SqlConnection
import org.apache.ibatis.cursor.Cursor
import org.apache.ibatis.executor.BatchResult
import org.apache.ibatis.session.Configuration
import org.apache.ibatis.session.RowBounds

/**
 * description <br></br>
 *
 * @author luomin <br></br>
 * 2025/1/23 17:28 <br></br>
 * @see org.apache.ibatis.session.SqlSession
 */
interface VertxSqlSession {

    fun <T> selectOne(statementId: String): Future<T>

    fun <T> selectOne(statementId: String, parameter: Any?): Future<T>

    fun <E> selectList(statement: String): Future<List<E>>

    fun <E> selectList(statement: String, parameter: Any?): Future<List<E>>

    fun <E> selectList(statement: String, parameter: Any?, rowBounds: RowBounds?): Future<List<E>>

    fun <K, V> selectMap(statement: String, mapKey: String): Future<Map<K, V>>

    fun <K, V> selectMap(statement: String, parameter: Any, mapKey: String): Future<Map<K, V>>

    fun <K, V> selectMap(statement: String, parameter: Any, mapKey: String, rowBounds: RowBounds): Future<Map<K, V>>

    fun <T> selectCursor(statement: String): Future<Cursor<T>>

    fun <T> selectCursor(statement: String, parameter: Any): Future<Cursor<T>>

    fun <T> selectCursor(statement: String, parameter: Any, rowBounds: RowBounds): Future<Cursor<T>>

    fun insert(statement: String): Future<Int>

    fun insert(statement: String, parameter: Any?): Future<Int>

    @Deprecated("这种Batch有点问题")
    fun insertBatch(statement: String, parameter: Any?): Future<Int>

    fun addInsertBatch(statement: String, parameter: Any?): Future<Int>

    fun update(statement: String): Future<Int>

    fun update(statement: String, parameter: Any?): Future<Int>

    @Deprecated("这种Batch有点问题")
    fun updateBatch(statement: String, parameter: Any?): Future<Int>

    fun addUpdateBatch(statement: String, parameter: Any?): Future<Int>

    fun flushBatch(): Future<List<BatchResult>>

    fun delete(statement: String): Future<Int>

    fun delete(statement: String, parameter: Any?): Future<Int>

    fun commit(): Future<Void>

    fun commit(force: Boolean): Future<Void>

    fun rollback(): Future<Void>

    fun rollback(force: Boolean): Future<Void>

    fun close(): Future<Void>

    val configuration: Configuration

    val sqlSessionFactory: VertxSqlSessionFactory

    fun <T> getMapper(type: Class<T>): T

    val connection: Future<SqlConnection>

}
DefaultVertxSqlSession
kotlin 复制代码
package com.luomin.mva.session.defaults

import com.luomin.mva.binding.VertxMapperProxyFactory
import com.luomin.mva.executor.VertxExecutor
import com.luomin.mva.session.VertxSqlSession
import com.luomin.mva.session.VertxSqlSessionFactory
import io.vertx.core.Future
import io.vertx.sqlclient.SqlConnection
import org.apache.ibatis.cursor.Cursor
import org.apache.ibatis.exceptions.ExceptionFactory
import org.apache.ibatis.exceptions.TooManyResultsException
import org.apache.ibatis.executor.BatchResult
import org.apache.ibatis.executor.ErrorContext
import org.apache.ibatis.reflection.ParamNameResolver
import org.apache.ibatis.session.Configuration
import org.apache.ibatis.session.RowBounds

/**
 * description <br></br>
 *
 * @author luomin <br></br>
 * 2025/1/23 17:32 <br></br>
 * @see org.apache.ibatis.session.defaults.DefaultSqlSession
 */
class DefaultVertxSqlSession(
    override val configuration: Configuration,
    override val sqlSessionFactory: VertxSqlSessionFactory,
    private val executor: VertxExecutor,
    private val autoCommit: Boolean,
    private var dirty: Boolean = false
) : VertxSqlSession {

    override fun <T> selectOne(statement: String): Future<T> {
        return this.selectOne(statement, null)
    }

    override fun <T> selectOne(statement: String, parameter: Any?): Future<T> {
        return this.selectList<T>(statement, parameter).flatMap {
            if (it.size == 1) {
                return@flatMap Future.succeededFuture(it[0])
            }
            if (it.size > 1) {
                return@flatMap Future.failedFuture(TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + it.size))
            } else return@flatMap Future.succeededFuture()
        }
    }

    override fun <E> selectList(statement: String): Future<List<E>> {
        return this.selectList(statement, null)
    }

    override fun <E> selectList(statement: String, parameter: Any?): Future<List<E>> {
        return this.selectList(statement, parameter, RowBounds.DEFAULT)
    }

    override fun <E> selectList(statement: String, parameter: Any?, rowBounds: RowBounds?): Future<List<E>> {
        val ms = configuration.getMappedStatement(statement)
        // dirty |= ms.isDirtySelect();
        this.dirty = this.dirty or ms.isDirtySelect
        return executor.query<E>(ms, wrapCollection(parameter), rowBounds)
            .recover {
                if (it is Exception) {
                    Future.failedFuture<List<E>>(ExceptionFactory.wrapException("Error querying database.  Cause: $it", it as Exception))
                } else {
                    Future.failedFuture<List<E>>(it)
                }
            }
            .onComplete { ErrorContext.instance().reset() }
    }

    override fun <K, V> selectMap(statement: String, mapKey: String): Future<Map<K, V>> {
        TODO("暂未实现的方法")
    }

    override fun <K, V> selectMap(statement: String, parameter: Any, mapKey: String): Future<Map<K, V>> {
        TODO("暂未实现的方法")
    }

    override fun <K, V> selectMap(statement: String, parameter: Any, mapKey: String, rowBounds: RowBounds): Future<Map<K, V>> {
        TODO("暂未实现的方法")
    }

    override fun <T> selectCursor(statement: String): Future<Cursor<T>> {
        TODO("暂未实现的方法")
    }

    override fun <T> selectCursor(statement: String, parameter: Any): Future<Cursor<T>> {
        TODO("暂未实现的方法")
    }

    override fun <T> selectCursor(statement: String, parameter: Any, rowBounds: RowBounds): Future<Cursor<T>> {
        TODO("暂未实现的方法")
    }

    override fun insert(statement: String): Future<Int> {
        return insert(statement, null)
    }

    override fun insert(statement: String, parameter: Any?): Future<Int> {
        return update(statement, parameter)
    }

    @Deprecated("这种Batch有点问题")
    override fun insertBatch(statement: String, parameter: Any?): Future<Int> {
        return updateBatch(statement, parameter)
    }

    override fun addInsertBatch(statement: String, parameter: Any?): Future<Int> {
        return this.addUpdateBatch(statement, parameter)
    }

    override fun update(statement: String): Future<Int> {
        return update(statement, null)
    }

    override fun update(statement: String, parameter: Any?): Future<Int> {
        val ms = configuration.getMappedStatement(statement)
        this.dirty = true
        return executor.update(ms, wrapCollection(parameter))
            .recover {
                if (it is Exception) {
                    return@recover Future.failedFuture(ExceptionFactory.wrapException("Error updating database.  Cause: $it", it))
                }
                Future.failedFuture(it)
            }
            .onComplete { ErrorContext.instance().reset() }
    }

    @Deprecated("这种Batch有点问题")
    override fun updateBatch(statement: String, parameter: Any?): Future<Int> {
        val ms = configuration.getMappedStatement(statement)
        this.dirty = true
        return executor.updateBatch(ms, wrapCollection(parameter))
            .recover {
                if (it is Exception) {
                    return@recover Future.failedFuture(ExceptionFactory.wrapException("Error updating database.  Cause: $it", it))
                }
                Future.failedFuture(it)
            }
            .onComplete { ErrorContext.instance().reset() }
    }

    override fun addUpdateBatch(statement: String, parameter: Any?): Future<Int> {
        val ms = configuration.getMappedStatement(statement)
        this.dirty = true
        return executor.addBatch(ms, wrapCollection(parameter))
            .recover {
                if (it is Exception) {
                    return@recover Future.failedFuture(ExceptionFactory.wrapException("Error updating database.  Cause: $it", it))
                }
                Future.failedFuture(it)
            }
            .onComplete { ErrorContext.instance().reset() }
    }

    override fun flushBatch(): Future<List<BatchResult>> {
        return executor.flushBatch()
            .recover {
                if (it is Exception) {
                    return@recover Future.failedFuture(ExceptionFactory.wrapException("Error flushing statements.  Cause: $it", it))
                }
                Future.failedFuture(it)
            }
            .onComplete { ErrorContext.instance().reset() }
    }

    override fun delete(statement: String): Future<Int> {
        return update(statement, null)
    }

    override fun delete(statement: String, parameter: Any?): Future<Int> {
        return update(statement, parameter)
    }

    override fun commit(): Future<Void> {
        return commit(false)
    }

    override fun commit(force: Boolean): Future<Void> {
        return this.executor.commit(this.isCommitOrRollbackRequired(force)).onComplete { this.dirty = false }
    }

    override fun rollback(): Future<Void> {
        return rollback(false)
    }

    override fun rollback(force: Boolean): Future<Void> {
        return this.executor.rollback(this.isCommitOrRollbackRequired(force)).onComplete { this.dirty = false }
    }

    override fun close(): Future<Void> {
        return this.executor.close(this.isCommitOrRollbackRequired(false)).onComplete { this.dirty = false }
    }

    override fun <T> getMapper(type: Class<T>): T {
        val mapperProxyFactory = VertxMapperProxyFactory(type)
        return mapperProxyFactory.newInstance(this)
    }

    override val connection: Future<SqlConnection>
        get() = this.executor.transaction.connection

    private fun wrapCollection(`object`: Any?): Any? {
        return ParamNameResolver.wrapToMapIfCollection(`object`, null)
    }

    private fun isCommitOrRollbackRequired(force: Boolean): Boolean {
        return !autoCommit && this.dirty || force
    }
}

响应式Mybatis事务实现

同Mybatis原生的事务实现差不多,这里底层使用 vertx-jdbc-client 来实现。

Mybatis用的原生jdbc,可以直接拿到Connection,而 vertx-jdbc-client 需要先获取 SqlConnection,才能开启事务。

所以这里使用SqlConnection懒加载,然后缓存Future,等需要的时候,直接返回这个Future。

这里很重要,包括后面整合进Spring的Vertx响应式事务管理器。

VertxTransactionFactory
kotlin 复制代码
package com.luomin.mva.transaction

import io.vertx.jdbcclient.JDBCPool
import io.vertx.sqlclient.SqlConnection

/**
 * description <br></br>
 * @see org.apache.ibatis.transaction.TransactionFactory
 *
 * @author luomin <br></br>
 * 2025/1/24 11:03 <br></br>
 */
interface VertxTransactionFactory {

    fun newTransaction(): VertxTransaction

    fun newTransaction(autoCommit: Boolean): VertxTransaction

    fun newTransaction(sqlConnection: SqlConnection): VertxTransaction

    fun newTransaction(sqlConnection: SqlConnection, autoCommit: Boolean): VertxTransaction

    fun newTransaction(jdbcPool: JDBCPool): VertxTransaction

    fun newTransaction(jdbcPool: JDBCPool, autoCommit: Boolean): VertxTransaction

}
VertxJdbcTransactionFactory
kotlin 复制代码
package com.luomin.mva.transaction

import io.vertx.jdbcclient.JDBCPool
import io.vertx.sqlclient.SqlConnection

/**
 * description <br></br>
 *
 * @author luomin <br></br>
 * 2025/1/24 11:04 <br></br>
 */
class VertxJdbcTransactionFactory(private val jdbcPool: JDBCPool) : VertxTransactionFactory {

    override fun newTransaction(): VertxTransaction {
        return this.newTransaction(true)
    }

    override fun newTransaction(autoCommit: Boolean): VertxTransaction {
        return VertxJdbcTransaction(jdbcPool, autoCommit)
    }

    override fun newTransaction(sqlConnection: SqlConnection): VertxTransaction {
        return this.newTransaction(sqlConnection, true)
    }

    override fun newTransaction(sqlConnection: SqlConnection, autoCommit: Boolean): VertxTransaction {
        return VertxJdbcTransaction(sqlConnection, autoCommit)
    }

    override fun newTransaction(jdbcPool: JDBCPool): VertxTransaction {
        return this.newTransaction(jdbcPool, true)
    }

    override fun newTransaction(jdbcPool: JDBCPool, autoCommit: Boolean): VertxTransaction {
        return VertxJdbcTransaction(jdbcPool, autoCommit)
    }

    companion object {

        private val factories = mutableMapOf<JDBCPool, VertxTransactionFactory>()

        fun factory(jdbcPool: JDBCPool): VertxTransactionFactory {
            return factories.computeIfAbsent(jdbcPool) { VertxJdbcTransactionFactory(jdbcPool) }
        }

    }

}
VertxTransaction
kotlin 复制代码
package com.luomin.mva.transaction

import io.vertx.core.Future
import io.vertx.sqlclient.SqlConnection

/**
 * description <br></br>
 *
 * @author luomin <br></br>
 * 2025/1/24 10:13 <br></br>
 * @see org.apache.ibatis.transaction.Transaction
 */
interface VertxTransaction {
    val connection: Future<SqlConnection>

    fun commit(): Future<Void>

    fun rollback(): Future<Void>

    fun close(): Future<Void>
}
VertxJdbcTransaction
kotlin 复制代码
package com.luomin.mva.transaction

import io.vertx.core.Future
import io.vertx.jdbcclient.JDBCPool
import io.vertx.sqlclient.SqlConnection
import io.vertx.sqlclient.Transaction

/**
 * description <br></br>
 *
 * @author luomin <br></br>
 * 2025/1/24 10:15 <br></br>
 * @see org.apache.ibatis.transaction.jdbc.JdbcTransaction
 */
class VertxJdbcTransaction : VertxTransaction {

    private var jdbcPool: JDBCPool? = null
    private var autoCommit: Boolean = false
    private var sqlConnectionFuture: Future<SqlConnection>? = null

    var transaction: Future<Transaction>? = null
        private set

    constructor(jdbcPool: JDBCPool) : this(jdbcPool, true)

    constructor(jdbcPool: JDBCPool, autoCommit: Boolean) {
        this.jdbcPool = jdbcPool
        this.autoCommit = autoCommit
    }

    constructor(sqlConnection: SqlConnection) : this(sqlConnection, true)

    constructor(sqlConnection: SqlConnection, autoCommit: Boolean) {
        this.sqlConnectionFuture = Future.succeededFuture(sqlConnection)
        this.autoCommit = autoCommit
    }

    override val connection: Future<SqlConnection>
        get() {
            if (sqlConnectionFuture == null) {
                this.sqlConnectionFuture = this.jdbcPool!!.connection
                if (!this.autoCommit) {
                    this.transaction = begin()
                }
            }
            return sqlConnectionFuture!!
        }

    override fun commit(): Future<Void> {
        if (autoCommit) return Future.succeededFuture()
        return this.transaction!!.flatMap { it.commit() }
    }

    override fun rollback(): Future<Void> {
        if (autoCommit) return Future.succeededFuture()
        return this.transaction!!.flatMap { it.rollback() }
    }

    override fun close(): Future<Void> {
        return this.transaction!!.flatMap { it.completion() }.flatMap { this.connection.flatMap { it.close() } }
    }

    private fun begin(): Future<Transaction> {
        return this.connection.flatMap { it.begin() }
    }
}

核心实现 VertxMapper 代理

这里最核心的内容,就是需要将Mybatis的MappedStatement进行代理,能够正确路由到VertxSqlSession API 上。

VertxMapperProxyFactory
kotlin 复制代码
package com.luomin.mva.binding

import com.luomin.mva.session.VertxSqlSession
import lombok.Getter
import java.lang.reflect.Method
import java.lang.reflect.Proxy
import java.util.concurrent.ConcurrentHashMap

/**
 * description <br></br>
 *
 * @author luomin <br></br>
 * 2025/1/23 17:28 <br></br>
 * @see org.apache.ibatis.binding.MapperProxyFactory
 */
@Getter
class VertxMapperProxyFactory<T>(private val mapperInterface: Class<T>) {

    private val methodCache = ConcurrentHashMap<Method?, VertxMapperProxy.MapperMethodInvoker>()

    fun newInstance(mapperProxy: VertxMapperProxy<T>): T {
        return Proxy.newProxyInstance(mapperInterface.getClassLoader(), arrayOf<Class<*>>(mapperInterface), mapperProxy) as T
    }

    fun newInstance(sqlSession: VertxSqlSession): T {
        val mapperProxy = VertxMapperProxy(sqlSession, mapperInterface, methodCache)
        return newInstance(mapperProxy)
    }

}
VertxMapperProxy
kotlin 复制代码
package com.luomin.mva.binding

import com.luomin.mva.session.VertxSqlSession
import org.apache.ibatis.reflection.ExceptionUtil
import java.lang.invoke.MethodHandle
import java.lang.invoke.MethodHandles
import java.lang.invoke.MethodType
import java.lang.reflect.Constructor
import java.lang.reflect.InvocationHandler
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method

/**
 * description <br></br>
 *
 * @author luomin <br></br>
 * 2025/1/23 17:27 <br></br>
 * @see org.apache.ibatis.binding.MapperProxy
 */
class VertxMapperProxy<T>(val sqlSession: VertxSqlSession, val mapperInterface: Class<T>, private val methodCache: MutableMap<Method?, MapperMethodInvoker>) : InvocationHandler {

    override fun invoke(proxy: Any, method: Method, args: Array<Any?>?): Any? {
        return try {
            if (Any::class.java == method.declaringClass) {
                method.invoke(this, *(args ?: emptyArray()))
            } else {
                cachedInvoker(method).invoke(proxy, method, args, sqlSession)
            }
        } catch (t: Throwable) {
            throw ExceptionUtil.unwrapThrowable(t)
        }
    }

    private fun cachedInvoker(method: Method): MapperMethodInvoker {
        try {
            return methodCache.computeIfAbsent(method) {
                if (it!!.isDefault) {
                    try {
                        if (privateLookupInMethod == null) {
                            return@computeIfAbsent DefaultMethodInvoker(getMethodHandleJava8(method))
                        } else {
                            return@computeIfAbsent DefaultMethodInvoker(getMethodHandleJava9(method))
                        }
                    } catch (e: IllegalAccessException) {
                        throw RuntimeException(e)
                    } catch (e: InstantiationException) {
                        throw RuntimeException(e)
                    } catch (e: InvocationTargetException) {
                        throw RuntimeException(e)
                    } catch (e: NoSuchMethodException) {
                        throw RuntimeException(e)
                    }
                } else {
                    return@computeIfAbsent PlainMethodInvoker(VertxMapperMethod(mapperInterface, method, sqlSession.configuration))
                }
            }
        } catch (re: RuntimeException) {
            val cause = re.cause
            throw cause ?: re
        }
    }

    private fun getMethodHandleJava9(method: Method): MethodHandle {
        val declaringClass = method.declaringClass
        return (privateLookupInMethod!!.invoke(null, declaringClass, MethodHandles.lookup()) as MethodHandles.Lookup).findSpecial(
            declaringClass, method.name, MethodType.methodType(method.returnType, method.parameterTypes),
            declaringClass
        )
    }

    private fun getMethodHandleJava8(method: Method): MethodHandle {
        val declaringClass = method.declaringClass
        return lookupConstructor!!.newInstance(declaringClass, ALLOWED_MODES)!!.unreflectSpecial(method, declaringClass)
    }

    interface MapperMethodInvoker {
        fun invoke(proxy: Any, method: Method, args: Array<Any?>?, sqlSession: VertxSqlSession): Any?
    }

    private class PlainMethodInvoker(private val mapperMethod: VertxMapperMethod) : MapperMethodInvoker {
        override fun invoke(proxy: Any, method: Method, args: Array<Any?>?, sqlSession: VertxSqlSession): Any? {
            return mapperMethod.execute(sqlSession, args)
        }
    }

    private class DefaultMethodInvoker(private val methodHandle: MethodHandle) : MapperMethodInvoker {
        override fun invoke(proxy: Any, method: Method, args: Array<Any?>?, sqlSession: VertxSqlSession): Any? {
            return methodHandle.bindTo(proxy).invokeWithArguments(*(args ?: emptyArray()))
        }
    }

    companion object {
        private const val ALLOWED_MODES = (MethodHandles.Lookup.PRIVATE or MethodHandles.Lookup.PROTECTED or MethodHandles.Lookup.PACKAGE or MethodHandles.Lookup.PUBLIC)
        private val lookupConstructor: Constructor<MethodHandles.Lookup?>?
        private val privateLookupInMethod: Method?

        init {
            var privateLookupIn: Method? = try {
                MethodHandles::class.java.getMethod("privateLookupIn", Class::class.java, MethodHandles.Lookup::class.java)
            } catch (e: NoSuchMethodException) {
                null
            }
            privateLookupInMethod = privateLookupIn

            var lookup: Constructor<MethodHandles.Lookup?>? = null
            if (privateLookupInMethod == null) {
                // JDK 1.8
                try {
                    lookup = MethodHandles.Lookup::class.java.getDeclaredConstructor(Class::class.java, Int::class.javaPrimitiveType)
                    lookup.setAccessible(true)
                } catch (e: NoSuchMethodException) {
                    throw IllegalStateException("There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.", e)
                } catch (e: Exception) {
                    lookup = null
                }
            }
            lookupConstructor = lookup
        }
    }
}
VertxMapperMethod
kotlin 复制代码
package com.luomin.mva.binding

import com.luomin.mva.session.VertxSqlSession
import io.vertx.core.Future
import org.apache.ibatis.annotations.MapKey
import org.apache.ibatis.binding.BindingException
import org.apache.ibatis.mapping.MappedStatement
import org.apache.ibatis.mapping.SqlCommandType
import org.apache.ibatis.reflection.MetaObject
import org.apache.ibatis.reflection.ParamNameResolver
import org.apache.ibatis.reflection.TypeParameterResolver
import org.apache.ibatis.session.Configuration
import org.apache.ibatis.session.ResultHandler
import org.apache.ibatis.session.RowBounds
import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type

/**
 * description <br></br>
 *
 * @author luomin <br></br>
 * 2025/1/23 17:27 <br></br>
 * @see org.apache.ibatis.binding.MapperMethod
 */
class VertxMapperMethod(mapperInterface: Class<*>, method: Method, config: Configuration) {
    private val command = SqlCommand(config, mapperInterface, method)
    private val method = MethodSignature(config, mapperInterface, method)

    fun execute(sqlSession: VertxSqlSession, args: Array<Any?>?): Any? {
        if (!method.returnsFuture) {
            throw BindingException("Mapper method '" + command.name + " must be return io.vertx.core.Future.class .")
        }
        val result: Any?
        when (command.type) {
            SqlCommandType.INSERT -> {
                val param = method.convertArgsToSqlCommandParam(args)
                result = sqlSession.insert(command.name, param).map { rowCount: Int -> this.rowCountResult(rowCount) }
            }

            SqlCommandType.UPDATE -> {
                val param = method.convertArgsToSqlCommandParam(args)
                result = sqlSession.update(command.name, param).map { rowCount: Int -> this.rowCountResult(rowCount) }
            }

            SqlCommandType.DELETE -> {
                val param = method.convertArgsToSqlCommandParam(args)
                result = sqlSession.delete(command.name, param).map { rowCount: Int -> this.rowCountResult(rowCount) }
            }

            SqlCommandType.SELECT -> result = if (method.returnsVoid) {
                null
            } else if (method.returnsMany) {
                executeForMany<Any?>(sqlSession, args)
            } else {
                val param = method.convertArgsToSqlCommandParam(args)
                sqlSession.selectOne<Any?>(command.name, param)
            }

            else -> throw BindingException("Unknown execution method for: " + command.name)
        }
        if (result == null && method.returnType.isPrimitive && !method.returnsVoid) {
            throw BindingException(("Mapper method '" + command.name + " attempted to return null from a method with a primitive return type (" + method.returnType + ")."))
        }
        return result
    }

    private fun <E> executeForMany(sqlSession: VertxSqlSession, args: Array<Any?>?): Any? {
        val result: Future<List<E>>
        val param = method.convertArgsToSqlCommandParam(args)
        if (method.hasRowBounds()) {
            val rowBounds: RowBounds? = method.extractRowBounds(args)
            result = sqlSession.selectList(command.name, param, rowBounds)
        } else {
            result = sqlSession.selectList(command.name, param)
        }
        if (!method.returnType.isAssignableFrom(result.javaClass)) {
            if (method.returnType.isArray) {
                return result.map { this.convertToArray(it) }
            }
            return result.map { convertToDeclaredCollection<E?>(sqlSession.configuration, it as MutableList<E?>?) }
        }
        return result
    }

    private fun <E> convertToDeclaredCollection(config: Configuration, list: MutableList<E?>?): Any? {
        val collection: Any? = config.getObjectFactory().create(method.returnType)
        val metaObject: MetaObject = config.newMetaObject(collection)
        metaObject.addAll<E?>(list)
        return collection
    }

    private fun <E> convertToArray(list: List<E>): Any {
        val arrayComponentType = method.returnType.componentType
        val array = java.lang.reflect.Array.newInstance(arrayComponentType, list.size)
        if (!arrayComponentType.isPrimitive) {
            val collection = list as java.util.Collection<*>
            return collection.toArray(array as Array<*>)
        }
        for (i in list.indices) {
            java.lang.reflect.Array.set(array, i, list[i])
        }
        return array
    }

    private fun rowCountResult(rowCount: Int): Any? {
        val result: Any?
        if (method.returnsVoid) {
            result = null
        } else {
            val returnInferredType = method.returnEntityType
            result = if (Int::class.javaObjectType == returnInferredType) {
                rowCount
            } else if (Long::class.javaObjectType == returnInferredType) {
                rowCount.toLong()
            } else if (Boolean::class.javaObjectType == returnInferredType) {
                rowCount > 0
            } else {
                throw BindingException("Mapper method '" + command.name + "' has an unsupported return type: " + returnInferredType)
            }
        }
        return result
    }

    /**
     * @see org.apache.ibatis.binding.MapperMethod.SqlCommand
     */
    class SqlCommand(configuration: Configuration, mapperInterface: Class<*>, method: Method) {
        internal var name: String
        internal var type: SqlCommandType

        init {
            val methodName = method.name
            val declaringClass = method.declaringClass
            val ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration)
            if (ms == null) {
                throw BindingException(("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName))
            } else {
                name = ms.id
                type = ms.sqlCommandType
                if (type == SqlCommandType.UNKNOWN) {
                    throw BindingException("Unknown execution method for: $name")
                }
            }
        }

        private fun resolveMappedStatement(
            mapperInterface: Class<*>, methodName: String?,
            declaringClass: Class<*>, configuration: Configuration
        ): MappedStatement? {
            val statementId = mapperInterface.getName() + "." + methodName
            if (configuration.hasStatement(statementId)) {
                return configuration.getMappedStatement(statementId)
            } else if (mapperInterface == declaringClass) {
                return null
            }
            for (superInterface in mapperInterface.interfaces) {
                if (declaringClass.isAssignableFrom(superInterface)) {
                    val ms: MappedStatement? = resolveMappedStatement(
                        superInterface, methodName,
                        declaringClass, configuration
                    )
                    if (ms != null) {
                        return ms
                    }
                }
            }
            return null
        }
    }

    /**
     * @see org.apache.ibatis.binding.MapperMethod.MethodSignature
     */
    class MethodSignature(configuration: Configuration, mapperInterface: Class<*>?, method: Method) {
        internal val returnsMany: Boolean
        internal val returnsVoid: Boolean
        internal var returnsFuture: Boolean
        internal var returnType: Class<*>
        internal val returnEntityType: Class<*>
        private val mapKey: String?
        private val resultHandlerIndex: Int?
        private val rowBoundsIndex: Int?
        private val paramNameResolver: ParamNameResolver

        init {
            val resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface)
            this.returnType = parseReturnClass(resolvedReturnType) ?: method.returnType
            this.returnsFuture = Future::class.java.isAssignableFrom(this.returnType)
            if (!this.returnsFuture) throw BindingException("Method '" + method.name + "' must return type Future.class")
            // 2025/4/14 by luomin. 解析泛型
            this.returnEntityType = parseEntityClass(resolvedReturnType) ?: Void::class.java
            this.returnsVoid = this.returnEntityType == Void::class.java
            val futureResultType = parseFutureContainerClass(method.genericReturnType) ?: Void::class.java
            this.returnsMany = configuration.getObjectFactory().isCollection(futureResultType) || futureResultType.isArray
            this.mapKey = getMapKey(method)
            this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds::class.java)
            this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler::class.java)
            this.paramNameResolver = ParamNameResolver(configuration, method)
        }

        fun convertArgsToSqlCommandParam(args: Array<Any?>?): Any? {
            return paramNameResolver.getNamedParams(args)
        }

        fun hasRowBounds(): Boolean {
            return rowBoundsIndex != null
        }

        fun extractRowBounds(args: Array<Any?>?): RowBounds? {
            return if (hasRowBounds()) args?.get(rowBoundsIndex!!) as RowBounds? else null
        }

        private fun getUniqueParamIndex(method: Method, paramType: Class<*>): Int? {
            var index: Int? = null
            val argTypes = method.parameterTypes
            for (i in argTypes.indices) {
                if (paramType.isAssignableFrom(argTypes[i])) {
                    if (index == null) {
                        index = i
                    } else {
                        throw BindingException(method.name + " cannot have multiple " + paramType.getSimpleName() + " parameters")
                    }
                }
            }
            return index
        }

        private fun getMapKey(method: Method): String? {
            var mapKey: String? = null
            if (MutableMap::class.java.isAssignableFrom(method.returnType)) {
                val mapKeyAnnotation = method.getAnnotation<MapKey?>(MapKey::class.java)
                if (mapKeyAnnotation != null) {
                    mapKey = mapKeyAnnotation.value
                }
            }
            return mapKey
        }

        companion object {

            fun parseReturnClass(resolvedReturnType: Type): Class<*>? {
                return if (resolvedReturnType is Class<*>) {
                    resolvedReturnType
                } else if (resolvedReturnType is ParameterizedType) {
                    resolvedReturnType.rawType as Class<*>
                } else {
                    null
                }
            }

            fun parseEntityClass(resolvedReturnType: Type): Class<*>? {
                return if (resolvedReturnType is Class<*>) {
                    resolvedReturnType
                } else if (resolvedReturnType is ParameterizedType) {
                    val containerType = resolvedReturnType.actualTypeArguments[0]
                    if (containerType is ParameterizedType) {
                        containerType.actualTypeArguments[0] as Class<*>
                    } else {
                        containerType as Class<*>
                    }
                } else {
                    null
                }
            }

            fun parseFutureContainerClass(resolvedReturnType: Type): Class<*>? {
                try {
                    return if (resolvedReturnType is Class<*>) {
                        resolvedReturnType
                    } else if (resolvedReturnType is ParameterizedType) {
                        val containerType = resolvedReturnType.actualTypeArguments[0]
                        if (containerType is ParameterizedType) {
                            containerType.rawType as Class<*>
                        } else {
                            containerType as Class<*>
                        }
                    } else {
                        null
                    }
                } catch (_: Exception) {
                    // 2025/9/22 by luomin. 返回的不是集合容器, 会报错
                    return null
                }
            }

            @Deprecated("不好用")
            fun parseInferredClass(genericType: Type?, getFutureFirst: Boolean = false): Class<*> {
                var inferredClass: Class<*>? = null
                if (genericType is ParameterizedType) {
                    val type = genericType
                    val typeArguments = type.actualTypeArguments
                    if (typeArguments.size > 0) {
                        val typeArgument = typeArguments[0]
                        if (typeArgument is ParameterizedType) {
                            if (getFutureFirst) return typeArgument.rawType as Class<*>
                            inferredClass = typeArgument.actualTypeArguments[0] as Class<*>
                        } else if (typeArgument is Class<*>) {
                            inferredClass = typeArgument
                        } else {
                            var typeName = typeArgument.typeName
                            if (typeName.contains(" ")) {
                                typeName = typeName.substring(typeName.lastIndexOf(" ") + 1)
                            }
                            if (typeName.contains("<")) {
                                typeName = typeName.substring(0, typeName.indexOf("<"))
                            }
                            try {
                                inferredClass = Class.forName(typeName)
                            } catch (e: Exception) {
                                e.printStackTrace()
                            }
                        }
                    }
                }
                if (inferredClass == null && genericType is Class<*>) {
                    inferredClass = genericType
                }
                return inferredClass!!
            }
        }
    }
}

核心实现 VertxExecutor 执行器

这里需要自己写一套类似Mybatis原生的Executor API,因为我们需要返回值是Future。

执行器只负责 VertxStatementHandler 的生命周期。

我只实现了Mybatis 的 SimpleExecutor 和 BatchExecutor 两种模式,合在一起,VertxJdbcExecutor。

VertxExecutor
kotlin 复制代码
package com.luomin.mva.executor

import com.luomin.mva.transaction.VertxTransaction
import io.vertx.core.Future
import org.apache.ibatis.executor.BatchResult
import org.apache.ibatis.mapping.BoundSql
import org.apache.ibatis.mapping.MappedStatement
import org.apache.ibatis.session.RowBounds

/**
 * description <br></br>
 *
 * @author luomin <br></br>
 * 2025/1/24 10:59 <br></br>
 * @see org.apache.ibatis.executor.Executor
 */
interface VertxExecutor {
    fun <E> query(ms: MappedStatement, parameter: Any?, rowBounds: RowBounds?): Future<List<E>>

    fun <E> query(ms: MappedStatement, parameter: Any?, rowBounds: RowBounds?, boundSql: BoundSql?): Future<List<E>>

    fun <E> doQuery(ms: MappedStatement, parameter: Any?, rowBounds: RowBounds?, boundSql: BoundSql?): Future<List<E>>

    fun update(ms: MappedStatement, parameter: Any?): Future<Int>

    @Deprecated("这种Batch有点问题")
    fun updateBatch(ms: MappedStatement, parameter: Any?): Future<Int>

    fun addBatch(ms: MappedStatement, parameter: Any?): Future<Int>

    fun doUpdate(ms: MappedStatement, parameter: Any?): Future<Int>

    @Deprecated("这种Batch有点问题")
    fun doUpdateBatch(ms: MappedStatement, parameter: Any?): Future<Int>

    fun flushBatch(): Future<List<BatchResult>>

    fun commit(required: Boolean): Future<Void>

    fun rollback(required: Boolean): Future<Void>

    fun close(forceRollback: Boolean): Future<Void>

    var transaction: VertxTransaction
}
VertxJdbcExecutor
kotlin 复制代码
package com.luomin.mva.executor

import com.luomin.mva.executor.statement.VertxStatementHandler
import com.luomin.mva.extension.Configurations.newStatementHandler
import com.luomin.mva.logging.jdbc.VertxSqlConnectionLogger
import com.luomin.mva.transaction.VertxTransaction
import io.vertx.core.Future
import io.vertx.sqlclient.PreparedStatement
import io.vertx.sqlclient.SqlConnection
import io.vertx.sqlclient.Tuple
import org.apache.ibatis.executor.BatchResult
import org.apache.ibatis.executor.ErrorContext
import org.apache.ibatis.logging.Log
import org.apache.ibatis.mapping.BoundSql
import org.apache.ibatis.mapping.MappedStatement
import org.apache.ibatis.session.RowBounds

/**
 * description <br></br>
 *
 * @author luomin <br></br>
 * 2025/1/24 10:59 <br></br>
 * @see org.apache.ibatis.executor.SimpleExecutor
 */
class VertxJdbcExecutor(override var transaction: VertxTransaction) : VertxExecutor {

    private var queryStack: Int = 0

    // org.apache.ibatis.executor.BatchExecutor
    private val statementList = mutableListOf<Any>()
    private val statementHandlerList = mutableListOf<VertxStatementHandler<Any>>()
    private val batchResultList = mutableListOf<BatchResult>()
    private var currentSql: String? = null
    private var currentStatement: MappedStatement? = null

    override fun <E> query(ms: MappedStatement, parameter: Any?, rowBounds: RowBounds?): Future<List<E>> {
        val boundSql = ms.getBoundSql(parameter)
        return this.query(ms, parameter, rowBounds, boundSql)
    }

    override fun <E> query(ms: MappedStatement, parameter: Any?, rowBounds: RowBounds?, boundSql: BoundSql?): Future<List<E>> {
        ErrorContext.instance().resource(ms.resource).activity("executing a query").`object`(ms.id)
        try {
            queryStack++
            return this.doQuery(ms, parameter, rowBounds, boundSql)
        } finally {
            queryStack--
        }
    }

    override fun <E> doQuery(ms: MappedStatement, parameter: Any?, rowBounds: RowBounds?, boundSql: BoundSql?): Future<List<E>> {
        val configuration = ms.configuration!!
        val handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, boundSql, false)
        val stmt = prepareStatement(handler, ms.statementLog)
        val query = stmt.flatMap { handler.query<E>(it) }
        query.onComplete { this.closeStatement(stmt.result()) }
        return query
    }

    override fun update(ms: MappedStatement, parameter: Any?): Future<Int> {
        ErrorContext.instance().resource(ms.resource).activity("executing an update").`object`(ms.id)
        return doUpdate(ms, parameter)
    }

    @Deprecated("这种Batch有点问题")
    override fun updateBatch(ms: MappedStatement, parameter: Any?): Future<Int> {
        ErrorContext.instance().resource(ms.resource).activity("executing an update batch").`object`(ms.id)
        return doUpdateBatch(ms, parameter)
    }

    /**
     * @see org.apache.ibatis.executor.BatchExecutor
     */
    override fun addBatch(ms: MappedStatement, parameter: Any?): Future<Int> {
        ErrorContext.instance().resource(ms.resource).activity("executing an update batch").`object`(ms.id)
        val configuration = ms.configuration
        val boundSql = ms.getBoundSql(parameter)
        val sql = boundSql.sql
        return if (sql == currentSql && ms == currentStatement) {
            val last = statementList.size - 1
            val stmt = statementList[last]
            val handler = statementHandlerList[last]
            // 只准备参数
            val tuple = Tuple.tuple()
            handler.parameterize(stmt, parameter, tuple)
            val batchResult = batchResultList[last]
            batchResult.addParameterObject(parameter)
            handler.addBatch(stmt, parameter, tuple)
        } else {
            // 因为vertx-jdbc不支持addBatch, 所以这里跟mybatis原本不一样, 要复用StatementHandler
            // 这里的parameter只为生成语句
            val handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, false)
            // 准备语句与参数
            val tuple = Tuple.tuple()
            getConnection(ms.statementLog, true).flatMap { handler.prepare(it) }.map {
                handler.parameterize(it, parameter, tuple)
                it
            }.flatMap {
                currentSql = sql
                currentStatement = ms
                statementList.add(it)
                statementHandlerList.add(handler)
                batchResultList.add(BatchResult(ms, sql, parameter))
                handler.addBatch(it, parameter, tuple)
            }
        }
    }

    override fun doUpdate(ms: MappedStatement, parameter: Any?): Future<Int> {
        val configuration = ms.configuration
        val handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, false)
        val stmt = prepareStatement(handler, ms.statementLog)
        val update = stmt.flatMap { handler.update(it) }
        update.onComplete { closeStatement(stmt.result()) }
        return update
    }

    @Deprecated("这种Batch有点问题")
    override fun doUpdateBatch(ms: MappedStatement, parameter: Any?): Future<Int> {
        val configuration = ms.configuration
        val handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, true)
        val stmt = prepareStatement(handler, ms.statementLog)
        val update = stmt.flatMap { handler.updateBatch(it) }
        update.onComplete { closeStatement(stmt.result()) }
        return update
    }

    override fun flushBatch(): Future<List<BatchResult>> {
        val batchFutureList = mutableListOf<Future<BatchResult>>()
        for ((i, stmt) in statementList.withIndex()) {
            val batchResult = batchResultList[i]
            val handler = statementHandlerList[i]
            val batchFuture = handler.flushBatch(stmt).onComplete { closeStatement(stmt) }.map { batchResult.apply { this.updateCounts = it.toIntArray() } }
            batchFutureList.add(batchFuture)
        }
        return Future.all(batchFutureList).map {
            val results = mutableListOf<BatchResult>()
            for (i in 0 until statementList.size) {
                val resultAt = it.resultAt<BatchResult>(i)
                results.add(resultAt)
            }
            results
        }
    }

    override fun commit(required: Boolean): Future<Void> {
        if (required) {
            return transaction.commit()
        }
        return Future.succeededFuture()
    }

    override fun rollback(required: Boolean): Future<Void> {
        if (required) {
            return transaction.rollback()
        }
        return Future.succeededFuture()
    }

    override fun close(forceRollback: Boolean): Future<Void> {
        // 丢弃rollback的异常
        return this.rollback(forceRollback).recover { Future.succeededFuture() }.flatMap { this.transaction.close() }
    }

    private fun prepareStatement(handler: VertxStatementHandler<Any>, statementLog: Log): Future<Any> {
        return getConnection(statementLog, false).flatMap { handler.prepare(it) }.map {
            handler.parameterize(it)
            it
        }
    }

    private fun getConnection(statementLog: Log, batch: Boolean): Future<SqlConnection> {
        val connection = transaction.connection
        if (statementLog.isDebugEnabled) {
            return connection.map { VertxSqlConnectionLogger.newInstance(it, statementLog, queryStack, batch) }
        }
        return connection
    }

    private fun closeStatement(statement: Any?): Future<Void> {
        if (statement == null) {
            return Future.succeededFuture()
        }
        return if (statement is PreparedStatement) {
            statement.close()
        } else Future.succeededFuture()
    }
}

核心实现 VertxStatementHandler 语句处理器

VertxStatementHandler 负责准备语句、持有参数处理器用于设置参数、持有结果集处理器用于处理返回值。

这里的实现类,只有 VertxPreparedQueryStatementHandler,用于处理Vertx的PreparedQuery<RowSet>。

VertxStatementHandler
kotlin 复制代码
package com.luomin.mva.executor.statement

import com.luomin.mva.executor.parameter.TupleParameterHandler
import io.vertx.core.Future
import io.vertx.sqlclient.SqlConnection
import io.vertx.sqlclient.Tuple
import org.apache.ibatis.mapping.BoundSql

/**
 * description <br></br>
 *
 * @author luomin <br></br>
 * 2025/2/5 15:46 <br></br>
 * @see org.apache.ibatis.executor.statement.StatementHandler
 */
interface VertxStatementHandler<S> {
    fun prepare(connection: SqlConnection): Future<S>

    fun parameterize(statement: S)

    fun parameterize(statement: S, parameterObject: Any?, tuple: Tuple)

    fun <E> query(statement: S): Future<List<E>>

    fun update(statement: S): Future<Int>

    @Deprecated("这种Batch有点问题")
    fun updateBatch(statement: S): Future<Int>

    fun addBatch(statement: Any, parameterObject: Any?, parameterTuple: Tuple): Future<Int>

    fun flushBatch(statement: S): Future<Array<Int>>

    var boundSql: BoundSql?

    var parameterHandler: TupleParameterHandler
}
VertxPreparedQueryStatementHandler
kotlin 复制代码
package com.luomin.mva.executor.statement

import com.luomin.mva.executor.VertxExecutor
import com.luomin.mva.executor.keygen.adapter.KeyGeneratorAdapter
import com.luomin.mva.executor.parameter.TupleParameterHandler
import com.luomin.mva.executor.resultset.RowSetResultSetHandler
import com.luomin.mva.extension.Configurations.newResultSetHandler
import com.luomin.mva.extension.Configurations.newTupleParameterHandler
import com.luomin.mva.util.ParamMapUtils
import io.vertx.core.Future
import io.vertx.sqlclient.*
import org.apache.ibatis.binding.MapperMethod
import org.apache.ibatis.mapping.BoundSql
import org.apache.ibatis.mapping.MappedStatement
import org.apache.ibatis.session.RowBounds

/**
 * <br>description<br/>
 *
 * @author luomin
 * <br>2025/4/21 17:53<br/>
 */
class VertxPreparedQueryStatementHandler(
    private val executor: VertxExecutor,
    private val mappedStatement: MappedStatement,
    parameterObject: Any?,
    rowBounds: RowBounds?,
    override var boundSql: BoundSql?,
    private val batchMode: Boolean
) : VertxStatementHandler<PreparedQuery<RowSet<Row>>> {

    private val configuration = mappedStatement.configuration
    private val resultSetHandler: RowSetResultSetHandler
    private val parameterObjects = mutableListOf<Any?>()
    private val parameterTuples = mutableListOf<Tuple>()
    override var parameterHandler: TupleParameterHandler

    init {
        this.boundSql = this.buildBoundSql(boundSql, parameterObject)
        this.parameterHandler = configuration.newTupleParameterHandler(mappedStatement, parameterObject, boundSql!!)
        this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, boundSql!!)
    }

    override fun prepare(connection: SqlConnection): Future<PreparedQuery<RowSet<Row>>> {
        return Future.succeededFuture(connection.preparedQuery(this.boundSql!!.sql))
    }

    override fun parameterize(statement: PreparedQuery<RowSet<Row>>) {
        if (!batchMode) parameterHandler.setParameters(statement) else parameterHandler.setBatchParameters(statement)
    }

    override fun parameterize(statement: PreparedQuery<RowSet<Row>>, parameterObject: Any?, tuple: Tuple) {
        parameterHandler.setParameters(statement, parameterObject, tuple)
    }

    override fun <E> query(statement: PreparedQuery<RowSet<Row>>): Future<List<E>> {
        return statement.execute(parameterHandler.parameterTuple).map { resultSetHandler.handleResultSets(it) }
    }

    override fun update(statement: PreparedQuery<RowSet<Row>>): Future<Int> {
        return this.doUpdate(statement, boundSql!!.parameterObject, parameterHandler.parameterTuple)
    }

    @Deprecated("这种Batch有点问题")
    override fun updateBatch(statement: PreparedQuery<RowSet<Row>>): Future<Int> {
        return statement.executeBatch(parameterHandler.parameterTuples).flatMap { rowSet: RowSet<Row> ->
            val parameterObject = boundSql!!.parameterObject
            val keyGenerator = KeyGeneratorAdapter.adaptive(mappedStatement.keyGenerator)
            val voidFuture = keyGenerator.processAfter(executor, mappedStatement, rowSet, parameterObject)
            voidFuture.map { rowSet.rowCount() }
        }
    }

    override fun addBatch(statement: Any, parameterObject: Any?, parameterTuple: Tuple): Future<Int> {
        this.parameterObjects.add(parameterObject)
        this.parameterTuples.add(parameterTuple)
        return Future.succeededFuture(1)
    }

    override fun flushBatch(statement: PreparedQuery<RowSet<Row>>): Future<Array<Int>> {
        return Future.all(this.parameterTuples.mapIndexed { i, tuple -> this.doUpdate(statement, this.parameterObjects[i], tuple) }).map {
            val values = arrayOfNulls<Int>(this.parameterTuples.size)
            for (i in 0 until this.parameterTuples.size) {
                val resultAt = it.resultAt<Int>(i)
                values[i] = resultAt
            }
            values as Array<Int>
        }
    }

    private fun doUpdate(statement: PreparedQuery<RowSet<Row>>, parameterObject: Any?, parameterTuple: Tuple): Future<Int> {
        return statement.execute(parameterTuple).flatMap { rowSet: RowSet<Row> ->
            val keyGenerator = KeyGeneratorAdapter.adaptive(mappedStatement.keyGenerator)
            val voidFuture = keyGenerator.processAfter(executor, mappedStatement, rowSet, parameterObject)
            voidFuture.map { rowSet.rowCount() }
        }
    }

    @Deprecated("这种Batch有点问题")
    private fun buildBoundSql(boundSql: BoundSql?, parameterObject: Any?): BoundSql? {
        if (boundSql != null) {
            return boundSql
        }
        return if (!batchMode) {
            mappedStatement.getBoundSql(parameterObject)
        } else {
            if (ParamMapUtils.isBatchParam(parameterObject)) {
                return mappedStatement.getBoundSql(ParamMapUtils.getFirstBatchParam(parameterObject as MapperMethod.ParamMap<*>))
            }
            boundSql
        }
    }

}

核心实现 TupleParameterHandler 参数处理器

与Mybatis的ParameterHandler不同,我们这里用Tuple来保存SQL的参数列表,parameterObject只是为了利用Mybatis的参数传递和TypeHandler机制,最终我们需要将parameterObject的参数保存到Tuple中。

实现类,DefaultTupleParameterHandler,

TupleParameterHandler
kotlin 复制代码
package com.luomin.mva.executor.parameter

import io.vertx.sqlclient.Tuple

/**
 * description <br></br>
 *
 * @author luomin <br></br>
 * 2025/2/5 16:33 <br></br>
 * @see org.apache.ibatis.executor.parameter.ParameterHandler
 */
interface TupleParameterHandler {
    val parameterObject: Any?

    val parameterTuple: Tuple

    val parameterTuples: List<Tuple>

    fun setParameters(ignore: Any?)

    fun setParameters(ignore: Any?, parameterObject: Any?, tuple: Tuple)

    @Deprecated("这种Batch有点问题")
    fun setBatchParameters(ignore: Any?)

}
DefaultTupleParameterHandler
kotlin 复制代码
package com.luomin.mva.executor.parameter

import com.luomin.mva.type.adapter.TypeHandlerAdapter
import com.luomin.mva.util.ParamMapUtils
import io.vertx.sqlclient.Tuple
import org.apache.ibatis.binding.MapperMethod
import org.apache.ibatis.executor.ErrorContext
import org.apache.ibatis.mapping.BoundSql
import org.apache.ibatis.mapping.MappedStatement
import org.apache.ibatis.mapping.ParameterMapping
import org.apache.ibatis.mapping.ParameterMode
import org.apache.ibatis.reflection.MetaObject
import org.apache.ibatis.session.Configuration
import org.apache.ibatis.type.TypeException
import org.apache.ibatis.type.TypeHandler
import org.apache.ibatis.type.TypeHandlerRegistry

/**
 * description <br></br>
 *
 * @author luomin <br></br>
 * 2025/2/5 16:34 <br></br>
 * @see org.apache.ibatis.scripting.defaults.DefaultParameterHandler
 */
class DefaultTupleParameterHandler(private val mappedStatement: MappedStatement, override val parameterObject: Any?, private val boundSql: BoundSql) : TupleParameterHandler {

    private val typeHandlerRegistry: TypeHandlerRegistry = mappedStatement.configuration.getTypeHandlerRegistry()

    override val parameterTuple: Tuple = Tuple.tuple()

    override val parameterTuples = mutableListOf<Tuple>()

    private val configuration: Configuration = mappedStatement.configuration

    override fun setParameters(ignore: Any?) {
        this.setParameters(ignore, parameterObject, parameterTuple)
    }

    override fun setParameters(ignore: Any?, parameterObject: Any?, tuple: Tuple) {
        ErrorContext.instance().activity("setting parameters").`object`(mappedStatement.parameterMap.id)
        val parameterMappings = boundSql.parameterMappings
        if (parameterMappings != null) {
            var metaObject: MetaObject? = null
            for (i in parameterMappings.indices) {
                val parameterMapping: ParameterMapping = parameterMappings[i]
                if (parameterMapping.mode != ParameterMode.OUT) {
                    val value: Any?
                    val propertyName = parameterMapping.property
                    if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
                        value = boundSql.getAdditionalParameter(propertyName)
                    } else if (parameterObject == null) {
                        value = null
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.javaClass)) {
                        value = parameterObject
                    } else {
                        if (metaObject == null) {
                            metaObject = configuration.newMetaObject(parameterObject)
                        }
                        value = metaObject.getValue(propertyName)
                    }
                    val typeHandler = parameterMapping.typeHandler
                    var jdbcType = parameterMapping.jdbcType
                    if (value == null && jdbcType == null) {
                        jdbcType = configuration.getJdbcTypeForNull()
                    }
                    try {
                        val typeHandlerAdapter = TypeHandlerAdapter.adaptive(typeHandler as TypeHandler<Any>)
                        typeHandlerAdapter.setParameter(tuple, value, jdbcType)
                    } catch (e: TypeException) {
                        throw TypeException("Could not set parameters for mapping: $parameterMapping. Cause: $e", e)
                    }
                }
            }
        }
    }

    @Deprecated("这种Batch有点问题")
    override fun setBatchParameters(ignore: Any?) {
        if (!ParamMapUtils.isBatchParam(parameterObject)) return this.setParameters(ignore)

        ErrorContext.instance().activity("setting parameters").`object`(mappedStatement.parameterMap.id)
        val parameterMappings = boundSql.parameterMappings
        if (parameterMappings != null) {
            val batchParams = ParamMapUtils.getBatchParams(parameterObject as MapperMethod.ParamMap<*>)
            batchParams?.forEach {
                var metaObject: MetaObject? = null
                val tuple: Tuple = Tuple.tuple()
                for (i in parameterMappings.indices) {
                    val parameterMapping: ParameterMapping = parameterMappings[i]
                    if (parameterMapping.mode != ParameterMode.OUT) {
                        val value: Any?
                        val propertyName = parameterMapping.property
                        if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
                            value = boundSql.getAdditionalParameter(propertyName)
                        } else if (it == null) {
                            value = null
                        } else if (typeHandlerRegistry.hasTypeHandler(it.javaClass)) {
                            value = it
                        } else {
                            if (metaObject == null) {
                                metaObject = configuration.newMetaObject(it)
                            }
                            value = metaObject.getValue(propertyName)
                        }
                        val typeHandler = parameterMapping.typeHandler
                        var jdbcType = parameterMapping.jdbcType
                        if (value == null && jdbcType == null) {
                            jdbcType = configuration.getJdbcTypeForNull()
                        }
                        try {
                            val typeHandlerAdapter = TypeHandlerAdapter.adaptive(typeHandler as TypeHandler<Any>)
                            typeHandlerAdapter.setParameter(tuple, value, jdbcType)
                        } catch (e: TypeException) {
                            throw TypeException("Could not set parameters for mapping: $parameterMapping. Cause: $e", e)
                        }
                    }
                }
                this.parameterTuples.add(tuple)
            }
        }
    }

}

核心实现 RowSetResultSetHandler 结果集处理器

跟Mybatis原生ResultSetHandler差不多,这里主要是底层数据集改成从RowSet获取。

RowSetResultSetHandler
kotlin 复制代码
package com.luomin.mva.executor.resultset

import io.vertx.sqlclient.Row
import io.vertx.sqlclient.RowSet

/**
 * description <br></br>
 * @see org.apache.ibatis.executor.resultset.ResultSetHandler
 *
 *
 * @author luomin <br></br>
 * 2025/2/5 18:03 <br></br>
 */
interface RowSetResultSetHandler {
    fun <E> handleResultSets(rowSet: RowSet<Row>): List<E>
}
DefaultRowSetResultSetHandler
kotlin 复制代码
package com.luomin.mva.executor.resultset

import com.luomin.mva.type.adapter.TypeHandlerAdapter
import io.vertx.sqlclient.Row
import io.vertx.sqlclient.RowIterator
import io.vertx.sqlclient.RowSet
import org.apache.ibatis.annotations.AutomapConstructor
import org.apache.ibatis.annotations.Param
import org.apache.ibatis.cache.CacheKey
import org.apache.ibatis.executor.ErrorContext
import org.apache.ibatis.executor.ExecutorException
import org.apache.ibatis.executor.loader.ResultLoaderMap
import org.apache.ibatis.executor.result.DefaultResultContext
import org.apache.ibatis.executor.result.DefaultResultHandler
import org.apache.ibatis.executor.result.ResultMapException
import org.apache.ibatis.mapping.Discriminator
import org.apache.ibatis.mapping.MappedStatement
import org.apache.ibatis.mapping.ResultMap
import org.apache.ibatis.mapping.ResultMapping
import org.apache.ibatis.reflection.MetaClass
import org.apache.ibatis.reflection.MetaObject
import org.apache.ibatis.reflection.ReflectorFactory
import org.apache.ibatis.reflection.factory.ObjectFactory
import org.apache.ibatis.session.*
import org.apache.ibatis.type.JdbcType
import org.apache.ibatis.type.TypeHandler
import org.apache.ibatis.type.TypeHandlerRegistry
import org.apache.ibatis.util.MapUtil
import java.lang.reflect.Constructor
import java.text.MessageFormat

/**
 * description <br></br>
 *
 * @author luomin <br></br>
 * 2025/2/6 15:43 <br></br>
 * @see org.apache.ibatis.executor.resultset.DefaultResultSetHandler
 */
class DefaultRowSetResultSetHandler(private val mappedStatement: MappedStatement, private val rowBounds: RowBounds?) : RowSetResultSetHandler {

    private val configuration: Configuration = mappedStatement.configuration
    private val typeHandlerRegistry: TypeHandlerRegistry = configuration.getTypeHandlerRegistry()
    private val objectFactory: ObjectFactory = configuration.getObjectFactory()
    private val reflectorFactory: ReflectorFactory = configuration.getReflectorFactory()

    // nested resultmaps
    private val nestedResultObjects: MutableMap<CacheKey?, Any?> = HashMap<CacheKey?, Any?>()

    // multiple resultsets

    // Cached Automappings
    private val autoMappingsCache: MutableMap<String?, MutableList<UnMappedColumnAutoMapping>?> = HashMap<String?, MutableList<UnMappedColumnAutoMapping>?>()
    private val constructorAutoMappingColumns = mutableMapOf<String, MutableList<String>?>()

    // temporary marking flag that indicate using constructor mapping (use field to reduce memory usage)
    private var useConstructorMappings = false


    override fun <E> handleResultSets(rowSet: RowSet<Row>): List<E> {
        ErrorContext.instance().activity("handling results").`object`(mappedStatement.id)
        val multipleResults = mutableListOf<Any>()
        val rsw = getFirstResultSet(rowSet)
        val resultMaps = mappedStatement.resultMaps
        val resultMapCount = resultMaps.size
        validateResultMapsCount(rsw, resultMapCount)
        var resultSetCount = 0

        while (rsw != null && resultMapCount > resultSetCount) {
            val resultMap = resultMaps[resultSetCount]
            handleResultSet(rsw, resultMap, multipleResults, null)
            cleanUpAfterHandlingResultSet()
            resultSetCount++
        }

        val resultSets = mappedStatement.resultSets
        if (resultSets != null) {
            // 2025/2/6 by luomin. 不实现有多个ResultSet的情况
        }

        return collapseSingleResultList(multipleResults) as List<E>
    }

    private fun handleResultSet(rsw: RowSetResultSetWrapper, resultMap: ResultMap, multipleResults: MutableList<Any>, parentMapping: ResultMapping?) {
        if (parentMapping != null) {
            handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping)
        } else {
            val defaultResultHandler = DefaultResultHandler(objectFactory)
            handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null)
            multipleResults.add(defaultResultHandler.resultList)
        }
    }

    /**
     * HANDLE ROWS FOR SIMPLE RESULTMAP
     */
    fun handleRowValues(rsw: RowSetResultSetWrapper, resultMap: ResultMap, resultHandler: ResultHandler<*>?, rowBounds: RowBounds?, parentMapping: ResultMapping?) {
        if (resultMap.hasNestedResultMaps()) {
            ensureNoRowBounds()
            // 2025/2/7 by luomin. 补考嵌套的ResultMap
            handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds!!, parentMapping)
        } else {
            handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping)
        }
    }

    private fun handleRowValuesForSimpleResultMap(rsw: RowSetResultSetWrapper, resultMap: ResultMap, resultHandler: ResultHandler<*>?, rowBounds: RowBounds?, parentMapping: ResultMapping?) {
        val resultContext = DefaultResultContext<Any>()
        val rowSet = rsw.rowSet
        val rowIterator = rowSet.iterator()
        skipRows(rowIterator, rowBounds)
        while (shouldProcessMoreRows(resultContext, rowBounds) && rowIterator.hasNext()) {
            val row = rowIterator.next()
            rsw.row = row
            val discriminatedResultMap = resolveDiscriminatedResultMap(row, resultMap, null)
            val rowValue = getRowValue(rsw, discriminatedResultMap, null)
            storeObject(resultHandler, resultContext, rowValue, parentMapping, row)
        }
    }

    /**
     * HANDLE NESTED RESULT MAPS
     */
    private fun handleRowValuesForNestedResultMap(rsw: RowSetResultSetWrapper?, resultMap: ResultMap?, resultHandler: ResultHandler<*>?, rowBounds: RowBounds, parentMapping: ResultMapping?) {
        /*final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
        RowIterator<Row> rowIterator = rsw.getRowSet().iterator();
        skipRows(rowIterator, rowBounds);
        Object rowValue = previousRowValue;
        while (shouldProcessMoreRows(resultContext, rowBounds) && rowIterator.hasNext()) {
            Row row  = rowIterator.next();
            rsw.setRow(row);
            final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(row, resultMap, null);
            final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
            Object partialObject = nestedResultObjects.get(rowKey);
            // issue #577 && #542
            if (mappedStatement.isResultOrdered()) {
                if (partialObject == null && rowValue != null) {
                    nestedResultObjects.clear();
                    storeObject(resultHandler, resultContext, rowValue, parentMapping, row);
                }
                rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
            } else {
                rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
                if (partialObject == null) {
                    storeObject(resultHandler, resultContext, rowValue, parentMapping, row);
                }
            }
        }
        if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
            storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getRow());
            previousRowValue = null;
        } else if (rowValue != null) {
            previousRowValue = rowValue;
        }*/
    }

    private fun storeObject(resultHandler: ResultHandler<*>?, resultContext: DefaultResultContext<Any>, rowValue: Any?, parentMapping: ResultMapping?, row: Row?) {
        callResultHandler(resultHandler, resultContext, rowValue)
    }

    private fun callResultHandler(resultHandler: ResultHandler<*>?, resultContext: DefaultResultContext<Any>, rowValue: Any?) {
        resultContext.nextResultObject(rowValue)
        resultHandler ?: return
        (resultHandler as ResultHandler<Any>).handleResult(resultContext)
    }

    private fun getRowValue(rsw: RowSetResultSetWrapper, resultMap: ResultMap, columnPrefix: String?): Any? {
        val lazyLoader = ResultLoaderMap()
        var rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix)
        if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.type)) {
            val metaObject = configuration.newMetaObject(rowValue)
            var foundValues = this.useConstructorMappings
            if (shouldApplyAutomaticMappings(resultMap, false)) {
                foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues
            }
            foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues
            foundValues = lazyLoader.size() > 0 || foundValues
            rowValue = if (foundValues || configuration.isReturnInstanceForEmptyRow) rowValue else null
        }
        return rowValue
    }

    private fun createResultObject(rsw: RowSetResultSetWrapper, resultMap: ResultMap, lazyLoader: ResultLoaderMap?, columnPrefix: String?): Any? {
        this.useConstructorMappings = false
        val constructorArgTypes = mutableListOf<Class<*>>()
        val constructorArgs = mutableListOf<Any?>()
        var resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix)
        if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.type)) {
            val propertyMappings = resultMap.propertyResultMappings
            for (propertyMapping in propertyMappings) {
                // issue gcode #109 && issue #149
                if (propertyMapping.nestedQueryId != null && propertyMapping.isLazy) {
                    resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs)
                    break
                }
            }
        }
        this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty() // set current mapping result
        return resultObject
    }

    private fun createResultObject(rsw: RowSetResultSetWrapper, resultMap: ResultMap, constructorArgTypes: MutableList<Class<*>>, constructorArgs: MutableList<Any?>, columnPrefix: String?): Any? {
        val resultType = resultMap.type
        val metaType = MetaClass.forClass(resultType, reflectorFactory)
        val constructorMappings: List<ResultMapping> = resultMap.constructorResultMappings
        if (hasTypeHandlerForResultObject(rsw, resultType)) {
            return createPrimitiveResultObject(rsw, resultMap, columnPrefix)
        }
        if (!constructorMappings.isEmpty()) {
            return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix)
        } else if (resultType.isInterface || metaType.hasDefaultConstructor()) {
            return objectFactory.create(resultType)
        } else if (shouldApplyAutomaticMappings(resultMap, false)) {
            return createByConstructorSignature(
                rsw, resultMap, columnPrefix, resultType, constructorArgTypes,
                constructorArgs
            )
        }
        throw ExecutorException("Do not know how to create an instance of $resultType")
    }

    private fun createPrimitiveResultObject(rsw: RowSetResultSetWrapper, resultMap: ResultMap, columnPrefix: String?): Any? {
        val resultType = resultMap.type
        val columnName: String?
        if (!resultMap.resultMappings.isEmpty()) {
            val resultMappingList: MutableList<ResultMapping> = resultMap.resultMappings
            val mapping: ResultMapping = resultMappingList[0]
            columnName = prependPrefix(mapping.column, columnPrefix)
        } else {
            columnName = rsw.columnNames[0]
        }
        val typeHandler = rsw.getTypeHandler(resultType, columnName)
        val typeHandlerAdapter = TypeHandlerAdapter.adaptive(typeHandler)
        return typeHandlerAdapter.getResult(rsw.row!!, columnName)
    }

    private fun createParameterizedResultObject(
        rsw: RowSetResultSetWrapper,
        resultType: Class<*>?,
        constructorMappings: List<ResultMapping>,
        constructorArgTypes: MutableList<Class<*>>,
        constructorArgs: MutableList<Any?>,
        columnPrefix: String?
    ): Any? {
        var foundValues = false
        for (constructorMapping in constructorMappings) {
            val parameterType = constructorMapping.javaType
            val column: String? = constructorMapping.column
            val value: Any?
            try {
                if (constructorMapping.nestedResultMapId != null) {
                    val constructorColumnPrefix = getColumnPrefix(columnPrefix, constructorMapping)
                    val resultMap = resolveDiscriminatedResultMap(rsw.row, configuration.getResultMap(constructorMapping.nestedResultMapId), constructorColumnPrefix)
                    value = getRowValue(rsw, resultMap, constructorColumnPrefix)
                } else {
                    val typeHandler = constructorMapping.typeHandler
                    val typeHandlerAdapter = TypeHandlerAdapter.adaptive(typeHandler)
                    value = typeHandlerAdapter.getResult(rsw.row!!, prependPrefix(column, columnPrefix))
                }
            } catch (e: ResultMapException) {
                throw ExecutorException("Could not process result for mapping: $constructorMapping", e)
            }
            constructorArgTypes.add(parameterType)
            constructorArgs.add(value)
            foundValues = value != null || foundValues
        }
        return if (foundValues) objectFactory.create(resultType, constructorArgTypes, constructorArgs) else null
    }

    private fun createByConstructorSignature(
        rsw: RowSetResultSetWrapper,
        resultMap: ResultMap,
        columnPrefix: String?,
        resultType: Class<*>,
        constructorArgTypes: MutableList<Class<*>>,
        constructorArgs: MutableList<Any?>
    ): Any? {
        return applyConstructorAutomapping(
            rsw,
            resultMap,
            columnPrefix,
            resultType,
            constructorArgTypes,
            constructorArgs,
            findConstructorForAutomapping(resultType, rsw) ?: throw ExecutorException("No constructor found in " + resultType.getName() + " matching " + rsw.classNames)
        )
    }

    private fun resolveDiscriminatedResultMap(row: Row?, resultMap: ResultMap, columnPrefix: String?): ResultMap {
        var resultMap = resultMap
        val pastDiscriminators = mutableSetOf<String>()
        var discriminator = resultMap.discriminator
        while (discriminator != null) {
            val value = getDiscriminatorValue(row, discriminator, columnPrefix)
            val discriminatedMapId = discriminator.getMapIdFor(value.toString())
            if (!configuration.hasResultMap(discriminatedMapId)) {
                break
            }
            resultMap = configuration.getResultMap(discriminatedMapId)
            val lastDiscriminator: Discriminator? = discriminator
            discriminator = resultMap.discriminator
            if (discriminator === lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {
                break
            }
        }
        return resultMap
    }

    private fun getDiscriminatorValue(row: Row?, discriminator: Discriminator, columnPrefix: String?): Any? {
        val resultMapping = discriminator.resultMapping
        val typeHandler = resultMapping.typeHandler
        val typeHandlerAdapter = TypeHandlerAdapter.adaptive(typeHandler)
        return typeHandlerAdapter.getResult(row!!, prependPrefix(resultMapping.column, columnPrefix))
    }

    private fun getFirstResultSet(rowSet: RowSet<Row>?): RowSetResultSetWrapper? {
        return if (rowSet != null) RowSetResultSetWrapper(rowSet, configuration) else null
    }

    private fun shouldProcessMoreRows(context: ResultContext<*>, rowBounds: RowBounds?): Boolean {
        return !context.isStopped && context.resultCount < (rowBounds?.limit ?: Int.MAX_VALUE)
    }

    private fun skipRows(rowIterator: RowIterator<Row>, rowBounds: RowBounds?) {
        rowBounds ?: return
        for (i in 0..<rowBounds.offset) {
            if (!rowIterator.hasNext()) {
                break
            }
            rowIterator.next()
        }
    }

    private fun collapseSingleResultList(multipleResults: List<Any>): List<Any> {
        return if (multipleResults.size == 1) multipleResults[0] as List<Any> else multipleResults
    }

    private fun cleanUpAfterHandlingResultSet() {
        nestedResultObjects.clear()
    }

    private fun prependPrefix(columnName: String?, prefix: String?): String {
        if (columnName == null || columnName.isEmpty() || prefix == null || prefix.isEmpty()) {
            return columnName!!
        }
        return prefix + columnName
    }

    private fun getColumnPrefix(parentPrefix: String?, resultMapping: ResultMapping): String? {
        val columnPrefixBuilder = StringBuilder()
        if (parentPrefix != null) {
            columnPrefixBuilder.append(parentPrefix)
        }
        if (resultMapping.columnPrefix != null) {
            columnPrefixBuilder.append(resultMapping.columnPrefix)
        }
        return if (columnPrefixBuilder.isEmpty()) null else columnPrefixBuilder.toString().uppercase()
    }

    private fun hasTypeHandlerForResultObject(rsw: RowSetResultSetWrapper, resultType: Class<*>?): Boolean {
        if (rsw.columnNames.size == 1) {
            return typeHandlerRegistry.hasTypeHandler(resultType, rsw.getJdbcType(rsw.columnNames[0]))
        }
        return typeHandlerRegistry.hasTypeHandler(resultType)
    }

    private fun shouldApplyAutomaticMappings(resultMap: ResultMap, isNested: Boolean): Boolean {
        if (resultMap.autoMapping != null) {
            return resultMap.autoMapping
        }
        return if (isNested) {
            AutoMappingBehavior.FULL == configuration.getAutoMappingBehavior()
        } else {
            AutoMappingBehavior.NONE != configuration.getAutoMappingBehavior()
        }
    }

    private fun applyConstructorAutomapping(
        rsw: RowSetResultSetWrapper,
        resultMap: ResultMap,
        columnPrefix: String?,
        resultType: Class<*>?,
        constructorArgTypes: MutableList<Class<*>>,
        constructorArgs: MutableList<Any?>,
        constructor: Constructor<*>
    ): Any? {
        var foundValues = false
        foundValues = if (configuration.isArgNameBasedConstructorAutoMapping) {
            applyArgNameBasedConstructorAutoMapping(rsw, resultMap, columnPrefix, constructorArgTypes, constructorArgs, constructor, foundValues)
        } else {
            applyColumnOrderBasedConstructorAutomapping(rsw, constructorArgTypes, constructorArgs, constructor, foundValues)
        }
        return if (foundValues || configuration.isReturnInstanceForEmptyRow) objectFactory.create(resultType, constructorArgTypes, constructorArgs) else null
    }

    private fun applyArgNameBasedConstructorAutoMapping(
        rsw: RowSetResultSetWrapper, resultMap: ResultMap,
        columnPrefix: String?, constructorArgTypes: MutableList<Class<*>>, constructorArgs: MutableList<Any?>, constructor: Constructor<*>,
        foundValues: Boolean
    ): Boolean {
        var foundValues = foundValues
        var missingArgs: MutableList<String>? = null
        val params = constructor.parameters
        for (param in params) {
            var columnNotFound = true
            val paramAnno = param.getAnnotation(Param::class.java)
            val paramName = paramAnno?.value ?: param.name
            for (columnName in rsw.columnNames) {
                if (columnMatchesParam(columnName, paramName, columnPrefix)) {
                    val paramType = param.getType()
                    val typeHandler = rsw.getTypeHandler(paramType, columnName)
                    val value = TypeHandlerAdapter.adaptive(typeHandler).getResult(rsw.row!!, columnName)
                    constructorArgTypes.add(paramType)
                    constructorArgs.add(value)
                    val mapKey = resultMap.id + ":" + columnPrefix
                    if (!autoMappingsCache.containsKey(mapKey)) {
                        MapUtil.computeIfAbsent<String, MutableList<String>?>(constructorAutoMappingColumns, mapKey) { mutableListOf() }.add(columnName)
                    }
                    columnNotFound = false
                    foundValues = value != null || foundValues
                }
            }
            if (columnNotFound) {
                if (missingArgs == null) {
                    missingArgs = mutableListOf()
                }
                missingArgs.add(paramName)
            }
        }
        if (foundValues && constructorArgs.size < params.size) {
            throw ExecutorException(
                MessageFormat.format(
                    ("Constructor auto-mapping of ''{1}'' failed " + "because ''{0}'' were not found in the result set; Available columns are ''{2}'' and mapUnderscoreToCamelCase is ''{3}''."),
                    missingArgs, constructor, rsw.columnNames, configuration.isMapUnderscoreToCamelCase
                )
            )
        }
        return foundValues
    }

    private fun applyColumnOrderBasedConstructorAutomapping(
        rsw: RowSetResultSetWrapper,
        constructorArgTypes: MutableList<Class<*>>,
        constructorArgs: MutableList<Any?>,
        constructor: Constructor<*>,
        foundValues: Boolean
    ): Boolean {
        var foundValues = foundValues
        val parameterTypes = constructor.parameterTypes
        for (i in parameterTypes.indices) {
            val parameterType = parameterTypes[i]
            val columnName = rsw.columnNames[i]
            val typeHandler = rsw.getTypeHandler(parameterType, columnName)
            val value = TypeHandlerAdapter.adaptive(typeHandler).getResult(rsw.row!!, columnName)
            constructorArgTypes.add(parameterType)
            constructorArgs.add(value)
            foundValues = value != null || foundValues
        }
        return foundValues
    }

    private fun columnMatchesParam(columnName: String, paramName: String, columnPrefix: String?): Boolean {
        var columnName = columnName
        if (columnPrefix != null) {
            if (!columnName.uppercase().startsWith(columnPrefix)) {
                return false
            }
            columnName = columnName.substring(columnPrefix.length)
        }
        return paramName.equals(if (configuration.isMapUnderscoreToCamelCase) columnName.replace("_", "") else columnName, ignoreCase = true)
    }

    private fun applyAutomaticMappings(rsw: RowSetResultSetWrapper, resultMap: ResultMap, metaObject: MetaObject, columnPrefix: String?): Boolean {
        val autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix)
        var foundValues = false
        if (!autoMapping.isEmpty()) {
            for (mapping in autoMapping) {
                val typeHandler = mapping.typeHandler
                val value = TypeHandlerAdapter.adaptive(typeHandler as TypeHandler<Any>).getResult(rsw.row!!, mapping.column!!)
                if (value != null) {
                    foundValues = true
                }
                if (value != null || configuration.isCallSettersOnNulls && !mapping.primitive) {
                    // gcode issue #377, call setter on nulls (value is not 'found')
                    metaObject.setValue(mapping.property, value)
                }
            }
        }
        return foundValues
    }

    private fun findConstructorForAutomapping(resultType: Class<*>, rsw: RowSetResultSetWrapper): Constructor<*>? {
        val constructors = resultType.getDeclaredConstructors()
        if (constructors.size == 1) {
            return constructors[0]
        }
        val annotated = constructors.filter { it.isAnnotationPresent(AutomapConstructor::class.java) }
        if (annotated.size > 1) throw ExecutorException("@AutomapConstructor should be used in only one constructor.")
        if (annotated.isNotEmpty()) return annotated[0]


        if (configuration.isArgNameBasedConstructorAutoMapping) {
            // Finding-best-match type implementation is possible,
            // but using @AutomapConstructor seems sufficient.
            throw ExecutorException(
                MessageFormat.format(
                    "'argNameBasedConstructorAutoMapping' is enabled and the class ''{0}'' has multiple constructors, so @AutomapConstructor must be added to one of the constructors.",
                    resultType.getName()
                )
            )
        } else {
            return annotated.first { this.findUsableConstructorByArgTypes(it, rsw.jdbcTypes) }
        }
    }

    private fun applyPropertyMappings(rsw: RowSetResultSetWrapper, resultMap: ResultMap, metaObject: MetaObject, lazyLoader: ResultLoaderMap?, columnPrefix: String?): Boolean {
        val mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix)
        var foundValues = false
        val propertyMappings = resultMap.propertyResultMappings
        for (propertyMapping in propertyMappings) {
            var column: String? = prependPrefix(propertyMapping.column, columnPrefix)
            if (propertyMapping.nestedResultMapId != null) {
                // the user added a column attribute to a nested result map, ignore it
                column = null
            }
            if (propertyMapping.isCompositeResult || column != null && mappedColumnNames.contains(column.uppercase()) || propertyMapping.resultSet != null) {
                val value = getPropertyMappingValue(rsw.row, metaObject, propertyMapping, lazyLoader, columnPrefix)
                // issue #541 make property optional
                val property: String? = propertyMapping.property
                if (property == null) {
                    continue
                }
                if (value != null) {
                    foundValues = true
                }
                if (value != null || configuration.isCallSettersOnNulls && !metaObject.getSetterType(property).isPrimitive) {
                    // gcode issue #377, call setter on nulls (value is not 'found')
                    metaObject.setValue(property, value)
                }
            }
        }
        return foundValues
    }

    private fun findUsableConstructorByArgTypes(constructor: Constructor<*>, jdbcTypes: MutableList<JdbcType>): Boolean {
        val parameterTypes = constructor.parameterTypes
        if (parameterTypes.size != jdbcTypes.size) {
            return false
        }
        for (i in parameterTypes.indices) {
            if (!typeHandlerRegistry.hasTypeHandler(parameterTypes[i], jdbcTypes[i])) {
                return false
            }
        }
        return true
    }

    private fun getPropertyMappingValue(row: Row?, metaResultObject: MetaObject?, propertyMapping: ResultMapping, lazyLoader: ResultLoaderMap?, columnPrefix: String?): Any? {
        val typeHandler = propertyMapping.typeHandler
        val column = prependPrefix(propertyMapping.column, columnPrefix)
        return TypeHandlerAdapter.adaptive(typeHandler).getResult(row!!, column)
    }

    private fun ensureNoRowBounds() {
        if (configuration.isSafeRowBoundsEnabled && rowBounds != null && (rowBounds.limit < RowBounds.NO_ROW_LIMIT || rowBounds.offset > RowBounds.NO_ROW_OFFSET)) {
            throw ExecutorException("Mapped Statements with nested result mappings cannot be safely constrained by RowBounds. Use safeRowBoundsEnabled=false setting to bypass this check.")
        }
    }

    private fun validateResultMapsCount(rsw: RowSetResultSetWrapper?, resultMapCount: Int) {
        if (rsw != null && resultMapCount < 1) {
            throw ExecutorException(("A query was run and no Result Maps were found for the Mapped Statement '" + mappedStatement.id + "'. 'resultType' or 'resultMap' must be specified when there is no corresponding method."))
        }
    }

    private fun createAutomaticMappings(rsw: RowSetResultSetWrapper, resultMap: ResultMap, metaObject: MetaObject, columnPrefix: String?): List<UnMappedColumnAutoMapping> {
        val mapKey = resultMap.id + ":" + columnPrefix
        var autoMapping = autoMappingsCache[mapKey]
        if (autoMapping == null) {
            autoMapping = ArrayList()
            val unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix)
            // Remove the entry to release the memory
            val mappedInConstructorAutoMapping = constructorAutoMappingColumns.remove(mapKey)
            if (mappedInConstructorAutoMapping != null) {
                unmappedColumnNames.removeAll(mappedInConstructorAutoMapping)
            }
            for (columnName in unmappedColumnNames) {
                var propertyName = columnName
                if (columnPrefix != null && !columnPrefix.isEmpty()) {
                    // When columnPrefix is specified,
                    // ignore columns without the prefix.
                    if (!columnName.uppercase().startsWith(columnPrefix)) {
                        continue
                    }
                    propertyName = columnName.substring(columnPrefix.length)
                }
                val property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase)
                if (property != null && metaObject.hasSetter(property)) {
                    if (resultMap.mappedProperties.contains(property)) {
                        continue
                    }
                    val propertyType = metaObject.getSetterType(property)
                    if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
                        val typeHandler = rsw.getTypeHandler(propertyType, columnName)
                        autoMapping.add(UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive))
                    } else {
                        configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName, property, propertyType)
                    }
                } else {
                    configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName, property ?: propertyName, null)
                }
            }
            autoMappingsCache.put(mapKey, autoMapping)
        }
        return autoMapping
    }

    private class UnMappedColumnAutoMapping(val column: String?, val property: String?, val typeHandler: TypeHandler<*>?, val primitive: Boolean)
}

结束

至此,我们现在已经拥有一个能返回Vertx Future,底层由 vertx-jdbc-client 实现的响应式ORM框架 Mybatis。

后续还将整合进Spring,并支持Spring事务管理。

相关推荐
java1234_小锋15 小时前
Java高频面试题:如何编写一个MyBatis插件?
java·开发语言·mybatis
fe7tQnVan15 小时前
MyBatis-动态sql与高级映射
数据库·sql·mybatis
qq12_81151751517 小时前
Java Web 影城会员管理系统系统源码-SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0【含文档】
java·前端·mybatis
Java成神之路-19 小时前
MyBatis 开发模式演进:原生、Spring 与 Spring Boot 整合实战(MyBatis系列2)
spring boot·spring·mybatis
taWSw5OjU21 小时前
MyBatis-plus进阶之映射与条件构造器
数据库·oracle·mybatis
身如柳絮随风扬1 天前
Mybatis分页实现原理与PageHelper插件深度解析
mybatis
希望永不加班1 天前
SpringBoot 缓存注解:@Cacheable/@CacheEvict 使用
java·spring boot·spring·缓存·mybatis
oYD3FlT321 天前
MyBatis-缓存与注解式开发
java·缓存·mybatis
R***z1012 天前
Spring Boot 整合 MyBatis 与 PostgreSQL 实战指南
spring boot·postgresql·mybatis