diff --git a/docs/extensions.md b/docs/extensions.md index f4594cc1c894528082adb7f2bb6063418694e30f..6d7cc617de794cdc90e381e421cab897e13246e9 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -1,13 +1,17 @@ # 简单查询与扩展 -## 快速获取mapper与tableInfo +> Tips: 阅读本文档时请已核心库使用文档为主,此文档为辅,扩展模块只是基于核心库的提供了符合Kotlin的便捷函数, +> 并不是另立门户,二者可以混用。 + +## 获取mapper与tableInfo - **mapper** - - 通过mapper接口类型作为泛型调用 mapper() 方法直接获取 + - 通过mapper接口类型作为泛型调用 `mapper()` 方法直接获取 + > 注意:范型M为Mapper接口类型,不是实体类型 ````kotlin val accountMapper: AccountMapper = mapper() ```` - - 通过实体型 KClass 的 baseMapper 属性直接获取 + - 通过实体型 `KClass` 的 `baseMapper` 属性直接获取 > 注意:此方式获得的实例类型是 BaseMapper ,并不是 AccountMapper 所以无法使用 AccountMapper 接口定义的方法 ````kotlin @@ -19,17 +23,19 @@ val accountTableInfo: TableInfo = Account::class.tableInfo ```` -## 快速查询(无需注册Mapper接口) +## 查询 -1. `all<实体类>()` : 查泛型对应的表的所有数据 +1. `all(): List` : 查泛型对应的表的所有数据 (无需注册Mapper接口) ```kotlin val accounts: List = all() // 或者 Account::class.all (需要注册Mapper接口) ``` -2. `filter<实体类>(vararg KProperty<*>, () -> QueryCondition)`: 按条件查泛型对应的表的数据 - > 默认查所有列,可通过 vararg KProperty<*> 指定要查的列 +2. `filter(vararg KProperty<*>, () -> QueryCondition): List`: 按条件查泛型对应的表的数据 (无需注册Mapper接口) + * 默认查所有列,可通过 `vararg KProperty<*>` 指定要查的列 + + > 除此之外还有filterOne(只查一个),filterColumn(第一个入参改为Column),filterOneColumn(前两者结合) ```kotlin // a and b and (c or d) @@ -40,16 +46,121 @@ } ``` -3. `query<实体类>(QueryScope.() -> Unit)`: 较复杂查泛型对应的表的数据 (如分组,分页,排序等) - > 可以使用queryOne快速查询一个实体对象 +3. `query(QueryScope.() -> Unit): List`: 较复杂查泛型对应的表的数据 (如分组,分页,排序等)(无需注册Mapper接口) + > 除此之外还有queryOne(只查一个), + > queryRows(强制走默认RowMapper返回值为Row), + > queryRow(前两者结合) ```kotlin val accounts: List = query { select(Account::id, Account::userName) where { - and(Account::age `in` (17..19)) - and(Account::birthday between (start to end)) + Account::age `in` (17..19) and (Account::birthday between (start to end)) }orderBy -Account::id limit(2) } + ``` +4. `paginate( + pageNumber: Number, + pageSize: Number, + totalRow: Number? = null, + init: QueryScope.() -> Unit + ): Page` : 分页查询 (无需注册Mapper接口) + > 除此之外还有paginateWith(简单按条件分页), + > paginateRows(返回值为Row) + + ```kotlin + val accounts: List = paginate(1, 10) { + select(Account::id, Account::userName) + orderBy(-Account::id) + } + ``` +5. 使用原生的baseMapper的扩展方法查询: + ```kotlin + Account::class.baseMapper.query { + select(Account::id, Account::userName) + whereWith { Account::age ge 18 } + } + ``` + +## 更新 + +1. `update(scope: UpdateScope.() -> Unit): Int`: 更新单表数据 (无需注册Mapper接口) + ```kotlin + update { + Account::id set 5 + whereWith { Account::id eq 1 and (Account::userName eq "张三") } + } + ``` + 其中set方法后可以接QueryWrapper,QueryScope类型的参数用于设置子查询 + ```kotlin + Account::age set queryScope(Account::age.column){ + from(Account::class) + and(Account::age `in` (19..20)) + limit(1) + } + ``` + 或者使用更便捷地的setRow方法: + ```kotlin + Account::age.setRow(Account::age){ + from(Account::class) + and(Account::age `in` (19..20)) + } + ``` + > setRow方法会自动select单列并limit 1,所以不需要再使用limit方法 + + 如果有需求可以写成以下方式,此时会执行两次sql + ```kotlin + Account::age set filterOne(Account::age){ + Account::age `in` (19..20) + }?.age + ``` + > 注意: + > * 更新的实体类必须是open class,例如data class会报错,因为核心库中的`UpdateWrapper`使用了动态代理,而data + class的构造函数是final,从而导致的 + > * 用set子查询时flex核心库低版本存在[BUG](https://gitee.com/mybatis-flex/mybatis-flex/issues/I96XJA) + 会导致子查询参数丢失问题,需要更新核心库到1.8.2以上(不包含)版本 +2. 使用原生的baseMapper的扩展方法更新: + ```kotlin + val account = Account( + id = 5, + // 此时会执行一次sql + age = filterOne(Account::age) { + Account::age `in` (19..20) + }?.age + ) + Account::class.baseMapper.updateByCondition(account){ + Account::age `in` (19..20) + } + ``` + +## 操作符 + +1. `and`,`or` 等条件关联的中缀方法,返回值为`QueryCondition` +2. `eq`,`ne`,`gt`,`ge`,`lt`,`le`,`in`,`between` 等条件构建符的中缀方法,返回值为`QueryCondition` +3. `allAnd`,`allOr` 多条件相同关联的构建方法,返回值为`QueryCondition` +4. 基于第三点扩展`QueryCondition`和`QueryWrapper`的`andAll`,`orAll` 方法,返回值为`QueryCondition`或`QueryWrapper` + ````kotlin + query { + andAll( + Account::id eq 1, + Account::age le 18, + Account::userName eq "张三", + ) + } + ```` +5. `inTriple`,`inPair` 构建多属性组合 IN ,返回值为`QueryCondition` + ````kotlin + filter { + (Account::id to Account::userName to Account::age).inTriple( + 1 to "张三" to 18, + 2 to "李四" to 19, + ) + } + ```` + 执行的SQL: + ```sql + SELECT * FROM `tb_account` + WHERE (`id` = 1 AND `user_name` = '张三' AND `age` = 18) + OR (`id` = 2 AND `user_name` = '李四' AND `age` = 19) ``` \ No newline at end of file diff --git a/mybatis-flex-kotlin-extensions/src/main/kotlin/annotation/MybatisFlexDsl.kt b/mybatis-flex-kotlin-extensions/src/main/kotlin/annotation/MybatisFlexDsl.kt index dc1d1ab66053afb8603dcc41074aaed97fa85b18..e29836c96ec1605567a58411da8029e48e69e02d 100644 --- a/mybatis-flex-kotlin-extensions/src/main/kotlin/annotation/MybatisFlexDsl.kt +++ b/mybatis-flex-kotlin-extensions/src/main/kotlin/annotation/MybatisFlexDsl.kt @@ -16,6 +16,6 @@ package com.mybatisflex.kotlin.annotation @Retention(AnnotationRetention.BINARY) -@Target(AnnotationTarget.FUNCTION) +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) @DslMarker internal annotation class MybatisFlexDsl diff --git a/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/condition/QueryConditionExtensions.kt b/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/condition/QueryConditionExtensions.kt index b5f43532c936eb74ae3332b7830d627595e03bd0..c7fc65c8f372359cb8f416c33e6e9428f527bb7d 100644 --- a/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/condition/QueryConditionExtensions.kt +++ b/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/condition/QueryConditionExtensions.kt @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("unused") package com.mybatisflex.kotlin.extensions.condition import com.mybatisflex.core.query.QueryCondition @@ -40,3 +41,12 @@ infix fun QueryCondition.or(other: QueryCondition): QueryCondition = this.or(oth inline fun `if`(test: Boolean, block: () -> QueryCondition): QueryCondition = if (test) block() else QueryCondition.createEmpty() +fun QueryCondition.andAll(vararg conditions: QueryCondition): QueryCondition = this and allAnd(*conditions) + +fun QueryCondition.orAll(vararg conditions: QueryCondition): QueryCondition = this or allOr(*conditions) + +fun allAnd(vararg conditions: QueryCondition): QueryCondition = conditions.reduce(QueryCondition::and) + +fun allOr(vararg conditions: QueryCondition): QueryCondition = conditions.reduce(QueryCondition::or) + +fun emptyCondition(): QueryCondition = QueryCondition.createEmpty() diff --git a/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/condition/annotation/ExperimentalEmptyCondition.kt b/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/condition/annotation/ExperimentalEmptyCondition.kt new file mode 100644 index 0000000000000000000000000000000000000000..4882f86896e399f402bc2720d86091956915962a --- /dev/null +++ b/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/condition/annotation/ExperimentalEmptyCondition.kt @@ -0,0 +1,5 @@ +package com.mybatisflex.kotlin.extensions.condition.annotation + +@RequiresOptIn("EmptyCondition 单例仍在测试,请避免将其使用至生产项目中。" + + "如若遇到问题,请改用 QueryCondition.createEmpty() 方法。") +annotation class ExperimentalEmptyCondition diff --git a/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/db/DbExtensions.kt b/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/db/DbExtensions.kt index 34d547d933613a41deb9c24b59667972efdaa7ab..62bc4251af2b8a8a76063ef0872802503b57a9c3 100644 --- a/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/db/DbExtensions.kt +++ b/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/db/DbExtensions.kt @@ -21,6 +21,7 @@ import com.mybatisflex.core.mybatis.Mappers import com.mybatisflex.core.paginate.Page import com.mybatisflex.core.query.QueryColumn import com.mybatisflex.core.query.QueryCondition +import com.mybatisflex.core.query.QueryTable import com.mybatisflex.core.row.Db import com.mybatisflex.core.row.Db.selectListByQuery import com.mybatisflex.core.row.Db.selectOneByQuery @@ -33,7 +34,9 @@ import com.mybatisflex.kotlin.extensions.kproperty.toQueryColumns import com.mybatisflex.kotlin.extensions.model.toEntities import com.mybatisflex.kotlin.extensions.model.toEntityPage import com.mybatisflex.kotlin.scope.QueryScope +import com.mybatisflex.kotlin.scope.UpdateScope import com.mybatisflex.kotlin.scope.queryScope +import com.mybatisflex.kotlin.scope.updateScope import kotlin.reflect.KClass import kotlin.reflect.KProperty import kotlin.reflect.full.isSubclassOf @@ -70,6 +73,11 @@ val KClass.tableInfo: TableInfo " because the entity class corresponding to the generic used by this interface to inherit from BaseMapper cannot be found." } +val KClass.queryTable: QueryTable + get() { + val info = tableInfo + return QueryTable(info.schema, info.tableName) + } val KClass.tableInfoOrNull: TableInfo? get() = if (isSubclassOf(BaseMapper::class)) { @@ -84,19 +92,19 @@ val KClass.tableInfoOrNull: TableInfo? * @param columns 查询的列 * @param init 查询作用域初始化函数 */ -inline fun queryOne( +inline fun queryOne( vararg columns: QueryColumn, init: QueryScope.() -> Unit -): T? = T::class.baseMapperOrNull?.let { +): E? = E::class.baseMapperOrNull?.let { val scope = QueryScope().apply(init) if (!scope.hasSelect() && columns.isNotEmpty()) scope.select(*columns) it.selectOneByQuery(scope) -} ?: T::class.tableInfo.let { +} ?: E::class.tableInfo.let { queryRow(schema = it.schema, tableName = it.tableName, columns = columns) { init() // 如果未调用select方法,则默认查询所有列 - if (this.hasSelect().not()) select(T::class.allColumns) - }?.toEntity(T::class.java) + if (this.hasSelect().not()) select(E::class.allColumns) + }?.toEntity(E::class.java) } /** @@ -104,17 +112,17 @@ inline fun queryOne( * @param columns 查询的列 * @param init 查询作用域初始化函数 */ -inline fun query( +inline fun query( vararg columns: QueryColumn, init: QueryScope.() -> Unit -): List = try { - T::class.baseMapper.selectListByQuery(queryScope(columns = columns, init = init)) +): List = try { + E::class.baseMapper.selectListByQuery(queryScope(columns = columns, init = init)) } catch (e: MybatisFlexException) { - T::class.tableInfo.run { + E::class.tableInfo.run { queryRows(schema = schema, tableName = tableName, columns = columns) { init() // 如果未调用select方法,则默认查询所有列 - if (this.hasSelect().not()) select(*T::class.defaultColumns) + if (this.hasSelect().not()) select(*E::class.defaultColumns) }.toEntities() } } @@ -242,18 +250,33 @@ inline fun paginate( E::class.baseMapper.paginate(page, queryScope(init = init)) } catch (e: MybatisFlexException) { E::class.tableInfo.run { - queryPage(schema, tableName, Page(page.pageNumber, page.pageSize)) { + paginateRows(schema, tableName, Page(page.pageNumber, page.pageSize)) { init() if (this.hasSelect().not()) select(*E::class.defaultColumns) }.toEntityPage() } } - -inline fun queryPage( +inline fun paginateRows( schema: String? = null, tableName: String? = null, page: Page? = null, init: QueryScope.() -> Unit ): Page = Db.paginate(schema, tableName, page, queryScope(init = init)) + +// update---------- +inline fun update(scope: UpdateScope.() -> Unit): Int { + updateScope().run { + scope() + val entity = updateWrapper.toEntity() + return try { + E::class.baseMapper.updateByQuery(entity, this) + } catch (e: MybatisFlexException) { + TODO() +// E::class.tableInfo.let { +// Db.updateByQuery(it.schema, it.tableName, entity.toRow(), this) +// } + } + } +} \ No newline at end of file diff --git a/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/kproperty/KPropertyExtensions.kt b/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/kproperty/KPropertyExtensions.kt index 69242f782243b259ca6bce9f3853471dfd88193f..9d63555de4e74a1b6d06d2c41372a3fa51c131db 100644 --- a/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/kproperty/KPropertyExtensions.kt +++ b/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/kproperty/KPropertyExtensions.kt @@ -192,12 +192,18 @@ infix fun KProperty.`in`(other: QueryWrapper): QueryCondition = column.` infix fun KProperty.eq(other: T): QueryCondition = column.eq(other) +@Deprecated("使用 eq 和 null 进行比较可能是个错误。", ReplaceWith("this.isNull")) +infix fun KProperty.eq(other: Nothing?): QueryCondition = column.eq(other) + infix fun KProperty.eq(other: QueryColumn): QueryCondition = column.eq(other) infix fun KProperty.eq(other: KProperty): QueryCondition = column.eq(other.column) infix fun KProperty.ne(other: T): QueryCondition = column.ne(other) +@Deprecated("使用 ne 和 null 进行比较可能是个错误。", ReplaceWith("this.isNotNull")) +infix fun KProperty.ne(other: Nothing?): QueryCondition = column.ne(other) + infix fun KProperty.ne(other: QueryColumn): QueryCondition = column.ne(other) infix fun KProperty.ne(other: KProperty): QueryCondition = column.ne(other.column) diff --git a/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/model/ModelExtensions.kt b/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/model/ModelExtensions.kt index df6357847e277b19f2896adb74d09c89565cf7e7..f1d0cf5ba21830149a1b0c30e6954ee24e29fe92 100644 --- a/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/model/ModelExtensions.kt +++ b/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/model/ModelExtensions.kt @@ -19,6 +19,9 @@ import com.mybatisflex.core.activerecord.MapperModel import com.mybatisflex.core.paginate.Page import com.mybatisflex.core.row.Row import com.mybatisflex.core.row.RowUtil +import com.mybatisflex.core.table.EntityMetaObject +import com.mybatisflex.core.table.TableInfo +import com.mybatisflex.core.table.TableInfoFactory import com.mybatisflex.core.util.SqlUtil import com.mybatisflex.kotlin.extensions.db.* import java.io.Serializable @@ -28,11 +31,30 @@ import java.io.Serializable * @author KAMOsama */ +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") inline fun Row.toEntity(): T = RowUtil.toEntity(this, T::class.java) +fun T.toRow(): Row { + val tableInfo: TableInfo = TableInfoFactory.ofEntityClass(javaClass) + val metaObject = EntityMetaObject.forObject(this, tableInfo.reflectorFactory) + val row = Row() + tableInfo.primaryKeyList.forEach { + row[it.column] = metaObject.getValue(it.property) + } + tableInfo.columnInfoList.forEach { + row[it.column] = metaObject.getValue(it.property) + } + return row +} + inline fun Collection.toEntities(): MutableList = RowUtil.toEntityList(this.toMutableList(), E::class.java) +inline fun Row.toObject(): T = RowUtil.toObject(this, T::class.java) + +inline fun Collection.toObjects(): MutableList = + RowUtil.toObjectList(this.toMutableList(), E::class.java) + inline fun Page.toEntityPage(): Page = Page(records.toEntities(), pageNumber, pageSize, totalRow) inline fun > List.batchInsert() = E::class.baseMapper.insertBatch(this) diff --git a/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/sql/SqlExtensions.kt b/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/sql/SqlExtensions.kt index 33bb78120d1af739a085f93a9854de86ef076cbb..7665a4e15885e1755b03811e71f005600189f67f 100644 --- a/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/sql/SqlExtensions.kt +++ b/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/sql/SqlExtensions.kt @@ -33,10 +33,18 @@ infix fun QueryColumn.like(value: String): QueryCondition = this.like(value) infix fun QueryColumn.eq(value: Any?): QueryCondition = this.eq(value) +@Deprecated("使用 eq 和 null 进行比较可能是个错误。", ReplaceWith("this.isNull")) +infix fun QueryColumn.eq(value: Nothing?): QueryCondition = this.eq(value) + infix fun QueryColumn.ne(value: Any?): QueryCondition = this.ne(value) +@Deprecated("使用 ne 和 null 进行比较可能是个错误。", ReplaceWith("this.isNull")) +infix fun QueryColumn.ne(value: Nothing?): QueryCondition = this.ne(value) + +@JvmName("equals") infix fun QueryColumn.`=`(value: Any?): QueryCondition = this.eq(value) +@JvmName("notEquals") infix fun QueryColumn.`!=`(value: Any?): QueryCondition = this.ne(value) infix fun QueryColumn.gt(value: Any?): QueryCondition = this.gt(value) @@ -139,11 +147,4 @@ fun QueryColumn.toOrd(order: Order = Order.ASC): QueryOrderBy = when (order) { Order.DESC -> desc() } -operator fun QueryCondition.not(): QueryCondition = - if (this is OperatorQueryCondition) { // 如果是OperatorQueryCondition,则需要判断是否已经反转 - val field = OperatorQueryCondition::class.java.getDeclaredField("operator") - field.isAccessible = true - val operator = field[this] as? String? - if (operator !== null && "NOT" in operator.uppercase()) childCondition - else OperatorQueryCondition("NOT ", this) - } else OperatorQueryCondition("NOT ", this) \ No newline at end of file +operator fun QueryCondition.not(): QueryCondition = QueryMethods.not(this) \ No newline at end of file diff --git a/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/wrapper/QueryWrapperExtensions.kt b/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/wrapper/QueryWrapperExtensions.kt index 2e203072ec125584d485371038fa8c82c945ecdd..22b31ccc14c675e25e27013d41d0ccf8ca778db6 100644 --- a/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/wrapper/QueryWrapperExtensions.kt +++ b/mybatis-flex-kotlin-extensions/src/main/kotlin/extensions/wrapper/QueryWrapperExtensions.kt @@ -17,6 +17,8 @@ package com.mybatisflex.kotlin.extensions.wrapper import com.mybatisflex.core.query.* import com.mybatisflex.core.util.MapperUtil +import com.mybatisflex.kotlin.extensions.condition.allAnd +import com.mybatisflex.kotlin.extensions.condition.allOr import com.mybatisflex.kotlin.extensions.kproperty.defaultColumns import com.mybatisflex.kotlin.extensions.kproperty.toQueryColumns import com.mybatisflex.kotlin.scope.QueryScope @@ -56,7 +58,7 @@ fun QueryWrapper.selectProperties(vararg properties: KProperty<*>): QueryWrapper fun QueryWrapper.select(entityType: KClass<*>): QueryWrapper = this.select(*entityType.defaultColumns) -val T.self +val T.self: QueryWrapperDevelopEntry get() = QueryWrapperDevelopEntry(this) inline fun QueryWrapper.and(isEffective: Boolean, predicate: () -> QueryCondition): QueryWrapper = @@ -79,6 +81,20 @@ fun QueryWrapper.where(queryCondition: QueryCondition, consumer: (QueryWrapper) inline fun QueryWrapper.whereWith(queryCondition: () -> QueryCondition): QueryWrapper = where(queryCondition()) + +@OptIn(ExperimentalContracts::class) +inline fun QueryWrapper.having(predicate: () -> QueryCondition): QueryWrapper { + contract { + callsInPlace(predicate, InvocationKind.EXACTLY_ONCE) + } + return having(predicate()) +} + +fun QueryWrapper.andAll(vararg conditions: QueryCondition): QueryWrapper = this and allAnd(*conditions) + +fun QueryWrapper.orAll(vararg conditions: QueryCondition): QueryWrapper = this or allOr(*conditions) + + /** * wrapper的内部实现的访问,基于官方CPI而编写。其目的用于简化开发时的 * 代码编写,避免在开发时频繁地调用官方的API, @@ -105,9 +121,7 @@ value class QueryWrapperDevelopEntry(val wrapper: T) { var where: QueryCondition? get() = CPI.getWhereQueryCondition(wrapper) - set(value) = CPI.setWhereQueryCondition(wrapper, requireNotNull(value) { - "An error occurred while setting `where`, QueryCondition must not be empty." - }) + set(value) = CPI.setWhereQueryCondition(wrapper, value) var groupBy: List get() = CPI.getGroupByColumns(wrapper) ?: emptyList() @@ -119,6 +133,18 @@ value class QueryWrapperDevelopEntry(val wrapper: T) { val isDistinct: Boolean get() = MapperUtil.hasDistinct(selectColumns) + var joins: List + get() = CPI.getJoins(wrapper) ?: emptyList() + set(value) = CPI.setJoins(wrapper, value) + + var hint: String? + get() = CPI.getHint(wrapper) + set(value) = CPI.setHint(wrapper, value) + + var endFragments: List + get() = CPI.getEndFragments(wrapper) ?: emptyList() + set(value) = CPI.setEndFragments(wrapper, value) + var orderBys: List get() = CPI.getOrderBys(wrapper) ?: emptyList() set(value) = CPI.setOrderBys(wrapper, value) @@ -128,15 +154,18 @@ value class QueryWrapperDevelopEntry(val wrapper: T) { set(value) = CPI.setContext(wrapper, value) val childSelect: List - get() = CPI.getChildSelect(wrapper) + get() = CPI.getChildSelect(wrapper) ?: emptyList() - val valueArray: Array get() = CPI.getValueArray(wrapper) + val valueArray: Array + get() = CPI.getValueArray(wrapper) ?: emptyArray() - val joinValue: Array get() = CPI.getJoinValueArray(wrapper) + val joinValue: Array + get() = CPI.getJoinValueArray(wrapper) ?: emptyArray() - val conditionValue: Array get() = CPI.getConditionValueArray(wrapper) + val conditionValue: Array + get() = CPI.getConditionValueArray(wrapper) ?: emptyArray() - var dataSource: String + var dataSource: String? get() = CPI.getDataSource(wrapper) set(value) = CPI.setDataSource(wrapper, value) @@ -151,13 +180,7 @@ value class QueryWrapperDevelopEntry(val wrapper: T) { var rows: Long get() = CPI.getLimitRows(wrapper) set(value) = CPI.setLimitRows(wrapper, value) -} -@OptIn(ExperimentalContracts::class) -inline fun QueryWrapper.having(predicate: () -> QueryCondition): QueryWrapper { - contract { - callsInPlace(predicate, InvocationKind.EXACTLY_ONCE) - } + fun setFromIfNecessary(schema: String, tableName: String) = CPI.setFromIfNecessary(wrapper, schema, tableName) - return having(predicate()) } diff --git a/mybatis-flex-kotlin-extensions/src/main/kotlin/scope/QueryScope.kt b/mybatis-flex-kotlin-extensions/src/main/kotlin/scope/QueryScope.kt index 3a95d12ae83a3b1e2a9e01a6b185178bb841cb2b..c4a7d43ec6395395d57435bc4b7c9a2ca07aa156 100755 --- a/mybatis-flex-kotlin-extensions/src/main/kotlin/scope/QueryScope.kt +++ b/mybatis-flex-kotlin-extensions/src/main/kotlin/scope/QueryScope.kt @@ -16,7 +16,6 @@ package com.mybatisflex.kotlin.scope import com.mybatisflex.core.query.QueryColumn -import com.mybatisflex.core.query.QueryWrapper import com.mybatisflex.core.query.QueryWrapperAdapter import com.mybatisflex.core.util.LambdaGetter import com.mybatisflex.kotlin.extensions.kproperty.column @@ -52,7 +51,7 @@ class QueryScope : QueryWrapperAdapter() { } -inline fun queryScope(vararg columns: QueryColumn, init: QueryScope.() -> Unit = {}): QueryWrapper = +inline fun queryScope(vararg columns: QueryColumn, init: QueryScope.() -> Unit = {}): QueryScope = QueryScope().apply(init).apply { if (columns.isNotEmpty() && !hasSelect()) select(*columns) } diff --git a/mybatis-flex-kotlin-extensions/src/main/kotlin/scope/UpdateScope.kt b/mybatis-flex-kotlin-extensions/src/main/kotlin/scope/UpdateScope.kt new file mode 100644 index 0000000000000000000000000000000000000000..bfa84a241bd4a67ea0d5512f5ad9239cb44a5199 --- /dev/null +++ b/mybatis-flex-kotlin-extensions/src/main/kotlin/scope/UpdateScope.kt @@ -0,0 +1,45 @@ +package com.mybatisflex.kotlin.scope + +import com.mybatisflex.core.query.QueryColumn +import com.mybatisflex.core.query.QueryWrapperAdapter +import com.mybatisflex.core.table.TableInfoFactory +import com.mybatisflex.core.update.UpdateWrapper +import com.mybatisflex.core.util.UpdateEntity +import com.mybatisflex.kotlin.extensions.kproperty.column +import kotlin.reflect.KProperty1 + +class UpdateScope( + private val entityClass: Class, +) : QueryWrapperAdapter>() { + + @PublishedApi + internal val updateWrapper: UpdateWrapper = UpdateEntity.of(entityClass) as UpdateWrapper + + private val tableInfo = TableInfoFactory.ofEntityClass(entityClass) + + + infix fun KProperty1.set(value: V) { + updateWrapper.set(tableInfo.getColumnByProperty(this.name), value) + } + + infix fun KProperty1.setRaw(queryWrapper: QueryScope.() -> Unit) { + updateWrapper.setRaw(tableInfo.getColumnByProperty(this.name), queryScope().apply(queryWrapper).limit(1)) + } + + fun KProperty1.setRaw(column: QueryColumn, queryWrapper: QueryScope.() -> Unit) { + setRaw { + queryWrapper() + select(column) + } + } + + fun KProperty1.setRaw(property: KProperty1<*, *>, queryWrapper: QueryScope.() -> Unit) { + setRaw(property.column, queryWrapper) + } + +} + + +inline fun updateScope(): UpdateScope { + return UpdateScope(T::class.java) +} \ No newline at end of file diff --git a/mybatis-flex-kotlin-extensions/src/test/kotlin/example/KotlinExample.kt b/mybatis-flex-kotlin-extensions/src/test/kotlin/example/KotlinExample.kt index 9c67c49f86bf1b315b990198b8f227326ed8a23e..226d20ea2688bd01222c7ed3050dccd9931ae11b 100644 --- a/mybatis-flex-kotlin-extensions/src/test/kotlin/example/KotlinExample.kt +++ b/mybatis-flex-kotlin-extensions/src/test/kotlin/example/KotlinExample.kt @@ -7,20 +7,17 @@ import com.mybatisflex.core.query.QueryColumn import com.mybatisflex.core.query.QueryWrapper import com.mybatisflex.kotlin.example.entity.Account import com.mybatisflex.kotlin.example.mapper.AccountMapper +import com.mybatisflex.kotlin.extensions.condition.allAnd import com.mybatisflex.kotlin.extensions.condition.and import com.mybatisflex.kotlin.extensions.condition.or import com.mybatisflex.kotlin.extensions.db.* import com.mybatisflex.kotlin.extensions.kproperty.* -import com.mybatisflex.kotlin.extensions.mapper.remove -import com.mybatisflex.kotlin.extensions.mapper.save -import com.mybatisflex.kotlin.extensions.mapper.update +import com.mybatisflex.kotlin.extensions.mapper.* import com.mybatisflex.kotlin.extensions.model.batchDeleteById import com.mybatisflex.kotlin.extensions.model.batchInsert import com.mybatisflex.kotlin.extensions.model.batchUpdateById import com.mybatisflex.kotlin.extensions.sql.orderBy -import com.mybatisflex.kotlin.extensions.wrapper.and -import com.mybatisflex.kotlin.extensions.wrapper.from -import com.mybatisflex.kotlin.extensions.wrapper.selectFrom +import com.mybatisflex.kotlin.extensions.wrapper.* import com.mybatisflex.kotlin.scope.runFlex import org.apache.ibatis.logging.stdout.StdOutImpl import org.junit.jupiter.api.Test @@ -134,20 +131,64 @@ class KotlinExample { @Test fun testUpdate() { - // 通过条件更新 + // 通过条件查询到后更新 filterOne { Account::id eq 2 }?.apply { age = 20 }?.update { - Account::userName eq it.userName + Account::userName eq it.userName and (Account::age le 18) } // 通过id更新 // filterOne { Account::id eq 2 }?.apply { age = 20 }?.updateById() filterOne { Account::id eq 2 }?.also(::println) } + @Test + fun testUpdate2() { + println("更新前: ${Account::class.all.first()}") + filterOne(Account::age) { + Account::age `in` (19..20) + } + update { + Account::id set 5 +// Account::age setRaw { +// select(Account::age) +// from(Account::class) +// this.and(Account::age `in` (19..20)) +// limit(1) +// } +// 或者写成: + Account::age.setRaw(Account::age) { + from(Account::class) + and(Account::age `in` (19..20)) + } +// 或者写成: +// Account::age set queryScope(Account::age.column){ +// from(Account::class) +// and(Account::age `in` (19..20)) +// limit(1) +// } +// 或者写成 (此时会执行两次sql): +// Account::age set filterOne(Account::age){ +// Account::age `in` (19..20) +// }?.age + whereWith { Account::id eq 1 and (Account::userName eq "张三") } + } +// val account = Account( +// id = 5, +// // 此时会执行一次sql +// age = filterOne(Account::age) { +// Account::age `in` (19..20) +// }?.age +// ) +// Account::class.baseMapper.updateByCondition(account){ +// Account::age `in` (19..20) +// } + println("更新后: ${Account::class.all.first()}") + } + @Test fun testDelete() { // 通过条件删除 filterOne { Account::id eq 2 }?.remove { - Account::userName eq it.userName + Account::userName eq it.userName and (Account::age le 18) } // 通过id删除 // filterOne { Account::id eq 2 }?.apply { age = 20 }?.removeById() @@ -160,14 +201,14 @@ class KotlinExample { @Test fun testFilter() { val accounts: List = filter { - (Account::id.isNotNull) - .and { - (Account::id to Account::userName to Account::age).inTriple( - 1 to "张三" to 18, - 2 to "李四" to 19, - ) - } - .and(Account::age.`in`(17..19) or { Account::birthday between (start to end) }) + allAnd( + Account::id.isNotNull, + (Account::id to Account::userName to Account::age).inTriple( + 1 to "张三" to 18, + 2 to "李四" to 19 + ), + Account::age.`in`(17..19) + ) or { Account::birthday between (start to end) } } accounts.forEach(::println) } @@ -178,14 +219,14 @@ class KotlinExample { @Test fun testFilterOne() { val account: Account? = filterOne(Account::age) { - (Account::id.isNotNull) - .and { - (Account::id to Account::userName to Account::age).inTriple( - 1 to "张三" to 18, - 2 to "李四" to 19, - ) - } - .and(Account::age.`in`(17..19) or { Account::birthday between (start to end) }) + allAnd( + Account::id.isNotNull, + (Account::id to Account::userName to Account::age).inTriple( + 1 to "张三" to 18, + 2 to "李四" to 19 + ), + Account::age.`in`(17..19) + ) or { Account::birthday between (start to end) } } println(account) } @@ -197,9 +238,8 @@ class KotlinExample { fun testQuery() { val accounts: List = query { selectFrom(Account::id, Account::userName) - where { - and(Account::age `in` (17..19)) - and(Account::birthday between (start to end)) + whereWith { + Account::age `in` (17..19) and (Account::birthday between (start to end)) } orderBy -Account::id limit(2) } @@ -274,4 +314,35 @@ class KotlinExample { Account.findByAge(18, 1).forEach(::println) } + @Test + fun testAllCondition() { + query { +// andAll: + andAll( + Account::id eq 1, + Account::age eq 18, + Account::userName eq "张三", + ) +// or +// (Account::id eq 1).andAll( +// Account::age eq 18, +// Account::userName eq "张三", +// ) + +// orAll: +// orAll( +// Account::id eq 1, +// Account::age `in` (17..20), +// Account::userName eq "张三", +// ) +// or +// (Account::id eq 1).orAll( +// Account::age `in` (17..20), +// Account::userName eq "张三", +// ) + + }.also { println(it) } + } + + } diff --git a/mybatis-flex-kotlin-extensions/src/test/kotlin/example/entity/Account.kt b/mybatis-flex-kotlin-extensions/src/test/kotlin/example/entity/Account.kt index 84b07a35ceb34b83ab0f48c872e0a714060b0646..32cff45a85a76a88d2a820c79756d2adfab16b3c 100755 --- a/mybatis-flex-kotlin-extensions/src/test/kotlin/example/entity/Account.kt +++ b/mybatis-flex-kotlin-extensions/src/test/kotlin/example/entity/Account.kt @@ -21,7 +21,7 @@ import java.util.* */ @Table("tb_account") -data class Account( +open class Account( @Id var id: Int = -1, var userName: String? = null, var age: Int? = null, @@ -36,5 +36,9 @@ data class Account( } } } + + override fun toString(): String { + return "Account(id=$id, userName=$userName, age=$age, birthday=$birthday)" + } } diff --git a/readme.md b/readme.md index bf9653a9f4fa29e048061ada129c726eb2918636..a6e92a5c48c4f496f54bbfd42ae1379f73307fc2 100644 --- a/readme.md +++ b/readme.md @@ -157,7 +157,9 @@ data class Account( - 使用 `@Table("tb_account")` 设置实体类与表名的映射关系 - 使用 `@Id` 标识主键 -> ⚠️ 最好不要写成 data class ,否则没有无参构造某些情况下会报错; + +> ⚠️ 最好不要写成 data class ,否则没有无参构造某些情况下会报错,例如属性的顺序与数据库字段不一致会导致报错; +> > 如有需要可以安装官方 [noArg](https://kotlinlang.org/docs/no-arg-plugin.html) 插件 **第 4 步:开始使用** diff --git a/readme_zh.md b/readme_zh.md index eda0b332c625dc74cf1652b78ddea70a5b4d060e..fb82ab3226063e10b024576b8dda2a11639f599d 100644 --- a/readme_zh.md +++ b/readme_zh.md @@ -157,7 +157,9 @@ data class Account( - 使用 `@Table("tb_account")` 设置实体类与表名的映射关系 - 使用 `@Id` 标识主键 -> ⚠️ 最好不要写成 data class ,否则没有无参构造某些情况下会报错; + +> ⚠️ 最好不要写成 data class ,否则没有无参构造某些情况下会报错,例如属性的顺序与数据库字段不一致会导致报错; +> > 如有需要可以安装官方 [noArg](https://kotlinlang.org/docs/no-arg-plugin.html) 插件 **第 4 步:开始使用**