From 78214c11f1411547c4980a3a005d8e1635256e6f Mon Sep 17 00:00:00 2001 From: Suomm <1474983351@qq.com> Date: Wed, 7 Jun 2023 15:03:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=98=A0=E5=B0=84=E5=AE=9E=E4=BD=93=E7=B1=BB=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=20association=20=E5=92=8C=20collection=20=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/mybatis/FlexConfiguration.java | 81 ++++++--- .../com/mybatisflex/core/table/TableInfo.java | 172 ++++++++++++++++-- .../core/table/TableInfoFactory.java | 16 +- 3 files changed, 225 insertions(+), 44 deletions(-) diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/mybatis/FlexConfiguration.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/mybatis/FlexConfiguration.java index 99ad428b..5c700fa0 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/mybatis/FlexConfiguration.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/mybatis/FlexConfiguration.java @@ -1,17 +1,17 @@ -/** - * 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. +/* + * 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.mybatis; @@ -27,7 +27,6 @@ import com.mybatisflex.core.row.RowMapper; import com.mybatisflex.core.table.EntityWrapperFactory; import com.mybatisflex.core.table.TableInfo; import com.mybatisflex.core.table.TableInfoFactory; -import com.mybatisflex.core.util.CollectionUtil; import com.mybatisflex.core.util.StringUtil; import org.apache.ibatis.executor.CachingExecutor; import org.apache.ibatis.executor.Executor; @@ -36,15 +35,15 @@ import org.apache.ibatis.executor.keygen.NoKeyGenerator; import org.apache.ibatis.executor.keygen.SelectKeyGenerator; import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apache.ibatis.executor.statement.StatementHandler; -import org.apache.ibatis.mapping.BoundSql; -import org.apache.ibatis.mapping.Environment; -import org.apache.ibatis.mapping.MappedStatement; -import org.apache.ibatis.mapping.ResultMap; +import org.apache.ibatis.mapping.*; import org.apache.ibatis.session.*; import org.apache.ibatis.transaction.Transaction; import org.apache.ibatis.util.MapUtil; import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -178,14 +177,40 @@ public class FlexConfiguration extends Configuration { String resultMapId = tableInfo.getEntityClass().getName(); - ResultMap resultMap; + /*ResultMap resultMap; if (hasResultMap(resultMapId)) { resultMap = getResultMap(resultMapId); } else { resultMap = tableInfo.buildResultMap(this); this.addResultMap(resultMap); + }*/ + + // 变量名与属性名区分开 + List resultMapList; + if (hasResultMap(resultMapId)) { + resultMapList = new ArrayList<>(); + fillResultMapList(resultMapId, resultMapList); + } else { + resultMapList = tableInfo.buildResultMapList(this); + for (ResultMap resultMap : resultMapList) { + if (!hasResultMap(resultMap.getId())) { + addResultMap(resultMap); + } + } } + /* + * 这里解释一下为什么要反转这个集合: + * + * MyBatis 在解析 ResultMaps 的时候,是按照顺序一个一个进行解析的,对于有嵌套 + * 的 ResultMap 对象,也就是 nestResultMap 需要放在靠前的位置,首先解析。 + * + * 而我们进行递归 buildResultMapList 也好,fillResultMapList 也好,都是 + * 非嵌套 ResultMap 在集合最开始的位置,所以要反转一下集合,将 hasNestedResultMaps + * 的 ResultMap 对象放到集合的最前面。 + */ + Collections.reverse(resultMapList); + return new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), ms.getSqlSource(), ms.getSqlCommandType()) .resource(ms.getResource()) .fetchSize(ms.getFetchSize()) @@ -198,7 +223,8 @@ public class FlexConfiguration extends Configuration { .lang(ms.getLang()) .resultOrdered(ms.isResultOrdered()) .resultSets(ms.getResultSets() == null ? null : String.join(",", ms.getResultSets())) - .resultMaps(CollectionUtil.newArrayList(resultMap)) // 替换resultMap + //.resultMaps(CollectionUtil.newArrayList(resultMap)) // 替换resultMap + .resultMaps(resultMapList) .resultSetType(ms.getResultSetType()) .flushCacheRequired(ms.isFlushCacheRequired()) .useCache(ms.isUseCache()) @@ -206,6 +232,19 @@ public class FlexConfiguration extends Configuration { .build(); } + private void fillResultMapList(String resultMapId, List resultMapList) { + ResultMap resultMap = this.getResultMap(resultMapId); + resultMapList.add(resultMap); + if (resultMap.hasNestedResultMaps()) { + for (ResultMapping resultMapping : resultMap.getResultMappings()) { + String nestedResultMapId = resultMapping.getNestedResultMapId(); + if (nestedResultMapId != null) { + fillResultMapList(nestedResultMapId, resultMapList); + } + } + } + } + /** * 生成新的、已替换主键生成器的 MappedStatement * diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/table/TableInfo.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/table/TableInfo.java index 28c1db8f..13730c7a 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/table/TableInfo.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/table/TableInfo.java @@ -1,17 +1,17 @@ -/** - * 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. +/* + * 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.table; @@ -39,6 +39,7 @@ import org.apache.ibatis.session.Configuration; import org.apache.ibatis.type.TypeHandler; import org.apache.ibatis.util.MapUtil; +import java.lang.reflect.Field; import java.lang.reflect.Proxy; import java.sql.ResultSet; import java.sql.SQLException; @@ -96,9 +97,24 @@ public class TableInfo { private List onUpdateListeners; private List onSetListeners; + /** + * @deprecated 该功能有更好的方式实现,此属性可能会被移除。 + */ + @Deprecated private Map> joinTypes; + /** + * 对应 MapperXML 配置文件中 {@code } 标签下的 {@code } 标签。 + */ + private Map> associationType; + + /** + * 对应 MapperXML 配置文件中 {@code } 标签下的 {@code } 标签。 + */ + private Map> collectionType; + + private final ReflectorFactory reflectorFactory = new BaseReflectorFactory() { @Override public Reflector findForClass(Class type) { @@ -119,11 +135,11 @@ public class TableInfo { return tableName; } - public String getWrapSchemaAndTableName(IDialect dialect){ - if (StringUtil.isNotBlank(schema)){ - return dialect.wrap(dialect.getRealSchema(schema)) +"." + dialect.wrap(dialect.getRealTable(tableName)); - }else { - return dialect.wrap(dialect.getRealTable(tableName)); + public String getWrapSchemaAndTableName(IDialect dialect) { + if (StringUtil.isNotBlank(schema)) { + return dialect.wrap(dialect.getRealSchema(schema)) + "." + dialect.wrap(dialect.getRealTable(tableName)); + } else { + return dialect.wrap(dialect.getRealTable(tableName)); } } @@ -296,6 +312,36 @@ public class TableInfo { joinTypes.put(fieldName, clazz); } + public Map> getAssociationType() { + return associationType; + } + + public void setAssociationType(Map> associationType) { + this.associationType = associationType; + } + + public void addAssociationType(String fieldName, Class clazz) { + if (associationType == null) { + associationType = new HashMap<>(); + } + associationType.put(fieldName, clazz); + } + + public Map> getCollectionType() { + return collectionType; + } + + public void setCollectionType(Map> collectionType) { + this.collectionType = collectionType; + } + + public void addCollectionType(Field field, Class genericClass) { + if (collectionType == null) { + collectionType = new HashMap<>(); + } + collectionType.put(field, genericClass); + } + void setColumnInfoList(List columnInfoList) { this.columnInfoList = columnInfoList; this.columns = new String[columnInfoList.size()]; @@ -660,6 +706,10 @@ public class TableInfo { .collect(Collectors.toList()); } + /** + * @deprecated 该功能有更好的方式实现,此方法可能会被移除。 + */ + @Deprecated public ResultMap buildResultMap(Configuration configuration) { String resultMapId = entityClass.getName(); List resultMappings = new ArrayList<>(); @@ -727,6 +777,90 @@ public class TableInfo { return new ResultMap.Builder(configuration, resultMapId, entityClass, resultMappings).build(); } + public List buildResultMapList(Configuration configuration) { + String resultMapId = entityClass.getName(); + List resultMaps = new ArrayList<>(); + List resultMappings = new ArrayList<>(); + + // 标签下的 标签映射 + for (ColumnInfo columnInfo : columnInfoList) { + ResultMapping mapping = new ResultMapping.Builder(configuration, columnInfo.property, + columnInfo.column, columnInfo.propertyType) + .jdbcType(columnInfo.getJdbcType()) + .typeHandler(columnInfo.buildTypeHandler()) + .build(); + resultMappings.add(mapping); + + //add property mapper for sql: select xxx as property ... + if (!Objects.equals(columnInfo.getColumn(), columnInfo.getProperty())) { + ResultMapping propertyMapping = new ResultMapping.Builder(configuration, columnInfo.property, + columnInfo.property, columnInfo.propertyType) + .jdbcType(columnInfo.getJdbcType()) + .typeHandler(columnInfo.buildTypeHandler()) + .build(); + resultMappings.add(propertyMapping); + } + } + + // 标签下的 标签映射 + for (IdInfo idInfo : primaryKeyList) { + ResultMapping mapping = new ResultMapping.Builder(configuration, idInfo.property, + idInfo.column, idInfo.propertyType) + .flags(CollectionUtil.newArrayList(ResultFlag.ID)) + .jdbcType(idInfo.getJdbcType()) + .typeHandler(idInfo.buildTypeHandler()) + .build(); + resultMappings.add(mapping); + } + + // 标签下的 标签映射 + if (associationType != null) { + associationType.forEach((fieldName, fieldType) -> { + // 获取嵌套类型的信息,也就是 javaType 属性 + TableInfo tableInfo = TableInfoFactory.ofEntityClass(fieldType); + // 构建嵌套类型的 ResultMap 对象,也就是 标签下的内容 + // 这里是递归调用,直到嵌套类型里面没有其他嵌套类型或者集合类型为止 + List resultMapList = tableInfo.buildResultMapList(configuration); + // 寻找是否有嵌套 ResultMap 引用 + Optional nestedResultMap = resultMapList.stream() + .filter(e -> fieldType.getName().equals(e.getId())) + .findFirst(); + // 处理嵌套类型 ResultMapping 引用 + nestedResultMap.ifPresent(resultMap -> resultMappings.add(new ResultMapping.Builder(configuration, fieldName) + .javaType(fieldType) + .nestedResultMapId(resultMap.getId()) + .build())); + // 全部添加到 ResultMap 集合当中 + resultMaps.addAll(resultMapList); + }); + } + + // 标签下的 标签映射 + if (collectionType != null) { + collectionType.forEach((field, genericClass) -> { + // 获取集合泛型类型的信息,也就是 ofType 属性 + TableInfo tableInfo = TableInfoFactory.ofEntityClass(genericClass); + // 构建嵌套类型的 ResultMap 对象,也就是 标签下的内容 + // 这里是递归调用,直到集合类型里面没有其他嵌套类型或者集合类型为止 + List resultMapList = tableInfo.buildResultMapList(configuration); + // 寻找是否有嵌套 ResultMap 引用 + Optional nestedResultMap = resultMapList.stream() + .filter(e -> genericClass.getName().equals(e.getId())) + .findFirst(); + // 处理嵌套类型 ResultMapping 引用 + nestedResultMap.ifPresent(resultMap -> resultMappings.add(new ResultMapping.Builder(configuration, field.getName()) + .javaType(field.getType()) + .nestedResultMapId(resultMap.getId()) + .build())); + // 全部添加到 ResultMap 集合当中 + resultMaps.addAll(resultMapList); + }); + } + + resultMaps.add(new ResultMap.Builder(configuration, resultMapId, entityClass, resultMappings).build()); + + return resultMaps; + } private static boolean existColumn(List resultMappings, String name) { for (ResultMapping resultMapping : resultMappings) { diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/table/TableInfoFactory.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/table/TableInfoFactory.java index 61e1ebc5..4c1ab6aa 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/table/TableInfoFactory.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/table/TableInfoFactory.java @@ -212,11 +212,19 @@ public class TableInfoFactory { && !fieldType.isEnum() // 类型不是枚举 && !defaultSupportColumnTypes.contains(fieldType) //默认的自动类型不包含该类型 ) { - if (!Map.class.isAssignableFrom(fieldType) - && !Collection.class.isAssignableFrom(fieldType) - && !fieldType.isArray()) { - tableInfo.addJoinType(field.getName(), fieldType); + // 集合嵌套 + if (Collection.class.isAssignableFrom(fieldType)) { + ParameterizedType genericType = (ParameterizedType) field.getGenericType(); + Type actualTypeArgument = genericType.getActualTypeArguments()[0]; + tableInfo.addCollectionType(field, (Class) actualTypeArgument); } + // 实体类嵌套 + else if (!Map.class.isAssignableFrom(fieldType) + && !fieldType.isArray()) { + // tableInfo.addJoinType(field.getName(), fieldType); + tableInfo.addAssociationType(field.getName(), fieldType); + } + // 不支持的类型直接跳过 continue; }