diff --git a/docs/zh/base/active-record.md b/docs/zh/base/active-record.md index 7ae33962..828bef07 100644 --- a/docs/zh/base/active-record.md +++ b/docs/zh/base/active-record.md @@ -136,3 +136,26 @@ Account.create() .where(Account::getAge).ge(18) .page(Page.of(1,10)); ``` + +## 多表关联 + +`Model` 提供了 `joins` 与 `@Relation` 两种方式实现多表关联查询,例如:用户与角色的关系: + +- 通过 [joins](./relations-query.md#方案-3join-query) 联表方式查询数据: + +```java +User.create() + .select(USER.ALL_COLUMNS,ROLE.ALL_COLUMNS) + .leftJoin(USER_ROLE).as("ur").on(USER_ROLE.USER_ID.eq(USER.USER_ID)) + .leftJoin(ROLE).as("r").on(USER_ROLE.ROLE_ID.eq(ROLE.ROLE_ID)) + .where(USER.USER_ID.eq(2)) + .one(); +``` + +- 通过 [@Relation](./relations-query.md#方案-1relations-注解) 相关注解查询数据: + +```java +User.create() + .where(USER.USER_ID.eq(2)) + .oneWithRelations(); +``` 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 index 1b4e7431..22e04711 100644 --- 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 @@ -56,7 +56,7 @@ public interface MapperModel { * * @return 主键数据数组 */ - default Object[] getPkValues() { + default Object[] pkValues() { TableInfo tableInfo = TableInfoFactory.ofEntityClass(getClass()); return tableInfo.buildPkSqlArgs(this); } @@ -107,7 +107,7 @@ public interface MapperModel { * @return {@code true} 删除成功,{@code false} 删除失败 */ default boolean removeById() { - return SqlUtil.toBool(baseMapper().deleteById(getPkValues())); + return SqlUtil.toBool(baseMapper().deleteById(pkValues())); } /** @@ -135,7 +135,7 @@ public interface MapperModel { * @return 数据 */ default T oneById() { - return baseMapper().selectOneById(getPkValues()); + return baseMapper().selectOneById(pkValues()); } /** @@ -147,4 +147,22 @@ public interface MapperModel { return Optional.ofNullable(oneById()); } + /** + * 根据实体类主键获取一条数据,并查询 {@code @Relation} 注解关联的内容。 + * + * @return 数据 + */ + default T oneWithRelationsById() { + return baseMapper().selectOneWithRelationsById(pkValues()); + } + + /** + * 根据实体类主键获取一条数据,并查询 {@code @Relation} 注解关联的内容,封装为 {@link Optional} 返回。 + * + * @return 数据 + */ + default Optional oneWithRelationsByIdOpt() { + return Optional.ofNullable(oneWithRelationsById()); + } + } 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 index d0d17efb..b8a4e26a 100644 --- 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 @@ -87,7 +87,16 @@ public abstract class Model> * @return 数据 */ public T one() { - return baseMapper().selectOneByQuery(getQueryWrapper().limit(1)); + return baseMapper().selectOneByQuery(getQueryWrapper()); + } + + /** + * 根据实体类构建的条件获取一条数据,并查询 {@code @Relation} 注解关联的内容。 + * + * @return 数据 + */ + public T oneWithRelations() { + return baseMapper().selectOneWithRelationsByQuery(getQueryWrapper().limit(1)); } /** @@ -99,6 +108,15 @@ public abstract class Model> return Optional.ofNullable(one()); } + /** + * 根据实体类构建的条件获取一条数据,并查询 {@code @Relation} 注解关联的内容,封装为 {@link Optional} 返回。 + * + * @return 数据 + */ + public Optional oneWithRelationsOpt() { + return Optional.ofNullable(oneWithRelations()); + } + /** * 根据实体类构建的条件获取多条数据。 * @@ -108,6 +126,15 @@ public abstract class Model> return baseMapper().selectListByQuery(getQueryWrapper()); } + /** + * 根据实体类构建的条件获取多条数据,并查询 {@code @Relation} 注解关联的内容。 + * + * @return 数据列表 + */ + public List listWithRelations() { + return baseMapper().selectListWithRelationsByQuery(getQueryWrapper()); + } + /** * 根据实体类构建的条件获取分页数据。 * @@ -118,4 +145,14 @@ public abstract class Model> return baseMapper().paginate(page, getQueryWrapper()); } + /** + * 根据实体类构建的条件获取分页数据,并查询 {@code @Relation} 注解关联的内容。 + * + * @param page 分页对象 + * @return 分页数据 + */ + public Page pageWithRelations(Page page) { + return baseMapper().paginateWithRelations(page, getQueryWrapper()); + } + } diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/query/JoinBuilder.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/query/JoinBuilder.java new file mode 100644 index 00000000..862c2795 --- /dev/null +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/activerecord/query/JoinBuilder.java @@ -0,0 +1,55 @@ +/* + * 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.Join; +import com.mybatisflex.core.query.QueryCondition; +import com.mybatisflex.core.query.RawFragment; + +/** + * Lambda joins 构建器。 + * + * @author 王帅 + * @since 2023-07-28 + */ +public class JoinBuilder> { + + private final R queryModel; + private final Join join; + + public JoinBuilder(R queryModel, Join join) { + this.queryModel = queryModel; + this.join = join; + } + + public JoinBuilder as(String alias) { + CPI.getJoinQueryTable(join).as(alias); + return this; + } + + public R on(String on) { + join.on(new RawFragment(on)); + return queryModel; + } + + public R on(QueryCondition on) { + join.on(on); + return queryModel; + } + +} 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 index ece914c4..44721101 100644 --- 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 @@ -17,7 +17,11 @@ package com.mybatisflex.core.activerecord.query; import com.mybatisflex.annotation.Column; +import com.mybatisflex.core.constant.SqlConsts; import com.mybatisflex.core.query.*; +import com.mybatisflex.core.table.TableDef; +import com.mybatisflex.core.table.TableInfo; +import com.mybatisflex.core.table.TableInfoFactory; import com.mybatisflex.core.util.LambdaGetter; import com.mybatisflex.core.util.LambdaUtil; @@ -126,6 +130,198 @@ public abstract class QueryModel> { return new WhereBuilder<>((T) this, LambdaUtil.getQueryColumn(column), SqlConnector.OR); } + public JoinBuilder leftJoin(String table) { + return joins(SqlConsts.LEFT_JOIN, new QueryTable(table), true); + } + + public JoinBuilder leftJoin(String table, boolean when) { + return joins(SqlConsts.LEFT_JOIN, new QueryTable(table), when); + } + + public JoinBuilder leftJoin(Class entityClass) { + return joins(SqlConsts.LEFT_JOIN, entityClass, true); + } + + public JoinBuilder leftJoin(Class entityClass, boolean when) { + return joins(SqlConsts.LEFT_JOIN, entityClass, when); + } + + public JoinBuilder leftJoin(TableDef table) { + return joins(SqlConsts.LEFT_JOIN, new QueryTable(table), true); + } + + public JoinBuilder leftJoin(TableDef table, boolean when) { + return joins(SqlConsts.LEFT_JOIN, new QueryTable(table), when); + } + + public JoinBuilder leftJoin(QueryWrapper table) { + return joins(SqlConsts.LEFT_JOIN, table, true); + } + + public JoinBuilder leftJoin(QueryWrapper table, boolean when) { + return joins(SqlConsts.LEFT_JOIN, table, when); + } + + public JoinBuilder rightJoin(String table) { + return joins(SqlConsts.RIGHT_JOIN, new QueryTable(table), true); + } + + public JoinBuilder rightJoin(String table, boolean when) { + return joins(SqlConsts.RIGHT_JOIN, new QueryTable(table), when); + } + + public JoinBuilder rightJoin(Class entityClass) { + return joins(SqlConsts.RIGHT_JOIN, entityClass, true); + } + + public JoinBuilder rightJoin(Class entityClass, boolean when) { + return joins(SqlConsts.RIGHT_JOIN, entityClass, when); + } + + public JoinBuilder rightJoin(TableDef table) { + return joins(SqlConsts.RIGHT_JOIN, new QueryTable(table), true); + } + + public JoinBuilder rightJoin(TableDef table, boolean when) { + return joins(SqlConsts.RIGHT_JOIN, new QueryTable(table), when); + } + + public JoinBuilder rightJoin(QueryWrapper table) { + return joins(SqlConsts.RIGHT_JOIN, table, true); + } + + public JoinBuilder rightJoin(QueryWrapper table, boolean when) { + return joins(SqlConsts.RIGHT_JOIN, table, when); + } + + public JoinBuilder innerJoin(String table) { + return joins(SqlConsts.INNER_JOIN, new QueryTable(table), true); + } + + public JoinBuilder innerJoin(String table, boolean when) { + return joins(SqlConsts.INNER_JOIN, new QueryTable(table), when); + } + + public JoinBuilder innerJoin(Class entityClass) { + return joins(SqlConsts.INNER_JOIN, entityClass, true); + } + + public JoinBuilder innerJoin(Class entityClass, boolean when) { + return joins(SqlConsts.INNER_JOIN, entityClass, when); + } + + public JoinBuilder innerJoin(TableDef table) { + return innerJoin(table, true); + } + + public JoinBuilder innerJoin(TableDef table, boolean when) { + return joins(SqlConsts.INNER_JOIN, new QueryTable(table), when); + } + + public JoinBuilder innerJoin(QueryWrapper table) { + return joins(SqlConsts.INNER_JOIN, table, true); + } + + public JoinBuilder innerJoin(QueryWrapper table, boolean when) { + return joins(SqlConsts.INNER_JOIN, table, when); + } + + public JoinBuilder fullJoin(String table) { + return joins(SqlConsts.FULL_JOIN, new QueryTable(table), true); + } + + public JoinBuilder fullJoin(String table, boolean when) { + return joins(SqlConsts.FULL_JOIN, new QueryTable(table), when); + } + + public JoinBuilder fullJoin(Class entityClass) { + return joins(SqlConsts.FULL_JOIN, entityClass, true); + } + + public JoinBuilder fullJoin(Class entityClass, boolean when) { + return joins(SqlConsts.FULL_JOIN, entityClass, when); + } + + public JoinBuilder fullJoin(TableDef table) { + return joins(SqlConsts.FULL_JOIN, new QueryTable(table), true); + } + + public JoinBuilder fullJoin(TableDef table, boolean when) { + return joins(SqlConsts.FULL_JOIN, new QueryTable(table), when); + } + + public JoinBuilder fullJoin(QueryWrapper table) { + return joins(SqlConsts.FULL_JOIN, table, true); + } + + public JoinBuilder fullJoin(QueryWrapper table, boolean when) { + return joins(SqlConsts.FULL_JOIN, table, when); + } + + public JoinBuilder crossJoin(String table) { + return joins(SqlConsts.CROSS_JOIN, new QueryTable(table), true); + } + + public JoinBuilder crossJoin(String table, boolean when) { + return joins(SqlConsts.CROSS_JOIN, new QueryTable(table), when); + } + + public JoinBuilder crossJoin(Class entityClass) { + return joins(SqlConsts.CROSS_JOIN, entityClass, true); + } + + public JoinBuilder crossJoin(Class entityClass, boolean when) { + return joins(SqlConsts.CROSS_JOIN, entityClass, when); + } + + public JoinBuilder crossJoin(TableDef table) { + return joins(SqlConsts.CROSS_JOIN, new QueryTable(table), true); + } + + public JoinBuilder crossJoin(TableDef table, boolean when) { + return joins(SqlConsts.CROSS_JOIN, new QueryTable(table), when); + } + + public JoinBuilder crossJoin(QueryWrapper table) { + return joins(SqlConsts.CROSS_JOIN, table, true); + } + + public JoinBuilder crossJoin(QueryWrapper table, boolean when) { + return joins(SqlConsts.CROSS_JOIN, table, when); + } + + public JoinBuilder join(String table) { + return joins(SqlConsts.JOIN, new QueryTable(table), true); + } + + public JoinBuilder join(String table, boolean when) { + return joins(SqlConsts.JOIN, new QueryTable(table), when); + } + + public JoinBuilder join(Class entityClass) { + return joins(SqlConsts.JOIN, entityClass, true); + } + + public JoinBuilder join(Class entityClass, boolean when) { + return joins(SqlConsts.JOIN, entityClass, when); + } + + public JoinBuilder join(TableDef table) { + return joins(SqlConsts.JOIN, new QueryTable(table), true); + } + + public JoinBuilder join(TableDef table, boolean when) { + return joins(SqlConsts.JOIN, new QueryTable(table), when); + } + + public JoinBuilder join(QueryWrapper table) { + return joins(SqlConsts.JOIN, table, true); + } + + public JoinBuilder join(QueryWrapper table, boolean when) { + return joins(SqlConsts.JOIN, table, when); + } + public T groupBy(String... names) { getQueryWrapper().groupBy(names); return (T) this; @@ -175,4 +371,22 @@ public abstract class QueryModel> { return (T) this; } + protected JoinBuilder joins(String type, QueryTable table, boolean when) { + Join join = new Join(type, table, when); + CPI.addJoin(getQueryWrapper(), join); + return new JoinBuilder<>((T) this, join); + } + + protected JoinBuilder joins(String type, Class entityClass, boolean when) { + TableInfo tableInfo = TableInfoFactory.ofEntityClass(entityClass); + QueryTable queryTable = new QueryTable(tableInfo.getSchema(), tableInfo.getTableName()); + return joins(type, queryTable, when); + } + + protected JoinBuilder joins(String type, QueryWrapper queryWrapper, boolean when) { + Join join = new Join(type, queryWrapper, when); + CPI.addJoin(getQueryWrapper(), join); + return new JoinBuilder<>((T) this, join); + } + } 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 7875bb95..3a8a3ba7 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 @@ -120,6 +120,11 @@ public class CPI { queryWrapper.setJoinTables(joinTables); } + public static void addJoin(QueryWrapper queryWrapper, Join join) { + queryWrapper.addJoinTable(join.getQueryTable()); + queryWrapper.addJoin(join); + } + public static QueryCondition getWhereQueryCondition(QueryWrapper queryWrapper) { return queryWrapper.getWhereQueryCondition(); diff --git a/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/model/Role.java b/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/model/Role.java index 04a5fc7f..6a7b0825 100644 --- a/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/model/Role.java +++ b/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/model/Role.java @@ -16,6 +16,7 @@ package com.mybatisflex.test.model; +import com.mybatisflex.annotation.Column; import com.mybatisflex.annotation.Id; import com.mybatisflex.annotation.Table; @@ -36,6 +37,7 @@ public class Role implements Comparable { private String roleKey; private String roleName; + @Column(ignore = true) private List userVOS; public Integer getRoleId() { diff --git a/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/model/User.java b/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/model/User.java index 13b89f01..41d318df 100644 --- a/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/model/User.java +++ b/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/model/User.java @@ -17,7 +17,9 @@ package com.mybatisflex.test.model; import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.RelationManyToMany; import com.mybatisflex.annotation.Table; +import com.mybatisflex.core.activerecord.Model; import java.util.List; @@ -29,15 +31,26 @@ import java.util.List; */ @Table("tb_user") -public class User { +public class User extends Model { @Id private Integer userId; private String userName; private String password; - //@Column(ignore = true) + + @RelationManyToMany( + selfField = "userId", + targetField = "roleId", + joinTable = "tb_user_role", + joinSelfColumn = "user_id", + joinTargetColumn = "role_id" + ) private List roleList; + public static User create() { + return new User(); + } + public Integer getUserId() { return userId; } 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 61d45f33..5b892129 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,11 +19,15 @@ package com.mybatisflex.test.mapper; import com.mybatisflex.core.mybatis.Mappers; import com.mybatisflex.core.query.QueryWrapper; import com.mybatisflex.test.model.Good; +import com.mybatisflex.test.model.User; 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; +import static com.mybatisflex.test.model.table.RoleTableDef.ROLE; +import static com.mybatisflex.test.model.table.UserRoleTableDef.USER_ROLE; +import static com.mybatisflex.test.model.table.UserTableDef.USER; /** * @author 王帅 @@ -105,4 +109,19 @@ class ActiveRecordTest { .forEach(System.out::println); } + @Test + void testRelation() { + User user1 = User.create().select(USER.ALL_COLUMNS, ROLE.ALL_COLUMNS) + .leftJoin(USER_ROLE).as("ur").on(USER_ROLE.USER_ID.eq(USER.USER_ID)) + .leftJoin(ROLE).as("r").on(USER_ROLE.ROLE_ID.eq(ROLE.ROLE_ID)) + .where(USER.USER_ID.eq(2)) + .one(); + + User user2 = User.create() + .where(USER.USER_ID.eq(2)) + .oneWithRelations(); + + Assertions.assertEquals(user1.toString(), user2.toString()); + } + }