diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 7bb627d8..b0277067 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -63,6 +63,7 @@ export default defineConfig({ {text: 'QueryWrapper', link: '/zh/base/querywrapper'}, {text: 'QueryWrapperChain', link: '/zh/base/query-wrapper-chain'}, {text: 'Db + Row', link: '/zh/base/db-row'}, + {text: 'Active Record', link: '/zh/base/active-record'}, {text: 'IService', link: '/zh/base/service'}, {text: 'SpringBoot 配置文件', link: '/zh/base/configuration'}, {text: 'MyBatisFlexCustomizer', link: '/zh/base/mybatis-flex-customizer'}, diff --git a/docs/zh/base/active-record.md b/docs/zh/base/active-record.md new file mode 100644 index 00000000..d8148ba7 --- /dev/null +++ b/docs/zh/base/active-record.md @@ -0,0 +1,141 @@ +# Active Record + +[Active Record 模式](http://www.martinfowler.com/eaaCatalog/activeRecord.html)出自 Martin Fowler +写的《[企业应用架构模式](https://book.douban.com/subject/4826290/)》书中。在 Active Record +模式中,对象中既有持久存储的数据,也有针对数据的操作。Active Record 模式把数据存取逻辑作为对象的一部分,处理对象的用户知道如何把数据写入数据库,还知道如何从数据库中读出数据。 + +在 MyBatis-Flex 中实现 Active Record +功能十分简单,只需继承 [Model](https://gitee.com/mybatis-flex/mybatis-flex/blob/main/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/Model.java) +即可。 + +::: tip 注意事项 + +- 使用 Active Record 功能时,项目中必须注入对应实体类的 BaseMapper 对象! +- 如果不想手动创建 Mapper 接口,可以使用 [APT](../others/apt.md#配置文件和选项) 辅助生成。 + ::: + +## 使用示例 + +在以下示例当中,使用了 [Lombok](https://www.projectlombok.org/) 对实体类进行了增强,以便我们全链式调用: + +1. `@Table` 标记了实体类对应的数据表。 +2. `@Data` 为我们生成了 setter/getter、toString、equals、hashCode 等方法, + 其中 `staticConstructor = "create"` 为我们创建了一个 `create()` 静态方法用于链式调用。 +3. `@Accessors(chain = true)` 为我们开启了 `return this;` 这样既可以被序列化,又可以链式调用。 + +```java + +@Table("tb_account") +@Accessors(chain = true) +@Data(staticConstructor = "create") +public class Account extends Model { + + @Id(keyType = KeyType.Auto) + private Long id; + private String userName; + private Integer age; + private Date birthday; + +} +``` + +这样我们就可以流畅的使用 Active Record 功能了: + +```java +@RestController +@RequestMapping("/account") +public class AccountController { + + @PostMapping("save") + public boolean save(@RequestBody Account account) { + return account.save(); + } + +} +``` + +## 保存数据 + +`Model` 提供了 `save` 方法来保存数据,调用该方法前需要将保存数据填充: + +```java +Account.create() + .setUserName("张三") + .setAge(18) + .setBirthday(new Date()) + .save(); +``` + +## 删除数据 + +`Model` 提供了 `remove` 方法来删除数据: + +- 根据主键删除 + +```java +Account.create() + .setId(1L) + .removeById(); +``` + +- 根据条件删除 + +```java +Account.create() + .where(Account::getId).eq(1L) + .remove(); +``` + +## 更新数据 + +`Model` 提供了 `update` 方法来更新数据,调用该方法前需要将更新数据填充: + +- 根据主键更新 + +```java +Account.create() + .setId(1L) + .setAge(100) + .updateById(); +``` + +- 根据条件更新 + +```java +Account.create() + .setAge(100) + .where(Account::getId).eq(1L) + .update(); +``` + +## 查询数据 + +### 查询一条数据 + +`Model` 提供了 `one` 方法来查询一条数据: + +```java +Account.create() + .where(Account::getId).eq(1L) + .one(); +``` + +### 查询多条数据 + +`Model` 提供了 `list` 方法来查询多条数据: + +```java +Account.create() + .where(Account::getAge).ge(18) + .list(); +``` + +### 查询分页数据 + +`Model` 提供了 `page` 方法来查询分页数据: + +```java +Account.create() + .where(Account::getAge).ge(18) + .page(Page.of(1,10)); +``` diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/MapperModel.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/MapperModel.java new file mode 100644 index 00000000..1b4e7431 --- /dev/null +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/MapperModel.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mybatisflex.core.activerecord; + +import com.mybatisflex.core.BaseMapper; +import com.mybatisflex.core.mybatis.Mappers; +import com.mybatisflex.core.table.TableInfo; +import com.mybatisflex.core.table.TableInfoFactory; +import com.mybatisflex.core.util.SqlUtil; + +import java.util.Optional; + +/** + *

使用 {@link BaseMapper} 进行 CRUD 操作的实体类的抽象接口。 + * + *

使用接口是为了方便拓展,该接口提供了简单的根据 主键 操作数据的方法, + * 实现类可以进行其他方法的扩展。 + * + * @param 实体类类型 + * @author 王帅 + * @since 2023-07-23 + */ +@SuppressWarnings({"unused", "unchecked"}) +public interface MapperModel { + + /** + * 获取实体类对应的 {@link BaseMapper} 接口。 + * + * @return {@link BaseMapper} 接口 + */ + default BaseMapper baseMapper() { + return Mappers.ofEntityClass((Class) getClass()); + } + + /** + *

获取实体类主键数据。 + * + *

可以拓展该方法提高效率,例如: + *

{@code
+     * return new Object[]{id};
+     * }
+ * + * @return 主键数据数组 + */ + default Object[] getPkValues() { + TableInfo tableInfo = TableInfoFactory.ofEntityClass(getClass()); + return tableInfo.buildPkSqlArgs(this); + } + + /** + * 保存数据(自动忽略 {@code null} 值)。 + * + * @return {@code true} 保存成功,{@code false} 保存失败 + */ + default boolean save() { + return save(true); + } + + /** + * 保存数据,并设置是否忽略 {@code null} 值。 + * + * @param ignoreNulls 是否忽略 {@code null} 值 + * @return {@code true} 保存成功,{@code false} 保存失败 + */ + default boolean save(boolean ignoreNulls) { + return SqlUtil.toBool(baseMapper().insert((T) this, ignoreNulls)); + } + + /** + * 保存或者更新数据,如果实体类主键没有值,则 保存 数据;如果实体类主键有值,则 + * 更新 数据(全部自动忽略 {@code null} 值)。 + * + * @return {@code true} 保存或更新成功,{@code false} 保存或更新失败 + */ + default boolean saveOrUpdate() { + return saveOrUpdate(true); + } + + /** + * 保存或者更新数据,如果实体类主键没有值,则 保存 数据;如果实体类主键有值,则 + * 更新 数据,并设置是否忽略 {@code null} 值。 + * + * @param ignoreNulls 是否忽略 {@code null} 值 + * @return {@code true} 保存或更新成功,{@code false} 保存或更新失败 + */ + default boolean saveOrUpdate(boolean ignoreNulls) { + return SqlUtil.toBool(baseMapper().insertOrUpdate((T) this, ignoreNulls)); + } + + /** + * 根据实体类主键删除数据。 + * + * @return {@code true} 删除成功,{@code false} 删除失败 + */ + default boolean removeById() { + return SqlUtil.toBool(baseMapper().deleteById(getPkValues())); + } + + /** + * 根据实体类主键更新数据(自动忽略 {@code null} 值)。 + * + * @return {@code true} 更新成功,{@code false} 更新失败 + */ + default boolean updateById() { + return updateById(true); + } + + /** + * 根据实体类主键更新数据,并设置是否忽略 {@code null} 值。 + * + * @param ignoreNulls 是否忽略 {@code null} 值 + * @return {@code true} 更新成功,{@code false} 更新失败 + */ + default boolean updateById(boolean ignoreNulls) { + return SqlUtil.toBool(baseMapper().update((T) this, ignoreNulls)); + } + + /** + * 根据实体类主键获取一条数据。 + * + * @return 数据 + */ + default T oneById() { + return baseMapper().selectOneById(getPkValues()); + } + + /** + * 根据实体类主键获取一条数据,并封装为 {@link Optional} 返回。 + * + * @return 数据 + */ + default Optional oneByIdOpt() { + return Optional.ofNullable(oneById()); + } + +} diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/Model.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/Model.java new file mode 100644 index 00000000..d0d17efb --- /dev/null +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/Model.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mybatisflex.core.activerecord; + +import com.mybatisflex.core.activerecord.query.QueryModel; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.util.SqlUtil; + +import java.util.List; +import java.util.Optional; + +/** + * Active Record 模型。 + * + * @param 实体类类型 + * @author 王帅 + * @since 2023-07-24 + */ +@SuppressWarnings({"unused", "unchecked"}) +public abstract class Model> + extends QueryModel + implements MapperModel { + + /** + * 根据实体类构建的条件删除数据。 + * + * @return {@code true} 删除成功,{@code false} 删除失败 + */ + public boolean remove() { + return SqlUtil.toBool(baseMapper().deleteByQuery(getQueryWrapper())); + } + + /** + * 根据实体类构建的条件更新数据(自动忽略 {@code null} 值)。 + * + * @return {@code true} 更新成功,{@code false} 更新失败 + */ + public boolean update() { + return update(true); + } + + /** + * 根据实体类构建的条件更新数据,并设置是否忽略 {@code null} 值。 + * + * @param ignoreNulls 是否忽略 {@code null} 值 + * @return {@code true} 更新成功,{@code false} 更新失败 + */ + public boolean update(boolean ignoreNulls) { + return SqlUtil.toBool(baseMapper().updateByQuery((T) this, ignoreNulls, getQueryWrapper())); + } + + /** + * 根据实体类构建的条件查询数据数量。 + * + * @return 数据数量 + */ + public long count() { + return baseMapper().selectCountByQuery(getQueryWrapper()); + } + + /** + * 根据实体类构建的条件判断数据是否存在。 + * + * @return {@code true} 数据存在,{@code false} 数据不存在 + */ + public boolean exists() { + return SqlUtil.toBool(count()); + } + + /** + * 根据实体类构建的条件获取一条数据。 + * + * @return 数据 + */ + public T one() { + return baseMapper().selectOneByQuery(getQueryWrapper().limit(1)); + } + + /** + * 根据实体类构建的条件获取一条数据,并封装为 {@link Optional} 返回。 + * + * @return 数据 + */ + public Optional oneOpt() { + return Optional.ofNullable(one()); + } + + /** + * 根据实体类构建的条件获取多条数据。 + * + * @return 数据列表 + */ + public List list() { + return baseMapper().selectListByQuery(getQueryWrapper()); + } + + /** + * 根据实体类构建的条件获取分页数据。 + * + * @param page 分页对象 + * @return 分页数据 + */ + public Page page(Page page) { + return baseMapper().paginate(page, getQueryWrapper()); + } + +} diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/package-info.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/package-info.java new file mode 100644 index 00000000..e1cd4ffb --- /dev/null +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Active Record 功能。 + */ +package com.mybatisflex.core.activerecord; diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/query/OrderByBuilder.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/query/OrderByBuilder.java new file mode 100644 index 00000000..7287001a --- /dev/null +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/query/OrderByBuilder.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mybatisflex.core.activerecord.query; + + +import com.mybatisflex.core.query.QueryColumn; +import com.mybatisflex.core.util.LambdaGetter; +import com.mybatisflex.core.util.LambdaUtil; + +/** + * Lambda 排序构建器。 + * + * @author 王帅 + * @since 2023-07-25 + */ +public class OrderByBuilder> { + + private final R queryModel; + private final QueryColumn queryColumn; + + public OrderByBuilder(R queryModel, LambdaGetter getter) { + this.queryModel = queryModel; + this.queryColumn = LambdaUtil.getQueryColumn(getter); + } + + public R asc() { + return queryModel.orderBy(queryColumn.asc()); + } + + public R desc() { + return queryModel.orderBy(queryColumn.desc()); + } + +} diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/query/QueryModel.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/query/QueryModel.java new file mode 100644 index 00000000..ece914c4 --- /dev/null +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/query/QueryModel.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mybatisflex.core.activerecord.query; + +import com.mybatisflex.annotation.Column; +import com.mybatisflex.core.query.*; +import com.mybatisflex.core.util.LambdaGetter; +import com.mybatisflex.core.util.LambdaUtil; + +/** + *

实体类条件查询构建模型。 + * + *

该类内部维护了一个 {@link QueryWrapper} 属性,用来构建查询条件。 + * 通过实体类属性构建的查询条件都是值等于,该扩展用于非等于值构建,及一些其他方法。 + * 如果不想通过实体类直接构建查询条件,可以不继承该类。 + * + * @param 实体类类型 + * @author 王帅 + * @since 2023-07-24 + */ +@SuppressWarnings({"unused", "unchecked"}) +public abstract class QueryModel> { + + @Column(ignore = true) + private QueryWrapper queryWrapper; + + protected QueryWrapper getQueryWrapper() { + if (queryWrapper == null) { + queryWrapper = QueryWrapper.create(); + } + return queryWrapper; + } + + public T select() { + return (T) this; + } + + public T select(String... columns) { + getQueryWrapper().select(columns); + return (T) this; + } + + public T select(QueryColumn... queryColumns) { + getQueryWrapper().select(queryColumns); + return (T) this; + } + + public T select(LambdaGetter... columns) { + getQueryWrapper().select(columns); + return (T) this; + } + + public T select(QueryColumn[]... queryColumns) { + getQueryWrapper().select(queryColumns); + return (T) this; + } + + public T where(QueryCondition queryCondition) { + getQueryWrapper().where(queryCondition); + return (T) this; + } + + public T where(String sql) { + getQueryWrapper().where(sql); + return (T) this; + } + + public T where(String sql, Object... params) { + getQueryWrapper().where(sql, params); + return (T) this; + } + + public WhereBuilder where(LambdaGetter column) { + return new WhereBuilder<>((T) this, LambdaUtil.getQueryColumn(column), SqlConnector.AND); + } + + public T and(QueryCondition queryCondition) { + getQueryWrapper().and(queryCondition); + return (T) this; + } + + public T and(String sql) { + getQueryWrapper().and(sql); + return (T) this; + } + + public T and(String sql, Object... params) { + getQueryWrapper().and(sql, params); + return (T) this; + } + + public WhereBuilder and(LambdaGetter column) { + return new WhereBuilder<>((T) this, LambdaUtil.getQueryColumn(column), SqlConnector.AND); + } + + public T or(QueryCondition queryCondition) { + getQueryWrapper().or(queryCondition); + return (T) this; + } + + public T or(String sql) { + getQueryWrapper().or(sql); + return (T) this; + } + + public T or(String sql, Object... params) { + getQueryWrapper().or(sql, params); + return (T) this; + } + + public WhereBuilder or(LambdaGetter column) { + return new WhereBuilder<>((T) this, LambdaUtil.getQueryColumn(column), SqlConnector.OR); + } + + public T groupBy(String... names) { + getQueryWrapper().groupBy(names); + return (T) this; + } + + public T groupBy(QueryColumn... columns) { + getQueryWrapper().groupBy(columns); + return (T) this; + } + + public T groupBy(LambdaGetter... columns) { + getQueryWrapper().groupBy(columns); + return (T) this; + } + + public T having(QueryCondition queryCondition) { + getQueryWrapper().having(queryCondition); + return (T) this; + } + + public T orderBy(QueryOrderBy... orderBys) { + getQueryWrapper().orderBy(orderBys); + return (T) this; + } + + public T orderBy(String... orderBys) { + getQueryWrapper().orderBy(orderBys); + return (T) this; + } + + public OrderByBuilder orderBy(LambdaGetter column) { + return new OrderByBuilder<>((T) this, column); + } + + public T limit(Integer rows) { + getQueryWrapper().limit(rows); + return (T) this; + } + + public T offset(Integer offset) { + getQueryWrapper().offset(offset); + return (T) this; + } + + public T limit(Integer offset, Integer rows) { + getQueryWrapper().limit(offset, rows); + return (T) this; + } + +} diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/query/WhereBuilder.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/query/WhereBuilder.java new file mode 100644 index 00000000..285429d6 --- /dev/null +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/query/WhereBuilder.java @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mybatisflex.core.activerecord.query; + +import com.mybatisflex.core.query.CPI; +import com.mybatisflex.core.query.QueryColumn; +import com.mybatisflex.core.query.SqlConnector; +import com.mybatisflex.core.util.LambdaGetter; +import com.mybatisflex.core.util.LambdaUtil; + +import java.util.Collection; +import java.util.function.Predicate; + +/** + * Lambda 条件构建器。 + * + * @author 王帅 + * @since 2023-07-24 + */ +public class WhereBuilder> { + + private final R queryModel; + private final QueryColumn queryColumn; + private final SqlConnector connector; + + public WhereBuilder(R queryModel, QueryColumn queryColumn, SqlConnector connector) { + this.queryModel = queryModel; + this.queryColumn = queryColumn; + this.connector = connector; + } + + public R eq(Object value) { + if (value != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.eq(value), connector); + } + return queryModel; + } + + public R eq(Object value, Predicate when) { + if (value != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.eq(value, when), connector); + } + return queryModel; + } + + public R eq(LambdaGetter value) { + return eq(LambdaUtil.getQueryColumn(value)); + } + + public R eq(LambdaGetter value, Predicate when) { + return eq(LambdaUtil.getQueryColumn(value), when); + } + + public R ne(Object value) { + if (value != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.ne(value), connector); + } + return queryModel; + } + + public R ne(Object value, Predicate when) { + if (value != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.ne(value, when), connector); + } + return queryModel; + } + + public R ne(LambdaGetter value) { + return ne(LambdaUtil.getQueryColumn(value)); + } + + public R ne(LambdaGetter value, Predicate when) { + return ne(LambdaUtil.getQueryColumn(value), when); + } + + public R like(Object value) { + if (value != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.like(value), connector); + } + return queryModel; + } + + public R like(Object value, Predicate when) { + if (value != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.like(value, when), connector); + } + return queryModel; + } + + public R likeLeft(Object value) { + if (value != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.likeLeft(value), connector); + } + return queryModel; + } + + public R likeLeft(Object value, Predicate when) { + if (value != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.likeLeft(value, when), connector); + } + return queryModel; + } + + public R likeRight(Object value) { + if (value != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.likeRight(value), connector); + } + return queryModel; + } + + public R likeRight(Object value, Predicate when) { + if (value != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.likeRight(value, when), connector); + } + return queryModel; + } + + public R gt(Object value) { + if (value != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.gt(value), connector); + } + return queryModel; + } + + public R gt(Object value, Predicate when) { + if (value != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.gt(value, when), connector); + } + return queryModel; + } + + public R gt(LambdaGetter value) { + return gt(LambdaUtil.getQueryColumn(value)); + } + + public R gt(LambdaGetter value, Predicate when) { + return gt(LambdaUtil.getQueryColumn(value), when); + } + + public R ge(Object value) { + if (value != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.ge(value), connector); + } + return queryModel; + } + + public R ge(Object value, Predicate when) { + if (value != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.ge(value, when), connector); + } + return queryModel; + } + + public R ge(LambdaGetter value) { + return ge(LambdaUtil.getQueryColumn(value)); + } + + public R ge(LambdaGetter value, Predicate when) { + return ge(LambdaUtil.getQueryColumn(value), when); + } + + public R lt(Object value) { + if (value != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.lt(value), connector); + } + return queryModel; + } + + public R lt(Object value, Predicate when) { + if (value != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.lt(value, when), connector); + } + return queryModel; + } + + public R lt(LambdaGetter value) { + return lt(LambdaUtil.getQueryColumn(value)); + } + + public R lt(LambdaGetter value, Predicate when) { + return lt(LambdaUtil.getQueryColumn(value), when); + } + + public R le(Object value) { + if (value != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.le(value), connector); + } + return queryModel; + } + + public R le(Object value, Predicate when) { + if (value != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.le(value, when), connector); + } + return queryModel; + } + + public R le(LambdaGetter value) { + return le(LambdaUtil.getQueryColumn(value)); + } + + public R le(LambdaGetter value, Predicate when) { + return le(LambdaUtil.getQueryColumn(value), when); + } + + public R isNull() { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.isNull(), connector); + return queryModel; + } + + public R isNull(Predicate when) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.isNull(when), connector); + return queryModel; + } + + public R isNotNull() { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.isNotNull(), connector); + return queryModel; + } + + public R isNotNull(Predicate when) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.isNotNull(when), connector); + return queryModel; + } + + public R in(Object... arrays) { + if (arrays != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.in(arrays), connector); + } + return queryModel; + } + + public R in(Object[] arrays, Predicate when) { + //忽略 QueryWrapper.in("name", null) 的情况 + if (arrays != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.in(arrays, when), connector); + } + return queryModel; + } + + public R in(R queryModel) { + if (queryModel != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.in(queryModel), connector); + } + return this.queryModel; + } + + public R in(R queryModel, Predicate when) { + if (queryModel != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.in(queryModel, when), connector); + } + return this.queryModel; + } + + public R in(Collection collection) { + if (queryModel != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.in(collection), connector); + } + return queryModel; + } + + public R in(Collection collection, Predicate when) { + if (queryModel != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.in(collection, when), connector); + } + return queryModel; + } + + public R notIn(Object... arrays) { + if (queryModel != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.notIn(arrays), connector); + } + return queryModel; + } + + public R notIn(Object[] arrays, Predicate when) { + if (queryModel != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.notIn(arrays, when), connector); + } + return queryModel; + } + + public R notIn(Collection collection) { + if (queryModel != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.notIn(collection), connector); + } + return queryModel; + } + + public R notIn(Collection collection, Predicate when) { + if (queryModel != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.notIn(collection, when), connector); + } + return queryModel; + } + + public R notIn(R queryModel) { + if (queryModel != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.notIn(queryModel), connector); + } + return this.queryModel; + } + + public R notIn(R queryModel, Predicate when) { + if (queryModel != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.notIn(queryModel, when), connector); + } + return this.queryModel; + } + + public R between(Object start, Object end) { + if (queryModel != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.between(start, end), connector); + } + return queryModel; + } + + public R between(Object start, Object end, Predicate when) { + if (queryModel != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.between(start, end, when), connector); + } + return queryModel; + } + + public R notBetween(Object start, Object end) { + if (queryModel != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.notBetween(start, end), connector); + } + return queryModel; + } + + public R notBetween(Object start, Object end, Predicate when) { + if (queryModel != null) { + CPI.addWhereQueryCondition(queryModel.getQueryWrapper(), queryColumn.notBetween(start, end, when), connector); + } + return queryModel; + } + +} diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/query/package-info.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/query/package-info.java new file mode 100644 index 00000000..ca0a2d88 --- /dev/null +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/query/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * 条件查询功能。 + */ +package com.mybatisflex.core.activerecord.query; diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/query/CPI.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/query/CPI.java index a5a45278..7875bb95 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/query/CPI.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/query/CPI.java @@ -125,6 +125,10 @@ public class CPI { return queryWrapper.getWhereQueryCondition(); } + public static void addWhereQueryCondition(QueryWrapper queryWrapper, QueryCondition queryCondition, SqlConnector connector) { + queryWrapper.addWhereQueryCondition(queryCondition, connector); + } + public static List getGroupByColumns(QueryWrapper queryWrapper) { return queryWrapper.getGroupByColumns(); } diff --git a/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/model/Good.java b/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/model/Good.java index 87d9def7..e95742c9 100644 --- a/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/model/Good.java +++ b/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/model/Good.java @@ -17,7 +17,9 @@ package com.mybatisflex.test.model; import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; import com.mybatisflex.annotation.Table; +import com.mybatisflex.core.activerecord.Model; import java.util.Objects; @@ -28,35 +30,42 @@ import java.util.Objects; * @since 2023-06-07 */ @Table("tb_good") -public class Good { +public class Good extends Model { - @Id + @Id(keyType = KeyType.Auto) private Integer goodId; private String name; - private double price; + private Double price; + + public static Good create() { + return new Good(); + } public Integer getGoodId() { return goodId; } - public void setGoodId(Integer goodId) { + public Good setGoodId(Integer goodId) { this.goodId = goodId; + return this; } public String getName() { return name; } - public void setName(String name) { + public Good setName(String name) { this.name = name; + return this; } - public double getPrice() { + public Double getPrice() { return price; } - public void setPrice(double price) { + public Good setPrice(Double price) { this.price = price; + return this; } @Override @@ -79,23 +88,20 @@ public class Good { Good good = (Good) o; - if (Double.compare(good.price, price) != 0) { - return false; - } if (!Objects.equals(goodId, good.goodId)) { return false; } - return Objects.equals(name, good.name); + if (!Objects.equals(name, good.name)) { + return false; + } + return Objects.equals(price, good.price); } @Override public int hashCode() { - int result; - long temp; - result = goodId != null ? goodId.hashCode() : 0; + int result = goodId != null ? goodId.hashCode() : 0; result = 31 * result + (name != null ? name.hashCode() : 0); - temp = Double.doubleToLongBits(price); - result = 31 * result + (int) (temp ^ (temp >>> 32)); + result = 31 * result + (price != null ? price.hashCode() : 0); return result; } diff --git a/mybatis-flex-test/mybatis-flex-spring-boot-test/src/test/java/com/mybatisflex/test/mapper/ActiveRecordTest.java b/mybatis-flex-test/mybatis-flex-spring-boot-test/src/test/java/com/mybatisflex/test/mapper/ActiveRecordTest.java index 17bd1675..61d45f33 100644 --- a/mybatis-flex-test/mybatis-flex-spring-boot-test/src/test/java/com/mybatisflex/test/mapper/ActiveRecordTest.java +++ b/mybatis-flex-test/mybatis-flex-spring-boot-test/src/test/java/com/mybatisflex/test/mapper/ActiveRecordTest.java @@ -19,9 +19,12 @@ package com.mybatisflex.test.mapper; import com.mybatisflex.core.mybatis.Mappers; import com.mybatisflex.core.query.QueryWrapper; import com.mybatisflex.test.model.Good; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import static com.mybatisflex.test.model.table.GoodTableDef.GOOD; + /** * @author 王帅 * @since 2023-07-23 @@ -31,12 +34,75 @@ class ActiveRecordTest { @Test void testMapper() { - Good good = new Good(); - good.setPrice(28); + Good good = Good.create(); - GoodMapper goodMapper = Mappers.ofMapperClass(GoodMapper.class); + good.setPrice(28.0); + + GoodMapper goodMapper = (GoodMapper) Mappers.ofEntityClass(Good.class); goodMapper.selectListByQuery(QueryWrapper.create(good)); } + @Test + void testInsert() { + boolean saved = Good.create() + .setPrice(28.0) + .setName("摆渡人") + .save(); + + Assertions.assertTrue(saved); + } + + @Test + void testUpdate() { + Good.create() + .setGoodId(11) + .setPrice(38.0) + .updateById(); + } + + @Test + void testDelete() { + boolean removed = Good.create() + .setGoodId(1) + .removeById(); + + Assertions.assertTrue(removed); + } + + @Test + void testSelectById() { + Good good = Good.create() + .setGoodId(11) + .oneById(); + + System.out.println(good); + } + + @Test + void testSelectOne() { + Good good1 = Good.create() + .setName("摆渡人") + .one(); + + Good good2 = Good.create() + .where(GOOD.NAME.eq("摆渡人")) + .one(); + + Good good3 = Good.create() + .where(Good::getName).eq("摆渡人") + .one(); + + Assertions.assertEquals(good1, good2); + Assertions.assertEquals(good1, good3); + } + + @Test + void testSelectList() { + Good.create() + .where(GOOD.PRICE.ge(28.0)) + .list() + .forEach(System.out::println); + } + }