diff --git a/docs/zh/base/relations-query.md b/docs/zh/base/relations-query.md index 328b4b78..bb248335 100644 --- a/docs/zh/base/relations-query.md +++ b/docs/zh/base/relations-query.md @@ -230,7 +230,128 @@ public class Account implements Serializable { > 多对多注解 `@RelationManyToMany` 也是如此。 +**splitBy 分割查询** +若 `selfField` 是一个 `由字符拼接而成的列表(如 1,2,3)`,那么,我们可以通过配置 `splitBy` 来指定使用 `selfField` 的值根据字符切割后查询, +如下代码所示: + +```java 8 +@Table(value = "tb_patient") +public class PatientVO1 implements Serializable { + private static final long serialVersionUID = -2298625009592638988L; + + /** + * ID + */ + @Id + private Integer patientId; + + /** + * 姓名 + */ + private String name; + + /** + * 所患病症(对应字符串类型) 英文逗号 分割 + */ + private String diseaseIds; + + /** + * 患者标签(对应数字类型) / 分割 + */ + private String tagIds; + + @RelationOneToMany( + selfField = "diseaseIds", + splitBy = ",", //使用 , 进行分割 + targetTable = "tb_disease", //只获取某个字段值需要填入目标表名 + targetField = "diseaseId", //测试目标字段是字符串类型是否正常转换 + valueField = "name" //测试只获取某个字段值是否正常 + ) + private List diseaseNameList; + + @RelationOneToMany( + selfField = "tagIds", + splitBy = "/", //使用 / 进行分割 + targetField = "tagId" //测试目标字段是数字类型是否正常转换 + ) + private List tagList; + + @RelationOneToMany( + selfField = "diseaseIds", + splitBy = ",", //使用 , 进行分割 + targetField = "diseaseId", //测试目标字段是字符串类型是否正常转换 + mapKeyField = "diseaseId" //测试Map映射 + ) + private Map diseaseMap; + + //getter setter toString +} +``` + +进行查询 +```java +PatientVO1 patientVO1 = patientMapper.selectOneWithRelationsByQueryAs(QueryWrapper.create().orderBy(PatientVO1::getPatientId, false).limit(1), PatientVO1.class); +System.out.println(JSON.toJSONString(patientVO1)); +``` + +其执行的 SQL 如下: + +```sql +SELECT `patient_id`, `name`, `disease_ids`, `tag_ids` FROM `tb_patient` ORDER BY `patient_id` DESC LIMIT 1; + +SELECT disease_id, name FROM `tb_disease` WHERE `disease_id` IN ('1', '2', '3', '4'); +SELECT `tag_id`, `name` FROM `tb_tag` WHERE `tag_id` IN (1, 2, 3); +SELECT `disease_id`, `name` FROM `tb_disease` WHERE `disease_id` IN ('1', '2', '3', '4'); +``` + +查询结果: +```json +{ + "patientId": 4, + "name": "赵六", + "diseaseIds": "1,2,3,4", + "tagIds": "1/2/3", + "diseaseNameList": [ + "心脑血管疾病", + "消化系统疾病", + "神经系统疾病", + "免疫系统疾病" + ], + "tagList": [ + { + "name": "VIP", + "tagId": 1 + }, + { + "name": "JAVA开发", + "tagId": 2 + }, + { + "name": "Web开发", + "tagId": 3 + } + ], + "diseaseMap": { + "1": { + "diseaseId": "1", + "name": "心脑血管疾病" + }, + "2": { + "diseaseId": "2", + "name": "消化系统疾病" + }, + "3": { + "diseaseId": "3", + "name": "神经系统疾病" + }, + "4": { + "diseaseId": "4", + "name": "免疫系统疾病" + } + } +} +``` ## 多对一 `@RelationManyToOne` diff --git a/mybatis-flex-annotation/src/main/java/com/mybatisflex/annotation/RelationOneToMany.java b/mybatis-flex-annotation/src/main/java/com/mybatisflex/annotation/RelationOneToMany.java index 35314fae..c352631b 100644 --- a/mybatis-flex-annotation/src/main/java/com/mybatisflex/annotation/RelationOneToMany.java +++ b/mybatis-flex-annotation/src/main/java/com/mybatisflex/annotation/RelationOneToMany.java @@ -34,6 +34,12 @@ public @interface RelationOneToMany { */ String selfField() default ""; + /** + * 当前字段值根据字符串分割 + * @return 分割字符串 + */ + String splitBy() default ""; + /** *

* 目标实体类对应的表的 schema 模式。 diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/relation/OneToMany.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/relation/OneToMany.java index 08e8472a..ed6fad0a 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/relation/OneToMany.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/relation/OneToMany.java @@ -35,6 +35,7 @@ class OneToMany extends ToManyRelation { , annotation.extraCondition() , annotation.selectColumns()); + this.splitBy = annotation.splitBy(); this.orderBy = annotation.orderBy(); this.limit = annotation.limit(); diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/relation/ToManyRelation.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/relation/ToManyRelation.java index 0eb6756b..607e9367 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/relation/ToManyRelation.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/relation/ToManyRelation.java @@ -29,6 +29,8 @@ class ToManyRelation extends AbstractRelation { protected FieldWrapper mapKeyFieldWrapper; protected String orderBy; protected long limit = 0; + protected String splitBy; + public ToManyRelation(String selfField, String targetSchema, String targetTable, String targetField, String valueField, @@ -42,6 +44,34 @@ class ToManyRelation extends AbstractRelation { ); } + /** + * 构建查询目标对象的 QueryWrapper + * + * @param targetValues 条件的值 + * @return QueryWrapper + */ + @Override + public QueryWrapper buildQueryWrapper(Set targetValues) { + if (StringUtil.isNotBlank(splitBy) && CollectionUtil.isNotEmpty(targetValues)) { + Set newTargetValues = new HashSet<>(); + for (Object targetValue : targetValues) { + if (targetValue == null) { + continue; + } + if (!(targetValue instanceof String)) { + throw FlexExceptions.wrap("被切割字段只支持String类型"); + } + String[] splitValues = ((String) targetValue).split(splitBy); + for (String splitValue : splitValues) { + //优化分割后的数据类型(防止在数据库查询时候出现隐式转换) + newTargetValues.add(ConvertUtil.convert(splitValue, targetFieldWrapper.getFieldType())); + } + } + targetValues = newTargetValues; + } + return super.buildQueryWrapper(targetValues); + } + @Override public void customizeQueryWrapper(QueryWrapper queryWrapper) { @@ -72,7 +102,12 @@ class ToManyRelation extends AbstractRelation { } } } else { - targetMappingValues.add((String) selfValue); + if (StringUtil.isNotBlank(splitBy)) { + String[] splitValues = ((String) selfValue).split(splitBy); + targetMappingValues.addAll(Arrays.asList(splitValues)); + } else { + targetMappingValues.add((String) selfValue); + } } if (targetMappingValues.isEmpty()) { diff --git a/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/model/Disease.java b/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/model/Disease.java new file mode 100644 index 00000000..f07d37ed --- /dev/null +++ b/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/model/Disease.java @@ -0,0 +1,45 @@ +package com.mybatisflex.test.model; + +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.annotation.Table; + +import java.io.Serializable; + +/** + * 疾病 + * + * @author Ice 2023/09/26 + * @version 1.0 + */ +@Table(value = "tb_disease") +public class Disease implements Serializable { + private static final long serialVersionUID = -3195530228167432902L; + + /** + * ID + */ + @Id(keyType = KeyType.Generator, value = "uuid") + private String diseaseId; + + /** + * 疾病名称 + */ + private String name; + + public String getDiseaseId() { + return diseaseId; + } + + public void setDiseaseId(String diseaseId) { + this.diseaseId = diseaseId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/model/Patient.java b/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/model/Patient.java new file mode 100644 index 00000000..0cdb7870 --- /dev/null +++ b/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/model/Patient.java @@ -0,0 +1,72 @@ +package com.mybatisflex.test.model; + +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.Table; +import com.mybatisflex.core.activerecord.Model; + +import java.io.Serializable; + +/** + * 患者信息 + * + * @author Ice 2023/09/26 + * @version 1.0 + */ +@Table(value = "tb_patient") +public class Patient extends Model implements Serializable { + private static final long serialVersionUID = 5117723684832788508L; + + + /** + * ID + */ + @Id + private Integer patientId; + + /** + * 姓名 + */ + private String name; + + /** + * 所患病症(对应字符串类型) 英文逗号 分割 + */ + private String diseaseIds; + + /** + * 患者标签(对应数字类型) / 分割 + */ + private String tagIds; + + public Integer getPatientId() { + return patientId; + } + + public void setPatientId(Integer patientId) { + this.patientId = patientId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDiseaseIds() { + return diseaseIds; + } + + public void setDiseaseIds(String diseaseIds) { + this.diseaseIds = diseaseIds; + } + + public String getTagIds() { + return tagIds; + } + + public void setTagIds(String tagIds) { + this.tagIds = tagIds; + } +} diff --git a/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/model/PatientVO1.java b/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/model/PatientVO1.java new file mode 100644 index 00000000..44202d31 --- /dev/null +++ b/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/model/PatientVO1.java @@ -0,0 +1,121 @@ +package com.mybatisflex.test.model; + +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.RelationOneToMany; +import com.mybatisflex.annotation.Table; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +/** + * 患者VO + * + * @author Ice 2023/09/26 + * @version 1.0 + */ +@Table(value = "tb_patient") +public class PatientVO1 implements Serializable { + private static final long serialVersionUID = -2298625009592638988L; + + /** + * ID + */ + @Id + private Integer patientId; + + /** + * 姓名 + */ + private String name; + + /** + * 所患病症(对应字符串类型) 英文逗号 分割 + */ + private String diseaseIds; + + /** + * 患者标签(对应数字类型) / 分割 + */ + private String tagIds; + + @RelationOneToMany( + selfField = "diseaseIds", + splitBy = ",", //使用 , 进行分割 + targetTable = "tb_disease", //只获取某个字段值需要填入目标表名 + targetField = "diseaseId", //测试目标字段是字符串类型是否正常转换 + valueField = "name" //测试只获取某个字段值是否正常 + ) + private List diseaseNameList; + + @RelationOneToMany( + selfField = "tagIds", + splitBy = "/", //使用 / 进行分割 + targetField = "tagId" //测试目标字段是数字类型是否正常转换 + ) + private List tagList; + + @RelationOneToMany( + selfField = "diseaseIds", + splitBy = ",", //使用 , 进行分割 + targetField = "diseaseId", //测试目标字段是字符串类型是否正常转换 + mapKeyField = "diseaseId" //测试Map映射 + ) + private Map diseaseMap; + + public Integer getPatientId() { + return patientId; + } + + public void setPatientId(Integer patientId) { + this.patientId = patientId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDiseaseIds() { + return diseaseIds; + } + + public void setDiseaseIds(String diseaseIds) { + this.diseaseIds = diseaseIds; + } + + public String getTagIds() { + return tagIds; + } + + public void setTagIds(String tagIds) { + this.tagIds = tagIds; + } + + public List getDiseaseNameList() { + return diseaseNameList; + } + + public void setDiseaseNameList(List diseaseNameList) { + this.diseaseNameList = diseaseNameList; + } + + public List getTagList() { + return tagList; + } + + public void setTagList(List tagList) { + this.tagList = tagList; + } + + public Map getDiseaseMap() { + return diseaseMap; + } + + public void setDiseaseMap(Map diseaseMap) { + this.diseaseMap = diseaseMap; + } +} diff --git a/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/model/Tag.java b/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/model/Tag.java new file mode 100644 index 00000000..2888d6be --- /dev/null +++ b/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/model/Tag.java @@ -0,0 +1,44 @@ +package com.mybatisflex.test.model; + +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.Table; + +import java.io.Serializable; + +/** + * 标签 + * + * @author Ice 2023/09/26 + * @version 1.0 + */ +@Table(value = "tb_tag") +public class Tag implements Serializable { + private static final long serialVersionUID = 5600670055904157386L; + + /** + * ID + */ + @Id + private Integer tagId; + + /** + * 标签名称 + */ + private String name; + + public Integer getTagId() { + return tagId; + } + + public void setTagId(Integer tagId) { + this.tagId = tagId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/resources/patient_data_split_test.sql b/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/resources/patient_data_split_test.sql new file mode 100644 index 00000000..d21fcc77 --- /dev/null +++ b/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/resources/patient_data_split_test.sql @@ -0,0 +1,59 @@ +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for tb_disease +-- ---------------------------- +DROP TABLE IF EXISTS `tb_disease`; +CREATE TABLE `tb_disease` ( + `disease_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'ID', + `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '疾病名称', + PRIMARY KEY (`disease_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '疾病信息' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of tb_disease +-- ---------------------------- +INSERT INTO `tb_disease` VALUES ('1', '心脑血管疾病'); +INSERT INTO `tb_disease` VALUES ('2', '消化系统疾病'); +INSERT INTO `tb_disease` VALUES ('3', '神经系统疾病'); +INSERT INTO `tb_disease` VALUES ('4', '免疫系统疾病'); + +-- ---------------------------- +-- Table structure for tb_patient +-- ---------------------------- +DROP TABLE IF EXISTS `tb_patient`; +CREATE TABLE `tb_patient` ( + `patient_id` int NOT NULL AUTO_INCREMENT COMMENT 'ID', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '姓名', + `disease_ids` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '所患病症(对应字符串类型) 英文逗号 分割', + `tag_ids` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '患者标签(对应数字类型) / 分割', + PRIMARY KEY (`patient_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '患者信息' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of tb_patient +-- ---------------------------- +INSERT INTO `tb_patient` VALUES (1, '张三', '1', NULL); +INSERT INTO `tb_patient` VALUES (2, '李四', '1,2', '1'); +INSERT INTO `tb_patient` VALUES (3, '王五', '1,2,3', '1/2'); +INSERT INTO `tb_patient` VALUES (4, '赵六', '1,2,3,4', '1/2/3'); + +-- ---------------------------- +-- Table structure for tb_tag +-- ---------------------------- +DROP TABLE IF EXISTS `tb_tag`; +CREATE TABLE `tb_tag` ( + `tag_id` int NOT NULL AUTO_INCREMENT COMMENT 'ID', + `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '标签名', + PRIMARY KEY (`tag_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '标签' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of tb_tag +-- ---------------------------- +INSERT INTO `tb_tag` VALUES (1, 'VIP'); +INSERT INTO `tb_tag` VALUES (2, 'JAVA开发'); +INSERT INTO `tb_tag` VALUES (3, 'Web开发'); + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/mybatis-flex-test/mybatis-flex-spring-boot-test/src/test/java/com/mybatisflex/test/mapper/PatientMapperTest.java b/mybatis-flex-test/mybatis-flex-spring-boot-test/src/test/java/com/mybatisflex/test/mapper/PatientMapperTest.java new file mode 100644 index 00000000..edc0644d --- /dev/null +++ b/mybatis-flex-test/mybatis-flex-spring-boot-test/src/test/java/com/mybatisflex/test/mapper/PatientMapperTest.java @@ -0,0 +1,30 @@ +package com.mybatisflex.test.mapper; + +import com.alibaba.fastjson2.JSON; +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.test.model.PatientVO1; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; + +/** + * 患者相关测试 + * + * @author Ice 2023/09/26 + * @version 1.0 + */ +@SpringBootTest +@SuppressWarnings("all") +public class PatientMapperTest { + + @Autowired + private PatientMapper patientMapper; + + @Test + public void testRelationOneToManySplitBy() { + PatientVO1 patientVO1 = patientMapper.selectOneWithRelationsByQueryAs(QueryWrapper.create().orderBy(PatientVO1::getPatientId, false).limit(1), PatientVO1.class); + System.out.println(JSON.toJSONString(patientVO1)); + } +}