MyBatis-Plus Extension module providing advanced features including service layers, Kotlin extensions, SQL parsers, caching mechanisms, and various interceptors for enhanced ORM functionality
—
MyBatis-Plus Extension provides comprehensive Kotlin support through specialized wrapper classes that leverage Kotlin's language features for type-safe database operations. These extensions offer property-based query building using Kotlin's reflection capabilities and provide a more idiomatic Kotlin development experience.
Type-safe query wrapper for Kotlin with property-based field references.
class KtQueryWrapper<T>(entity: T? = null) : AbstractKtWrapper<T, KtQueryWrapper<T>>() {
constructor(entityClass: Class<T>) : this(null)
// Select operations
fun select(vararg columns: KProperty<*>): KtQueryWrapper<T>
fun getSqlSelect(): String
// Factory methods
fun instance(): KtQueryWrapper<T>
fun clear(): KtQueryWrapper<T>
// Inherited condition methods from AbstractKtWrapper
// eq, ne, gt, ge, lt, le, like, notLike, in, notIn, etc.
}Type-safe update wrapper for Kotlin operations.
class KtUpdateWrapper<T>(entity: T? = null) : AbstractKtWrapper<T, KtUpdateWrapper<T>>() {
constructor(entityClass: Class<T>) : this(null)
// Set operations using Kotlin properties
fun set(column: KProperty<*>, value: Any?): KtUpdateWrapper<T>
fun setSql(sql: String): KtUpdateWrapper<T>
// Factory methods
fun instance(): KtUpdateWrapper<T>
fun clear(): KtUpdateWrapper<T>
}Kotlin chain wrapper for fluent query operations.
class KtQueryChainWrapper<T> : AbstractChainWrapper<T, KProperty<*>, KtQueryChainWrapper<T>, KtQueryWrapper<T>>(),
ChainQuery<T> {
constructor(baseMapper: BaseMapper<T>)
constructor(entityClass: Class<T>)
// Terminal operations
override fun list(): List<T>
override fun one(): T
override fun oneOpt(): Optional<T>
override fun count(): Long
override fun exists(): Boolean
override fun <E : IPage<T>> page(page: E): E
// Select operations
fun select(vararg columns: KProperty<*>): KtQueryChainWrapper<T>
}Kotlin chain wrapper for fluent update operations.
class KtUpdateChainWrapper<T> : AbstractChainWrapper<T, KProperty<*>, KtUpdateChainWrapper<T>, KtUpdateWrapper<T>>(),
ChainUpdate<T> {
constructor(baseMapper: BaseMapper<T>)
constructor(entityClass: Class<T>)
// Terminal operations
override fun update(): Boolean
override fun update(entity: T): Boolean
override fun remove(): Boolean
// Set operations
fun set(column: KProperty<*>, value: Any?): KtUpdateChainWrapper<T>
fun setSql(sql: String): KtUpdateChainWrapper<T>
}Base class for Kotlin wrappers providing type-safe condition methods.
abstract class AbstractKtWrapper<T, Children : AbstractKtWrapper<T, Children>> {
// Comparison conditions
fun eq(column: KProperty<*>, value: Any?): Children
fun ne(column: KProperty<*>, value: Any?): Children
fun gt(column: KProperty<*>, value: Any?): Children
fun ge(column: KProperty<*>, value: Any?): Children
fun lt(column: KProperty<*>, value: Any?): Children
fun le(column: KProperty<*>, value: Any?): Children
// String conditions
fun like(column: KProperty<*>, value: Any?): Children
fun notLike(column: KProperty<*>, value: Any?): Children
fun likeLeft(column: KProperty<*>, value: Any?): Children
fun likeRight(column: KProperty<*>, value: Any?): Children
// Null conditions
fun isNull(column: KProperty<*>): Children
fun isNotNull(column: KProperty<*>): Children
// Collection conditions
fun `in`(column: KProperty<*>, values: Collection<*>): Children
fun notIn(column: KProperty<*>, values: Collection<*>): Children
fun `in`(column: KProperty<*>, vararg values: Any?): Children
fun notIn(column: KProperty<*>, vararg values: Any?): Children
// Range conditions
fun between(column: KProperty<*>, val1: Any?, val2: Any?): Children
fun notBetween(column: KProperty<*>, val1: Any?, val2: Any?): Children
// Logical operators
fun and(): Children
fun or(): Children
fun and(consumer: (Children) -> Unit): Children
fun or(consumer: (Children) -> Unit): Children
// Ordering (for query wrappers)
fun orderByAsc(column: KProperty<*>): Children
fun orderByDesc(column: KProperty<*>): Children
fun orderByAsc(vararg columns: KProperty<*>): Children
fun orderByDesc(vararg columns: KProperty<*>): Children
// Grouping and Having
fun groupBy(column: KProperty<*>): Children
fun groupBy(vararg columns: KProperty<*>): Children
fun having(sqlHaving: String, vararg params: Any?): Children
// Utility methods
abstract fun instance(): Children
abstract fun clear(): Children
}@TableName("user")
data class User(
@TableId(type = IdType.AUTO)
var id: Long? = null,
var name: String? = null,
var email: String? = null,
var age: Int? = null,
var active: Boolean? = null,
var department: String? = null,
var salary: BigDecimal? = null,
var createdTime: LocalDateTime? = null,
var updatedTime: LocalDateTime? = null
) : Model<User>() {
override fun pkVal(): Serializable? = id
}// Using KtQueryWrapper directly
val wrapper = KtQueryWrapper<User>()
.eq(User::active, true)
.gt(User::age, 18)
.like(User::name, "John")
.orderByDesc(User::createdTime)
val users = userService.list(wrapper)
// Using entity instance for initialization
val user = User()
val wrapper2 = KtQueryWrapper(user)
.ne(User::id, user.id)
.eq(User::department, "IT")
// Type-safe property references
val activeUsers = userService.list(
KtQueryWrapper<User>()
.eq(User::active, true)
.isNotNull(User::email)
.orderByAsc(User::name)
)@Service
class UserService : ServiceImpl<UserMapper, User>() {
// Kotlin-specific query methods
fun findActiveUsersByDepartment(department: String): List<User> {
return this.ktQuery()
.eq(User::active, true)
.eq(User::department, department)
.orderByAsc(User::name)
.list()
}
fun findUsersByAgeRange(minAge: Int, maxAge: Int): List<User> {
return this.list(
KtQueryWrapper<User>()
.between(User::age, minAge, maxAge)
.eq(User::active, true)
)
}
fun updateUserSalary(userId: Long, newSalary: BigDecimal): Boolean {
return this.ktUpdate()
.set(User::salary, newSalary)
.set(User::updatedTime, LocalDateTime.now())
.eq(User::id, userId)
.update()
}
fun deactivateInactiveUsers(days: Int): Boolean {
val cutoffDate = LocalDateTime.now().minusDays(days.toLong())
return this.ktUpdate()
.set(User::active, false)
.set(User::updatedTime, LocalDateTime.now())
.lt(User::createdTime, cutoffDate)
.update()
}
}// Query chains with Kotlin extensions
val activeUsers = userService.ktQuery()
.eq(User::active, true)
.ge(User::age, 21)
.isNotNull(User::email)
.orderByDesc(User::createdTime)
.list()
// Single result query
val user = userService.ktQuery()
.eq(User::email, "john@example.com")
.eq(User::active, true)
.one()
// Optional result
val optionalUser = userService.ktQuery()
.eq(User::name, "John Doe")
.oneOpt()
if (optionalUser.isPresent) {
val user = optionalUser.get()
println("Found user: ${user.name}")
}
// Count operations
val activeCount = userService.ktQuery()
.eq(User::active, true)
.count()
// Existence check
val hasAdmins = userService.ktQuery()
.eq(User::department, "ADMIN")
.eq(User::active, true)
.exists()// Update with conditions
val updated = userService.ktUpdate()
.set(User::department, "Engineering")
.set(User::updatedTime, LocalDateTime.now())
.eq(User::department, "IT")
.update()
// Complex update conditions
val updated = userService.ktUpdate()
.set(User::active, false)
.set(User::updatedTime, LocalDateTime.now())
.and { wrapper ->
wrapper.lt(User::age, 18)
.or()
.gt(User::age, 65)
}
.update()
// Remove operations
val removed = userService.ktUpdate()
.eq(User::active, false)
.isNull(User::email)
.remove()// Nested conditions with lambdas
val users = userService.ktQuery()
.eq(User::active, true)
.and { wrapper ->
wrapper.eq(User::department, "IT")
.or()
.eq(User::department, "Engineering")
}
.or { wrapper ->
wrapper.ge(User::salary, BigDecimal("100000"))
.eq(User::active, true)
}
.orderByDesc(User::salary)
.list()
// Range and collection conditions
val users = userService.list(
KtQueryWrapper<User>()
.`in`(User::department, listOf("IT", "HR", "Finance"))
.between(User::age, 25, 45)
.notIn(User::id, listOf(1L, 2L, 3L))
.isNotNull(User::email)
)
// String matching conditions
val users = userService.list(
KtQueryWrapper<User>()
.like(User::name, "John")
.likeRight(User::email, "@company.com")
.notLike(User::department, "temp")
)// Select specific columns using property references
val wrapper = KtQueryWrapper<User>()
.select(User::id, User::name, User::email)
.eq(User::active, true)
.orderByAsc(User::name)
val users = userService.list(wrapper)
// Chain wrapper with select
val users = userService.ktQuery()
.select(User::name, User::department, User::salary)
.eq(User::active, true)
.gt(User::salary, BigDecimal("50000"))
.list()// Paginated query with Kotlin wrapper
val page = Page<User>(1, 10)
val result = userService.ktQuery()
.eq(User::active, true)
.ge(User::age, 18)
.orderByDesc(User::createdTime)
.page(page)
val users = result.records
val total = result.total
// Pagination with complex conditions
val searchPage = userService.page(
Page<User>(1, 20),
KtQueryWrapper<User>()
.and { wrapper ->
wrapper.like(User::name, "John")
.or()
.like(User::email, "john")
}
.eq(User::active, true)
.orderByDesc(User::updatedTime)
)// Using Db utility with Kotlin wrappers
val users = Db.ktQuery(User::class.java)
.eq(User::active, true)
.list()
val updated = Db.ktUpdate(User::class.java)
.set(User::updatedTime, LocalDateTime.now())
.eq(User::id, 1L)
.update()
// ChainWrappers with Kotlin
val users = ChainWrappers.ktQueryChain(User::class.java)
.eq(User::department, "Engineering")
.orderByAsc(User::name)
.list()
val updated = ChainWrappers.ktUpdateChain(userMapper)
.set(User::active, false)
.lt(User::createdTime, LocalDateTime.now().minusYears(1))
.update()// Extension functions for User entity
fun User.isAdult(): Boolean = this.age?.let { it >= 18 } ?: false
fun User.updateActivity(active: Boolean): Boolean {
return Db.ktUpdate(User::class.java)
.set(User::active, active)
.set(User::updatedTime, LocalDateTime.now())
.eq(User::id, this.id)
.update()
}
fun User.findSimilarUsers(): List<User> {
return Db.ktQuery(User::class.java)
.eq(User::department, this.department)
.eq(User::active, true)
.ne(User::id, this.id)
.list()
}
// Usage of extension functions
val user = userService.getById(1L)
if (user.isAdult()) {
user.updateActivity(true)
val similarUsers = user.findSimilarUsers()
}// Leveraging data class features
data class UserSearchCriteria(
val name: String? = null,
val department: String? = null,
val minAge: Int? = null,
val maxAge: Int? = null,
val active: Boolean? = null
)
class UserService : ServiceImpl<UserMapper, User>() {
fun searchUsers(criteria: UserSearchCriteria): List<User> {
val wrapper = KtQueryWrapper<User>()
criteria.name?.let { wrapper.like(User::name, it) }
criteria.department?.let { wrapper.eq(User::department, it) }
criteria.minAge?.let { wrapper.ge(User::age, it) }
criteria.maxAge?.let { wrapper.le(User::age, it) }
criteria.active?.let { wrapper.eq(User::active, it) }
return this.list(wrapper.orderByDesc(User::createdTime))
}
fun updateUserFields(id: Long, updates: UserUpdateData): Boolean {
val wrapper = KtUpdateWrapper<User>()
updates.name?.let { wrapper.set(User::name, it) }
updates.email?.let { wrapper.set(User::email, it) }
updates.department?.let { wrapper.set(User::department, it) }
updates.salary?.let { wrapper.set(User::salary, it) }
wrapper.set(User::updatedTime, LocalDateTime.now())
.eq(User::id, id)
return this.update(wrapper)
}
}
data class UserUpdateData(
val name: String? = null,
val email: String? = null,
val department: String? = null,
val salary: BigDecimal? = null
)@Repository
class UserRepository {
fun findActiveUsersByDepartments(departments: List<String>): List<User> {
return Db.ktQuery(User::class.java)
.eq(User::active, true)
.`in`(User::department, departments)
.orderByAsc(User::name)
.list()
}
fun findHighEarners(threshold: BigDecimal): List<User> {
return Db.list(
User::class.java,
KtQueryWrapper<User>()
.ge(User::salary, threshold)
.eq(User::active, true)
.orderByDesc(User::salary)
)
}
fun updateSalariesByDepartment(department: String, multiplier: Double): Boolean {
return Db.ktUpdate(User::class.java)
.setSql("salary = salary * $multiplier")
.set(User::updatedTime, LocalDateTime.now())
.eq(User::department, department)
.eq(User::active, true)
.update()
}
fun deactivateUsersOlderThan(age: Int): Boolean {
return Db.ktUpdate(User::class.java)
.set(User::active, false)
.set(User::updatedTime, LocalDateTime.now())
.gt(User::age, age)
.update()
}
fun countUsersByDepartment(): Map<String, Long> {
val users = Db.list(
User::class.java,
KtQueryWrapper<User>()
.select(User::department)
.eq(User::active, true)
)
return users.groupBy { it.department ?: "Unknown" }
.mapValues { it.value.size.toLong() }
}
}class KotlinUserService : ServiceImpl<UserMapper, User>() {
fun safeGetUser(id: Long): User? {
return try {
this.ktQuery()
.eq(User::id, id)
.eq(User::active, true)
.one()
} catch (e: Exception) {
println("Error fetching user $id: ${e.message}")
null
}
}
fun findUserOrDefault(email: String, defaultUser: User): User {
return try {
this.ktQuery()
.eq(User::email, email)
.eq(User::active, true)
.oneOpt()
.orElse(defaultUser)
} catch (e: Exception) {
println("Error finding user by email $email: ${e.message}")
defaultUser
}
}
fun batchUpdateWithValidation(updates: List<Pair<Long, String>>): Result<Int> {
return try {
var successCount = 0
updates.forEach { (id, newName) ->
val updated = this.ktUpdate()
.set(User::name, newName)
.set(User::updatedTime, LocalDateTime.now())
.eq(User::id, id)
.eq(User::active, true)
.update()
if (updated) successCount++
}
Result.success(successCount)
} catch (e: Exception) {
Result.failure(e)
}
}
}// Kotlin property references are cached automatically
// But you can store them for repeated use
object UserProperties {
val ID = User::id
val NAME = User::name
val EMAIL = User::email
val ACTIVE = User::active
val CREATED_TIME = User::createdTime
}
// Usage
val users = Db.ktQuery(User::class.java)
.eq(UserProperties.ACTIVE, true)
.orderByDesc(UserProperties.CREATED_TIME)
.list()// Compile-time safety - these won't compile if properties don't exist
val users = userService.ktQuery()
.eq(User::active, true) // ✓ Correct
.eq(User::name, "John") // ✓ Correct
// .eq(User::invalidField, "x") // ✗ Compilation error
.list()// Kotlin's null safety works with the wrappers
fun findUsersByOptionalCriteria(
name: String?,
department: String?,
minAge: Int?
): List<User> {
val wrapper = KtQueryWrapper<User>()
.eq(User::active, true)
// Safe navigation and let
name?.let { wrapper.like(User::name, it) }
department?.let { wrapper.eq(User::department, it) }
minAge?.let { wrapper.ge(User::age, it) }
return userService.list(wrapper)
}The Kotlin extensions provide a more idiomatic and type-safe way to work with MyBatis-Plus in Kotlin applications, leveraging Kotlin's language features for better developer experience and compile-time safety.
Install with Tessl CLI
npx tessl i tessl/maven-com-baomidou--mybatis-plus-extension