From 95513640de61726e0869d98e45b4ecd431c01531 Mon Sep 17 00:00:00 2001 From: FuriousPws002 <1938485828@qq.com> Date: Mon, 15 Apr 2024 23:44:36 +0800 Subject: [PATCH] =?UTF-8?q?resultMap=E7=BB=93=E6=9E=9C=E9=9B=86=E5=A4=84?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +- .../builder/MapperBuilderAssistant.java | 43 ++++++- .../ibatis/builder/xml/XMLMapperBuilder.java | 45 ++++++++ .../builder/xml/XMLStatementBuilder.java | 3 +- .../resultset/DefaultResultSetHandler.java | 107 +++++++++++++++++- .../apache/ibatis/session/Configuration.java | 10 ++ .../org/apache/ibatis/dao/UserMapper.java | 5 + .../org/apache/ibatis/entity/UserDTO.java | 8 +- .../apache/ibatis/session/SqlSessionTest.java | 33 ++++++ src/test/resources/mapper/UserMapper.xml | 35 ++++++ 10 files changed, 279 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 6c3c1b2..ad6b791 100644 --- a/README.md +++ b/README.md @@ -5,4 +5,5 @@ [2.解析xml中静态sql的mapper](https://github.com/FuriousPws002/mini-mybatis/wiki/2.%E8%A7%A3%E6%9E%90xml%E4%B8%AD%E9%9D%99%E6%80%81sql%E7%9A%84mapper "Markdown")
[3.执行静态sql](https://github.com/FuriousPws002/mini-mybatis/wiki/3.%E6%89%A7%E8%A1%8C%E9%9D%99%E6%80%81sql "Markdown")
[4.参数绑定](https://github.com/FuriousPws002/mini-mybatis/wiki/4.%E5%8F%82%E6%95%B0%E7%BB%91%E5%AE%9A "Markdown")
-[5.resultType结果集处理](https://github.com/FuriousPws002/mini-mybatis/wiki/5.resultType%E7%BB%93%E6%9E%9C%E9%9B%86%E5%A4%84%E7%90%86 "Markdown")
\ No newline at end of file +[5.resultType结果集处理](https://github.com/FuriousPws002/mini-mybatis/wiki/5.resultType%E7%BB%93%E6%9E%9C%E9%9B%86%E5%A4%84%E7%90%86 "Markdown")
+[6.resultMap结果集处理](https://github.com/FuriousPws002/mini-mybatis/wiki/6.resultMap%E7%BB%93%E6%9E%9C%E9%9B%86%E5%A4%84%E7%90%86 "Markdown")
diff --git a/src/main/java/org/apache/ibatis/builder/MapperBuilderAssistant.java b/src/main/java/org/apache/ibatis/builder/MapperBuilderAssistant.java index 47e8ede..d74130c 100644 --- a/src/main/java/org/apache/ibatis/builder/MapperBuilderAssistant.java +++ b/src/main/java/org/apache/ibatis/builder/MapperBuilderAssistant.java @@ -1,13 +1,18 @@ package org.apache.ibatis.builder; +import java.lang.reflect.Field; +import java.util.List; import java.util.Objects; +import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ResultMap; +import org.apache.ibatis.mapping.ResultMapping; import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.mapping.SqlSource; import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.type.TypeHandler; /** * @author furious 2024/4/7 @@ -43,11 +48,45 @@ public void addMappedStatement(String id, SqlCommandType sqlCommandType, SqlSour configuration.addMappedStatement(statement); } + public void addMappedStatement(String id, SqlCommandType sqlCommandType, SqlSource sqlSource, Class resultTypeClass, String resultMap) { + id = currentNamespace + "." + id; + MappedStatement statement = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType) + .resource(resource) + .resultMap(getStatementResultMap(resultTypeClass, resultMap)) + .build(); + configuration.addMappedStatement(statement); + } + + + public ResultMapping buildResultMapping(Class resultType, String property, String column, String nestedResultMap) throws Exception { + Field field = FieldUtils.getDeclaredField(resultType, property, true); + Class propertyType = field.getType(); + final TypeHandler typeHandler = configuration.getTypeHandlerRegistry().getTypeHandler(propertyType); + ResultMapping resultMapping = new ResultMapping(configuration, property, column, typeHandler); + resultMapping.setNestedResultMapId(nestedResultMap); + return resultMapping; + } + + public ResultMap addResultMap(String id, Class type, List resultMappings) { + ResultMap resultMap = new ResultMap(configuration, type); + resultMap.setId(currentNamespace + "." + id); + resultMap.setResultMappings(resultMappings); + configuration.addResultMap(resultMap); + return resultMap; + } + private ResultMap getStatementResultMap(Class resultType) { - if (Objects.isNull(resultType)) { + return getStatementResultMap(resultType, null); + } + + private ResultMap getStatementResultMap(Class resultType, String resultMap) { + if (Objects.isNull(resultType) && Objects.isNull(resultMap)) { return null; } - return new ResultMap(configuration, resultType); + if (Objects.nonNull(resultType)) { + return new ResultMap(configuration, resultType); + } + return configuration.getResultMap(currentNamespace + "." + resultMap); } } diff --git a/src/main/java/org/apache/ibatis/builder/xml/XMLMapperBuilder.java b/src/main/java/org/apache/ibatis/builder/xml/XMLMapperBuilder.java index 8439100..62efae7 100644 --- a/src/main/java/org/apache/ibatis/builder/xml/XMLMapperBuilder.java +++ b/src/main/java/org/apache/ibatis/builder/xml/XMLMapperBuilder.java @@ -1,12 +1,16 @@ package org.apache.ibatis.builder.xml; import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; import org.apache.ibatis.builder.BaseBuilder; import org.apache.ibatis.builder.BuilderException; import org.apache.ibatis.builder.MapperBuilderAssistant; +import org.apache.ibatis.mapping.ResultMap; +import org.apache.ibatis.mapping.ResultMapping; import org.apache.ibatis.parsing.XNode; import org.apache.ibatis.parsing.XPathParser; import org.apache.ibatis.session.Configuration; @@ -43,6 +47,7 @@ private void configurationElement(XNode context) { if (Objects.isNull(namespace) || namespace.isEmpty()) { throw new BuilderException("mapper namespace can not be empty"); } + resultMapElements(context.evalNodes("resultMap")); buildStatementFromContext(context.evalNodes("insert|select")); } catch (Exception e) { throw new BuilderException("parse mapper xml error", e); @@ -55,4 +60,44 @@ private void buildStatementFromContext(List list) { statementParser.parseStatementNode(); } } + + private void resultMapElements(List list) { + for (XNode resultMapNode : list) { + try { + resultMapElement(resultMapNode); + } catch (Exception ignored) { + } + } + } + + private ResultMap resultMapElement(XNode resultMapNode) throws Exception { + return resultMapElement(resultMapNode, Collections.emptyList(), null); + } + + private ResultMap resultMapElement(XNode resultMapNode, List additionalResultMappings, Class enclosingType) throws Exception { + String type = resultMapNode.getAttribute("type", resultMapNode.getAttribute("ofType")); + Class typeClass = resolveClass(type); + List resultMappings = new ArrayList<>(additionalResultMappings); + List resultChildren = resultMapNode.getChildren(); + for (XNode resultChild : resultChildren) { + resultMappings.add(buildResultMappingFromContext(resultChild, typeClass)); + } + String id = resultMapNode.getAttribute("id", resultMapNode.id()); + return builderAssistant.addResultMap(id, typeClass, resultMappings); + } + + private ResultMapping buildResultMappingFromContext(XNode context, Class resultType) throws Exception { + String property = context.getAttribute("property"); + String column = context.getAttribute("column"); + String nestedResultMap = processNestedResultMappings(context, Collections.emptyList(), resultType); + return builderAssistant.buildResultMapping(resultType, property, column, nestedResultMap); + } + + private String processNestedResultMappings(XNode context, List resultMappings, Class enclosingType) throws Exception { + if (Objects.equals(context.getName(), "collection")) { + ResultMap resultMap = resultMapElement(context, resultMappings, enclosingType); + return resultMap.getId(); + } + return null; + } } diff --git a/src/main/java/org/apache/ibatis/builder/xml/XMLStatementBuilder.java b/src/main/java/org/apache/ibatis/builder/xml/XMLStatementBuilder.java index 86e9f7d..61271d0 100644 --- a/src/main/java/org/apache/ibatis/builder/xml/XMLStatementBuilder.java +++ b/src/main/java/org/apache/ibatis/builder/xml/XMLStatementBuilder.java @@ -38,7 +38,8 @@ public void parseStatementNode() { SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); SqlSource sqlSource = configuration.getLanguageDriver().createSqlSource(configuration, context, null); String resultType = context.getAttribute("resultType"); + String resultMap = context.getAttribute("resultMap"); Class resultTypeClass = resolveClass(resultType); - builderAssistant.addMappedStatement(id, sqlCommandType, sqlSource,resultTypeClass); + builderAssistant.addMappedStatement(id, sqlCommandType, sqlSource, resultTypeClass, resultMap); } } diff --git a/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java b/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java index b5aca6c..61e4f9f 100644 --- a/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java +++ b/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java @@ -1,18 +1,27 @@ package org.apache.ibatis.executor.resultset; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.ibatis.executor.ExecutorException; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ResultMap; +import org.apache.ibatis.mapping.ResultMapping; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.type.TypeHandler; import org.apache.ibatis.type.TypeHandlerRegistry; @@ -54,9 +63,80 @@ private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List multipleResults) throws SQLException { - handleRowValuesForSimpleResultMap(rsw, resultMap, multipleResults); + if (resultMap.hasNestedResultMaps()) { + handleRowValuesForNestedResultMap(rsw, resultMap, multipleResults); + } else { + handleRowValuesForSimpleResultMap(rsw, resultMap, multipleResults); + } + } + + private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, List multipleResults) throws SQLException { + ResultSet resultSet = rsw.getResultSet(); + // nested resultmaps + Map nestedResultObjects = new HashMap<>(); + while (!resultSet.isClosed() && resultSet.next()) { + String rowKey = createRowKey(rsw, resultMap); + Object partialObject = nestedResultObjects.get(rowKey); + Object rowValue = getRowValue(rsw, resultMap, rowKey, partialObject, nestedResultObjects); + if (Objects.isNull(partialObject)) { + multipleResults.add(rowValue); + } + } + } + + private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String combinedKey, Object partialObject, Map nestedResultObjects) throws SQLException { + Object rowValue = partialObject; + if (Objects.nonNull(rowValue)) { + applyNestedResultMappings(rsw, resultMap, rowValue, nestedResultObjects, combinedKey); + } else { + rowValue = createResultObject(resultMap); + applyAutomaticMappings(rsw, resultMap, rowValue); + applyNestedResultMappings(rsw, resultMap, rowValue, nestedResultObjects, combinedKey); + nestedResultObjects.put(combinedKey, rowValue); + } + + return rowValue; + } + + private void applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, Object parentRowValue, Map nestedResultObjects, String parentRowKey) { + if (Objects.isNull(resultMap.getResultMappings())) { + return; + } + for (ResultMapping resultMapping : resultMap.getResultMappings()) { + final String nestedResultMapId = resultMapping.getNestedResultMapId(); + if (StringUtils.isEmpty(nestedResultMapId)) { + continue; + } + try { + ResultMap nestedResultMap = configuration.getResultMap(nestedResultMapId); + String rowKey = createRowKey(rsw, nestedResultMap); + String combinedKey = rowKey + parentRowKey; + Object rowValue = nestedResultObjects.get(combinedKey); + boolean knownValue = rowValue != null; + rowValue = getRowValue(rsw, nestedResultMap, combinedKey, rowValue, nestedResultObjects); + if (rowValue != null && !knownValue) { + linkObjects(parentRowValue, resultMapping, rowValue); + } + } catch (SQLException e) { + throw new ExecutorException("Error getting nested result map values for '" + resultMapping.getProperty() + "'. Cause: " + e, e); + } + } + } + + private void linkObjects(Object parentRowValue, ResultMapping resultMapping, Object rowValue) { + try { + List old = (List) FieldUtils.readField(parentRowValue, resultMapping.getProperty(), true); + Field field = FieldUtils.getDeclaredField(parentRowValue.getClass(), resultMapping.getProperty(), true); + Object list = field.getType().newInstance(); + Method add = List.class.getDeclaredMethod("add", Object.class); + add.invoke(list, rowValue); + old.addAll(((List) list)); + } catch (Exception e) { + ExceptionUtils.rethrow(e); + } } + private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, List multipleResults) throws SQLException { ResultSet resultSet = rsw.getResultSet(); while (!resultSet.isClosed() && resultSet.next()) { @@ -65,14 +145,19 @@ private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap r } } + private String createRowKey(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException { + Object rowValue = getRowValue(rsw, resultMap); + return rsw.hashCode() + ToStringBuilder.reflectionToString(rowValue, ToStringStyle.NO_CLASS_NAME_STYLE); + } + private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException { Object rowValue = createResultObject(resultMap); - applyAutomaticMappings(rsw, rowValue); + applyAutomaticMappings(rsw, resultMap, rowValue); return rowValue; } - private boolean applyAutomaticMappings(ResultSetWrapper rsw, Object rowValue) throws SQLException { - List autoMapping = createAutomaticMappings(rsw, rowValue); + private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, Object rowValue) throws SQLException { + List autoMapping = createAutomaticMappings(rsw, resultMap, rowValue); boolean foundValues = false; if (!autoMapping.isEmpty()) { for (UnMappedColumnAutoMapping mapping : autoMapping) { @@ -90,8 +175,20 @@ private boolean applyAutomaticMappings(ResultSetWrapper rsw, Object rowValue) th return foundValues; } - private List createAutomaticMappings(ResultSetWrapper rsw, Object rowValue) { + private List createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, Object rowValue) { List autoMapping = new ArrayList<>(); + //若存在resultMapping,使用resultMapping + if (Objects.nonNull(resultMap.getResultMappings()) && !resultMap.getResultMappings().isEmpty()) { + return resultMap.getResultMappings().stream() + .filter(m -> StringUtils.isEmpty(m.getNestedResultMapId())) + .map(m -> { + UnMappedColumnAutoMapping auto = new UnMappedColumnAutoMapping(m.getColumn(), m.getProperty(), m.getTypeHandler()); +// if(Objects.isNull(auto.typeHandler)){ +// +// } + return auto; + }).collect(Collectors.toList()); + } List columnLabels = rsw.getColumnLabels(); for (String columnLabel : columnLabels) { Field field = FieldUtils.getDeclaredField(rowValue.getClass(), columnLabel, true); diff --git a/src/main/java/org/apache/ibatis/session/Configuration.java b/src/main/java/org/apache/ibatis/session/Configuration.java index 1974c2f..c00a47e 100644 --- a/src/main/java/org/apache/ibatis/session/Configuration.java +++ b/src/main/java/org/apache/ibatis/session/Configuration.java @@ -17,6 +17,7 @@ import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ResultMap; import org.apache.ibatis.scripting.LanguageDriver; import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver; import org.apache.ibatis.type.TypeHandlerRegistry; @@ -35,6 +36,7 @@ public class Configuration { private final Map mappedStatements = new HashMap<>(); private final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(); private final LanguageDriver languageDriver = new XMLLanguageDriver(); + private final Map resultMaps = new HashMap<>(); public Configuration() { } @@ -98,4 +100,12 @@ public TypeHandlerRegistry getTypeHandlerRegistry() { public LanguageDriver getLanguageDriver() { return languageDriver; } + + public void addResultMap(ResultMap resultMap) { + resultMaps.put(resultMap.getId(), resultMap); + } + + public ResultMap getResultMap(String id) { + return resultMaps.get(id); + } } diff --git a/src/test/java/org/apache/ibatis/dao/UserMapper.java b/src/test/java/org/apache/ibatis/dao/UserMapper.java index 6b57765..5d9a47c 100644 --- a/src/test/java/org/apache/ibatis/dao/UserMapper.java +++ b/src/test/java/org/apache/ibatis/dao/UserMapper.java @@ -4,6 +4,7 @@ import org.apache.ibatis.annotations.Param; import org.apache.ibatis.entity.UserDO; +import org.apache.ibatis.entity.UserDTO; import org.apache.ibatis.reflection.ParamNameResolver; import org.apache.ibatis.scripting.defaults.DefaultParameterHandler; @@ -30,4 +31,8 @@ public interface UserMapper { void insertWithPOJO(UserDO user); List select(); + + List selectResultMap(); + + List selectNestedResultMap(); } diff --git a/src/test/java/org/apache/ibatis/entity/UserDTO.java b/src/test/java/org/apache/ibatis/entity/UserDTO.java index 3c57d63..dd7c1bb 100644 --- a/src/test/java/org/apache/ibatis/entity/UserDTO.java +++ b/src/test/java/org/apache/ibatis/entity/UserDTO.java @@ -1,6 +1,6 @@ package org.apache.ibatis.entity; -import java.util.List; +import java.util.ArrayList; /** * @author furious 2024/4/13 @@ -10,7 +10,7 @@ public class UserDTO { private String name; private Integer age; - private List carList; + private ArrayList carList = new ArrayList<>(); public String getName() { return name; @@ -28,11 +28,11 @@ public void setAge(Integer age) { this.age = age; } - public List getCarList() { + public ArrayList getCarList() { return carList; } - public void setCarList(List carList) { + public void setCarList(ArrayList carList) { this.carList = carList; } } diff --git a/src/test/java/org/apache/ibatis/session/SqlSessionTest.java b/src/test/java/org/apache/ibatis/session/SqlSessionTest.java index 295ec34..b269bed 100644 --- a/src/test/java/org/apache/ibatis/session/SqlSessionTest.java +++ b/src/test/java/org/apache/ibatis/session/SqlSessionTest.java @@ -5,6 +5,7 @@ import org.apache.ibatis.DataSourceBuilderTest; import org.apache.ibatis.dao.UserMapper; import org.apache.ibatis.entity.UserDO; +import org.apache.ibatis.entity.UserDTO; import org.apache.ibatis.session.defaults.DefaultSqlSession; import org.junit.Assert; import org.junit.Test; @@ -94,4 +95,36 @@ public void queryPOJOHandleTheResult() { Assert.assertNotNull(userDO.getNamex()); } + /** + * 查询简单resultMap映射 + */ + @Test + public void queryResultMap() { + Configuration configuration = new Configuration(); + configuration.setDataSource(DataSourceBuilderTest.build()); + configuration.addMapper(UserMapper.class); + SqlSession sqlSession = new DefaultSqlSession(configuration); + UserMapper userMapper = sqlSession.getMapper(UserMapper.class); + List list = userMapper.selectResultMap(); + Assert.assertNotNull(list); + UserDO userDO = list.get(0); + Assert.assertNotNull(userDO.getNamex()); + } + + /** + * 查询嵌套resultMap映射 + */ + @Test + public void queryNestedResultMap() { + Configuration configuration = new Configuration(); + configuration.setDataSource(DataSourceBuilderTest.build()); + configuration.addMapper(UserMapper.class); + SqlSession sqlSession = new DefaultSqlSession(configuration); + UserMapper userMapper = sqlSession.getMapper(UserMapper.class); + List list = userMapper.selectNestedResultMap(); + Assert.assertNotNull(list); + UserDTO user = list.get(0); + Assert.assertNotNull(user.getName()); + Assert.assertNotNull(user.getCarList()); + } } diff --git a/src/test/resources/mapper/UserMapper.xml b/src/test/resources/mapper/UserMapper.xml index a986b05..5b7b2a4 100644 --- a/src/test/resources/mapper/UserMapper.xml +++ b/src/test/resources/mapper/UserMapper.xml @@ -20,4 +20,39 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file