From 97d0c4d409a667052fef6a637ad287c6692d5937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=80=E6=BA=90=E6=B5=B7=E5=93=A5?= Date: Thu, 6 Jul 2023 19:10:24 +0800 Subject: [PATCH] update docs --- docs/.vitepress/config.ts | 2 +- docs/zh/base/field-query.md | 190 --------- docs/zh/base/relations-query.md | 379 ++++++++++++++++++ .../test/relation/onetoone/Account.java | 6 +- .../relation/onetoone/RelationsTester.java | 4 +- 5 files changed, 383 insertions(+), 198 deletions(-) delete mode 100644 docs/zh/base/field-query.md create mode 100644 docs/zh/base/relations-query.md diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index cc7ad175..7792b657 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -62,7 +62,7 @@ export default defineConfig({ items: [ {text: '增、删、改', link: '/zh/base/add-delete-update'}, {text: '查询(多表和分页)', link: '/zh/base/query'}, - {text: '一对多、多对一', link: '/zh/base/field-query'}, + {text: '一对多、多对一', link: '/zh/base/relations-query'}, {text: '批量操作', link: '/zh/base/batch'}, {text: 'QueryWrapper', link: '/zh/base/querywrapper'}, {text: 'Db + Row', link: '/zh/base/db-row'}, diff --git a/docs/zh/base/field-query.md b/docs/zh/base/field-query.md deleted file mode 100644 index 2bc2cb87..00000000 --- a/docs/zh/base/field-query.md +++ /dev/null @@ -1,190 +0,0 @@ -# 一对多、多对一 - -在很多场景下,我们可能会用到 `一对多`、`一对一`、`多对一`、`多对多`等场景的关联查询,MyBatis-Flex 内置了相关的方法,用于支持此类场景。 - -## Field Query 代码示例 - -以下是文章的示例,一篇文章可能归属于多个分类,一个类可以有多篇文章,需要用到中间表 `article_category_mapping`。 - -```java -public class Article { - private Long id; - private String title; - private String content; - - //文章的归属分类,可能是 1 个或者多个 - private List categories; - - //getter setter -} -``` - -查询代码如下: - -```java {9-13} -QueryWrapper queryWrapper = QueryWrapper.create() - .select().from(ARTICLE) - .where(ARTICLE.id.ge(100)); - -List
articles = mapper.selectListByQuery(queryWrapper - , fieldQueryBuilder -> fieldQueryBuilder - .field(Article::getCategories) // 或者 .field("categories") - .queryWrapper(article -> QueryWrapper.create() - .select().from(CATEGORY) - .where(CATEGORY.id.in( - select("category_id").from("article_category_mapping") - .where("article_id = ?", article.getId()) - ) - ) - ); -``` - -通过以上代码可以看出,`Article.categories` 字段的结果,来源于 `queryWrapper()` 方法构建的 `QueryWrapper`。 - -其原理是:MyBatis-Flex 的内部逻辑是先查询出 `Article` 的数据,然后再根据 `Article` 构建出新的 SQL,查询分类,并赋值给 `Article.categories` 属性, -假设 `Article` 有 10 条数据,那么最终会进行 11 次数据库查询。 - -查询的 SQL 大概如下: - -```sql -select * from tb_article where id >= 100; - --- 以上 SQL 得到结果后,再执行查询分类的 SQL,如下: -select * from tb_category where id in -(select category_id from article_category_mapping where article_id = 100); - -select * from tb_category where id in -(select category_id from article_category_mapping where article_id = 101); - -select * from tb_category where id in -(select category_id from article_category_mapping where article_id = 102); - -select * from tb_category where id in -(select category_id from article_category_mapping where article_id = 103); - -select * from tb_category where id in -(select category_id from article_category_mapping where article_id = 104); - -select * from tb_category where id in -(select category_id from article_category_mapping where article_id = 105); - -select * from tb_category where id in -(select category_id from article_category_mapping where article_id = 106); - -select * from tb_category where id in -(select category_id from article_category_mapping where article_id = 107); - -select * from tb_category where id in -(select category_id from article_category_mapping where article_id = 108); - -select * from tb_category where id in -(select category_id from article_category_mapping where article_id = 109); -``` - -## 知识点 -MyBatis-Flex 的关联子查询,和 JPA 等其他第三方框架有很大的差异,比如 JPA 是通过配置来构建查询 SQL,其构建生成的 SQL 对于用户来说是不透明的。 -因此,用户几乎无法对 JPA 的注解生成 SQL 优化。 - -而 MyBatis-Flex 关联子查询的 SQL 完全是由用户构建的,因此会更加灵活,更加有利于我们进行 SQL 优化。在子查询中,有很多的场景, JPA 对一对一、 -一对多等等不同的场景给出了不同的注解、以及参数,导致用户的学习成本非常高。 - -对 MyBatis-Flex 来说,学习成本是非常低的,在构建子查询时,只需要明白为哪个字段、通过什么样的 SQL 查询就可以了,以下是示例: - -```java 3,4 -List
articles = mapper.selectListByQuery(query - , fieldQueryBuilder -> fieldQueryBuilder - .field(...) // 为哪个字段查询的? - .queryWrapper(...) // 通过什么样的 SQL 查询的? - ); -``` - -因此,在 MyBatis-Flex 的设计中,无论是一对多、多对一、多对多... 还是其他任何一种场景,其逻辑都是一样的。 - - -## 更多场景 - -通过以上内容看出,`Article` 的任何属性,都是可以通过传入 `FieldQueryBuilder` 来构建 `QueryWrapper` 进行再次查询, -这些不仅仅只适用于 `一对多`、`一对一`、`多对一`、`多对多`等场景。任何 `Article` 对象里的属性,需要二次查询赋值的,都是可以通过这种方式进行。 - - -## Join Query - -Join Query 是用于构建原生 MyBatis 如下的 ``: - -```xml - - - - - - - - - -``` - -以上的 xml 是原生的 MyBatis 示例,在 MyBatis-Flex 我们不需要编写 xml 的 ``,这个过程由 MyBatis-Flex 自动完成, -我们只需要关注 MyBatis-Flex 的 SQL 构建即可。 - -## Join Query 代码示例 - -这里以 **用户** 和 **角色** 的 `多对多` 关系作为例子,用户表和角色表,分别对应着用户类和角色类: - -```java -@Table("sys_user") -public class User { - @Id - private Integer userId; - private String userName; -} - - -@Table("sys_role") -public class Role { - @Id - private Integer roleId; - private String roleKey; - private String roleName; -} -``` - -现在需要查询所有用户,以及每个用户对应的角色信息,并通过 UserVO 对象返回: - -```java -public class UserVO { - private String userId; - private String userName; - private List roleList; -} -``` - -这个操作只需要联表查询即可完成,对于联表查询的结果映射,MyBatis-Flex 会自动帮您完成: - -```java -QueryWrapper queryWrapper = QueryWrapper.create() - .select(USER.USER_ID, USER.USER_NAME, ROLE.ALL_COLUMNS) - .from(USER.as("u")) - .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)); -List userVOS = userMapper.selectListByQueryAs(queryWrapper, UserVO.class); -userVOS.forEach(System.err::println); -``` - -构建的联表查询 SQL 语句为: - -```sql -SELECT `u`.`user_id`, - `u`.`user_name`, - `r`.* -FROM `sys_user` AS `u` -LEFT JOIN `sys_user_role` AS `ur` ON `ur`.`user_id` = `u`.`user_id` -LEFT JOIN `sys_role` AS `r` ON `ur`.`role_id` = `r`.`role_id`; -``` - -最终自动映射的结果为: - -```txt -UserVO{userId='1', userName='admin', roleList=[Role{roleId=1, roleKey='admin', roleName='超级管理员'}]} -UserVO{userId='2', userName='ry', roleList=[Role{roleId=2, roleKey='common', roleName='普通角色'}]} -UserVO{userId='3', userName='test', roleList=[Role{roleId=1, roleKey='admin', roleName='超级管理员'}, Role{roleId=2, roleKey='common', roleName='普通角色'}]} -``` diff --git a/docs/zh/base/relations-query.md b/docs/zh/base/relations-query.md new file mode 100644 index 00000000..a039537e --- /dev/null +++ b/docs/zh/base/relations-query.md @@ -0,0 +1,379 @@ +# 一对多、多对一 + +在 MyBatis-Flex 中,我们内置了 3 种方式,帮助用户进行关联查询,比如 `一对多`、`一对一`、`多对一`、`多对多`等场景,他们分别是: + +- Relations 注解 +- Field Query +- Join Query + +## Relations 注解 + +在 MyBatis-Flex 中,提供了 4 个 Relations 注解,他们分别是: + +- **RelationOneToOne**:用于一对一的场景 +- **RelationOneToMany**:用于一对多的场景 +- **RelationManyToOne**:用于多对一的场景 +- **RelationManyToMany**:用于多对多的场景 + +添加了以上配置的实体类,在通过 `BaseMapper` 的方法查询数据时,需要调用 select*****WithRelations**() 方法,Relations 注解才能生效。 +否则 MyBatis-Flex 自动忽略 Relations 注解。 + +## 一对一 `@RelationOneToOne` + +假设有一个账户,账户有身份证,账户和身份证的关系是一对一的关系,代码如下所示: + +Account.java : +```java 8 +public class Account implements Serializable { + + @Id(keyType = KeyType.Auto) + private Long id; + + private String userName; + + @RelationOneToOne(selfField = "id", targetField = "accountId") + private IDCard idCard; + + //getter setter +} +``` + +IDCard.java : + +```java +@Table(value = "tb_idcard") +public class IDCard implements Serializable { + + private Long accountId; + private String cardNo; + private String content; + + //getter setter +} +``` + +`@RelationOneToOne` 配置描述: + +- selfField 当前实体类的属性 +- targetField 目标对象的关系实体类的属性 + + +## 一对多 `@RelationOneToMany` + +假设一个账户有很多本书籍,一本书只能归属一个账户所有;账户和书籍的关系是一对多的关系,代码如下: + +Account.java : +```java 8 +public class Account implements Serializable { + + @Id(keyType = KeyType.Auto) + private Long id; + + private String userName; + + @RelationOneToMany(selfField = "id", targetField = "accountId") + private List books; + + //getter setter +} +``` + +Book.java : + +```java +@Table(value = "tb_book") +public class Book implements Serializable { + + @Id(keyType = KeyType.Auto) + private Long id; + private Long accountId; + private String title; + + //getter setter +} +``` + +`@RelationOneToMany` 配置描述: + +- selfField 当前实体类的属性 +- targetField 目标对象的关系实体类的属性 + + + +## 多对一 `@RelationManyToOne` + +假设一个账户有很多本书籍,一本书只能归属一个账户所有;账户和书籍的关系是一对多的关系,书籍和账户的关系为多对一的关系,代码如下: + +Account.java 一对多的配置: +```java 8 +public class Account implements Serializable { + + @Id(keyType = KeyType.Auto) + private Long id; + + private String userName; + + @RelationOneToMany(selfField = "id", targetField = "accountId") + private List books; + + //getter setter +} +``` + +Book.java 多对一的配置: + +```java 9 +@Table(value = "tb_book") +public class Book implements Serializable { + + @Id(keyType = KeyType.Auto) + private Long id; + private Long accountId; + private String title; + + @RelationManyToOne(selfField = "accountId", targetField = "id") + private Account account; + + //getter setter +} +``` + +`@RelationOneToMany` 和 `@RelationManyToOne` 配置描述: + +- selfField 当前实体类的属性 +- targetField 目标对象的关系实体类的属性 + + + +## 多对多 `@RelationManyToOne` + +假设一个账户可以有多个角色,一个角色也可以有多个账户,他们是多对多的关系,需要通过中间件表 `tb_role_mapping` 来维护: + +`tb_role_mapping` 的表结构如下: + +```sql +CREATE TABLE `tb_role_mapping` +( + `account_id` INTEGER , + `role_id` INTEGER +); +``` + +Account.java 多对多的配置: +```java {7-11} +public class Account implements Serializable { + + @Id(keyType = KeyType.Auto) + private Long id; + private String userName; + + @RelationManyToMany( + joinTable = "tb_role_mapping", // 中间表 + selfField = "id", joinSelfColumn = "account_id", + targetField = "id", joinTargetColumn = "role_id" + ) + private List roles; + + //getter setter +} +``` + +Role.java 多对多的配置: + +```java {7-11} +@Table(value = "tb_role") +public class Role implements Serializable { + + private Long id; + private String name; + + @RelationManyToMany( + joinTable = "tb_role_mapping", + selfField = "id", joinSelfColumn = "role_id", + targetField = "id", joinTargetColumn = "account_id" + ) + private List accounts; + + //getter setter +} +``` + +`@RelationManyToMany` 配置描述: + +- selfField 当前实体类的属性 +- targetField 目标对象的关系实体类的属性 +- joinTable 中间表 +- joinSelfColumn 当前表和中间表的关系字段 +- joinTargetColumn 目标表和中间表的关系字段 + +> 注意:selfField 和 targetField 配置的是类的属性名,joinSelfColumn 和 joinTargetColumn 配置的是中间表的字段名。 + + + +## Field Query + +以下是文章的 `多对多` 示例,一篇文章可能归属于多个分类,一个分类可能有多篇文章,需要用到中间表 `article_category_mapping`。 + +```java +public class Article { + private Long id; + private String title; + private String content; + + //文章的归属分类,可能是 1 个或者多个 + private List categories; + + //getter setter +} +``` + +查询代码如下: + +```java {9-13} +QueryWrapper queryWrapper = QueryWrapper.create() + .select().from(ARTICLE) + .where(ARTICLE.id.ge(100)); + +List
articles = mapper.selectListByQuery(queryWrapper + , fieldQueryBuilder -> fieldQueryBuilder + .field(Article::getCategories) // 或者 .field("categories") + .queryWrapper(article -> QueryWrapper.create() + .select().from(CATEGORY) + .where(CATEGORY.id.in( + select("category_id").from("article_category_mapping") + .where("article_id = ?", article.getId()) + ) + ) + ); +``` + +通过以上代码可以看出,`Article.categories` 字段的结果,来源于 `queryWrapper()` 方法构建的 `QueryWrapper`。 + +其原理是:MyBatis-Flex 的内部逻辑是先查询出 `Article` 的数据,然后再根据 `Article` 构建出新的 SQL,查询分类,并赋值给 `Article.categories` 属性, +假设 `Article` 有 10 条数据,那么最终会进行 11 次数据库查询。 + +查询的 SQL 大概如下: + +```sql +select * from tb_article where id >= 100; + +-- 以上 SQL 得到结果后,再执行查询分类的 SQL,如下: +select * from tb_category where id in +(select category_id from article_category_mapping where article_id = 100); + +select * from tb_category where id in +(select category_id from article_category_mapping where article_id = 101); + +select * from tb_category where id in +(select category_id from article_category_mapping where article_id = 102); + +select * from tb_category where id in +(select category_id from article_category_mapping where article_id = 103); + +select * from tb_category where id in +(select category_id from article_category_mapping where article_id = 104); + +select * from tb_category where id in +(select category_id from article_category_mapping where article_id = 105); + +select * from tb_category where id in +(select category_id from article_category_mapping where article_id = 106); + +select * from tb_category where id in +(select category_id from article_category_mapping where article_id = 107); + +select * from tb_category where id in +(select category_id from article_category_mapping where article_id = 108); + +select * from tb_category where id in +(select category_id from article_category_mapping where article_id = 109); +``` + +## Field Query 知识点 + +相对 `Relations 注解` ,Field Query 的学习成本是非常低的,在构建子查询时,只需要明白为哪个字段、通过什么样的 SQL 查询就可以了,以下是示例: + +```java 3,4 +List
articles = mapper.selectListByQuery(query + , fieldQueryBuilder -> fieldQueryBuilder + .field(...) // 为哪个字段查询的? + .queryWrapper(...) // 通过什么样的 SQL 查询的? + ); +``` + +因此,在 MyBatis-Flex 的设计中,无论是一对多、多对一、多对多... 还是其他任何一种场景,其逻辑都是一样的。 + + +## Field Query 更多场景 + +通过以上内容看出,`Article` 的任何属性,都是可以通过传入 `FieldQueryBuilder` 来构建 `QueryWrapper` 进行再次查询, +这些不仅仅只适用于 `一对多`、`一对一`、`多对一`、`多对多`等场景。任何 `Article` 对象里的属性,需要二次查询赋值的,都是可以通过这种方式进行,比如一些统计的场景。 + + +## Join Query + +Join Query 是通过 QueryWrapper 构建 `Left Join` 等方式进行查询,其原理是 MyBatis-Flex 自动构建了 MyBatis 的 `` +,我们只需要关注 MyBatis-Flex 的 SQL 构建即可。 + +## Join Query 代码示例 + +这里以 **用户** 和 **角色** 的 `多对多` 关系作为例子,用户表和角色表,分别对应着用户类和角色类: + +```java +@Table("sys_user") +public class User { + @Id + private Integer userId; + private String userName; +} + + +@Table("sys_role") +public class Role { + @Id + private Integer roleId; + private String roleKey; + private String roleName; +} +``` + +现在需要查询所有用户,以及每个用户对应的角色信息,并通过 UserVO 对象返回: + +```java +public class UserVO { + private String userId; + private String userName; + private List roleList; +} +``` + +这个操作只需要联表查询即可完成,对于联表查询的结果映射,MyBatis-Flex 会自动帮您完成: + +```java +QueryWrapper queryWrapper = QueryWrapper.create() + .select(USER.USER_ID, USER.USER_NAME, ROLE.ALL_COLUMNS) + .from(USER.as("u")) + .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)); +List userVOS = userMapper.selectListByQueryAs(queryWrapper, UserVO.class); +userVOS.forEach(System.err::println); +``` + +构建的联表查询 SQL 语句为: + +```sql +SELECT `u`.`user_id`, + `u`.`user_name`, + `r`.* +FROM `sys_user` AS `u` +LEFT JOIN `sys_user_role` AS `ur` ON `ur`.`user_id` = `u`.`user_id` +LEFT JOIN `sys_role` AS `r` ON `ur`.`role_id` = `r`.`role_id`; +``` + +最终自动映射的结果为: + +```txt +UserVO{userId='1', userName='admin', roleList=[Role{roleId=1, roleKey='admin', roleName='超级管理员'}]} +UserVO{userId='2', userName='ry', roleList=[Role{roleId=2, roleKey='common', roleName='普通角色'}]} +UserVO{userId='3', userName='test', roleList=[Role{roleId=1, roleKey='admin', roleName='超级管理员'}, Role{roleId=2, roleKey='common', roleName='普通角色'}]} +``` diff --git a/mybatis-flex-test/mybatis-flex-native-test/src/main/java/com/mybatisflex/test/relation/onetoone/Account.java b/mybatis-flex-test/mybatis-flex-native-test/src/main/java/com/mybatisflex/test/relation/onetoone/Account.java index 9aa5fbdb..9fad5a5a 100644 --- a/mybatis-flex-test/mybatis-flex-native-test/src/main/java/com/mybatisflex/test/relation/onetoone/Account.java +++ b/mybatis-flex-test/mybatis-flex-native-test/src/main/java/com/mybatisflex/test/relation/onetoone/Account.java @@ -24,8 +24,6 @@ import java.util.List; @Table(value = "tb_account") public class Account implements Serializable { - private static final long serialVersionUID = 1L; - @Id(keyType = KeyType.Auto) private Long id; @@ -33,10 +31,10 @@ public class Account implements Serializable { private int age; -// @RelationOneToOne(selfField = "id", targetField = "accountId") + @RelationOneToOne(selfField = "id", targetField = "accountId") private IDCard idCard; -// @RelationOneToMany(selfField = "id", targetField = "accountId") + @RelationOneToMany(selfField = "id", targetField = "accountId") private List books; @RelationManyToMany( diff --git a/mybatis-flex-test/mybatis-flex-native-test/src/main/java/com/mybatisflex/test/relation/onetoone/RelationsTester.java b/mybatis-flex-test/mybatis-flex-native-test/src/main/java/com/mybatisflex/test/relation/onetoone/RelationsTester.java index 4ad946ac..615af0bd 100644 --- a/mybatis-flex-test/mybatis-flex-native-test/src/main/java/com/mybatisflex/test/relation/onetoone/RelationsTester.java +++ b/mybatis-flex-test/mybatis-flex-native-test/src/main/java/com/mybatisflex/test/relation/onetoone/RelationsTester.java @@ -64,10 +64,8 @@ public class RelationsTester { @Test public void testOneToOne() { - List accounts = accountMapper.selectAll(); + List accounts = accountMapper.selectAllWithRelations(); System.out.println(">>>>>>1: " + accounts); - RelationManager.queryRelations(accountMapper, accounts); - System.out.println(">>>>>>2: " + accounts); }