Vertx构建异步响应式reactive mybatis,mybatis-vertx-adaptor
-
- 背景
- 介绍
- 快速开始
- 工程结构
- 框架实现
-
- SqlSession入口
- 响应式Mybatis事务实现
- [核心实现 VertxMapper 代理](#核心实现 VertxMapper 代理)
- [核心实现 VertxExecutor 执行器](#核心实现 VertxExecutor 执行器)
- [核心实现 VertxStatementHandler 语句处理器](#核心实现 VertxStatementHandler 语句处理器)
- [核心实现 TupleParameterHandler 参数处理器](#核心实现 TupleParameterHandler 参数处理器)
- [核心实现 RowSetResultSetHandler 结果集处理器](#核心实现 RowSetResultSetHandler 结果集处理器)
- 结束
背景
基于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实现类。
- 构建Configuration对象
- 构建Vertx对象
- 构建VertxSqlSessionFactory对象
- 构建VertxSqlSession对象
- 使用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事务管理。