mybatis collection解析以及和association的区别
1.collection标签
说到mybatis的collection标签,我们肯定不陌生,可以通过它解决一对多的映射问题,举个例子一个用户对应多个系统权限,通过对用户表和权限表的关联查询我们可以得到好多条记录,但是用户信息这部分在多条记录中是重复的,只有权限不同,我们需要把这多条权限记录映射到这个用户之中,这个时候可以通过collection标签/association标签来解决(虽然assocation标签一般是解决一对一问题的,但它实际上也能实现我们的需求,可以通过后面的源码看出来)
1.1 相关代码和运行结果
实体类和mapper代码
@Data
public class UserDO {
private Integer userId;
private String username;
private String password;
private String nickname;
// 将用户的权限信息映射到用户中
private List permitDOList;
public UserDO() {}
public UserDO(@Param("userId") Integer userId, @Param("username") String username, @Param("password") String password, @Param("nickname") String nickname) {
this.userId = userId;
this.username = username;
this.password = password;
this.nickname = nickname;
}
}
@Data
public class PermitDO {
private Integer id;
private String code;
private String name;
private NodeTypeEnum type;
private Integer pid;
}
// mybatis代码
public interface UserMapper {
UserDO getByUserId(@Param("userId") Integer userId);
}
user_id, username, password, nickname
public class Test {
public static void main(String[] args) throws IOException {
try (InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml")) {
// 构建session工厂 DefaultSqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
UserDO userDO = userMapper.getByUserId(1);
System.out.println(userDO);
}
}
}
运行结果如下,可以看到权限记录映射到属性permitDOList 的list列表了
1.2 collection部分源码解析
通过PreparedStatement查询完之后得到ResultSet结果集,之后需要将结果集解析为java的pojo类中,下面通过源码简单讲下是如何解析的
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
// 是否有嵌套的resultMaps
if (resultMap.hasNestedResultMaps()) {
ensureNoRowBounds();
checkResultHandler();
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
// 无嵌套
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
根据有无嵌套分成两层逻辑,有嵌套resultMaps就是指
private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
final DefaultResultContext
这段代码主要是创建了一个缓存key,主要是根据resultMapId和
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException {
final String resultMapId = resultMap.getId();
Object rowValue = partialObject;
// rowValue不等于null时,说明此条记录可合并
if (rowValue != null) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
putAncestor(rowValue, resultMapId);
applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
ancestorObjects.remove(resultMapId);
} else {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 创建result接收对象,本例中是UserDO对象
rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
// 是否将查询出来的字段全部映射 默认false
if (shouldApplyAutomaticMappings(resultMap, true)) {
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
// 设置需要映射的属性值,不管有嵌套ResultMap的
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
// 存放第一条数据
putAncestor(rowValue, resultMapId);
// 处理有嵌套的resultMapping
foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;
ancestorObjects.remove(resultMapId);
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
// 将最终结果放入到nestedResultObjects中
if (combinedKey != CacheKey.NULL_CACHE_KEY) {
nestedResultObjects.put(combinedKey, rowValue);
}
}
return rowValue;
}
getRowValue方法主要是将ResultSet解析为实体类对象,applyPropertyMappings填充
private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject) {
boolean foundValues = false;
for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) {
// 嵌套id
final String nestedResultMapId = resultMapping.getNestedResultMapId();
// resultMapping有嵌套的map才继续
if (nestedResultMapId != null && resultMapping.getResultSet() == null) {
try {
final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping);
// 获取嵌套(经过一次鉴权)的ResultMap
final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix);
if (resultMapping.getColumnPrefix() == null) {
// try to fill circular reference only when columnPrefix
// is not specified for the nested result map (issue #215)
Object ancestorObject = ancestorObjects.get(nestedResultMapId);
if (ancestorObject != null) {
if (newObject) {
linkObjects(metaObject, resultMapping, ancestorObject); // issue #385
}
continue;
}
}
// 构建嵌套map的key
final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);
// 合并cacheKey
final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
// 尝试获取之前是否已经创建过
Object rowValue = nestedResultObjects.get(combinedKey);
boolean knownValue = rowValue != null;
// 实例化集合属性 list复制为空列表
instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatory
// 存在指定的非空列存在空值则返回false
if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {
rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);
if (rowValue != null && !knownValue) {
// 合并记录,设置对象-association或将对象添加到集合属性中-collection
linkObjects(metaObject, resultMapping, rowValue);
foundValues = true;
}
}
} catch (SQLException e) {
throw new ExecutorException("Error getting nested result map values for '" + resultMapping.getProperty() + "'. Cause: " + e, e);
}
}
}
return foundValues;
}
处理嵌套的结果映射,其实就是
private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {
final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
// 属性是集合进行添加
if (collectionProperty != null) {
final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
targetMetaObject.add(rowValue);
} else {
// 否则是对象 直接进行setter设置
metaObject.setValue(resultMapping.getProperty(), rowValue);
}
}
最后就把能合并的记录都合并在一起了,不同的权限映射到permitDOList这个集合中了
1.3 和的相同的和不同点
从上面的代码看来,关于
从上面的图中我们可以看到
另外在使用习惯上因为我们能确认表和表之间的关系是一对一还是一对多的,能够确认pojo类中的属性javaType是使用list还是普通对象,所以一般情况下一对一使用
最后
如果说的有问题欢迎提出指正讨论,代码提交在gitee上,感兴趣的同学可以下载看看