tk.mybatis 通用mapper查询 和 自定义xml查询 结果封装
(bug:通用mapper @Column注解 设置不生效,属性封装为null )
程序启动时,会检查所有的mapper方法(包括通用mapper方法和自定义mapper方法),并设置这些mapper方法的sql语句。
程序启动时
通用mapper方法
sql语句通常由在BaseSelectProvider/BaseInsertProvider/…提供,如果返回值为Bean实体,则在BaseXXXProvider中会调用setResultType()方法,修改返回值类型为实体类型。例如
public String selectAll(MappedStatement ms) {
final Class<?> entityClass = getEntityClass(ms);
//修改返回值类型为实体类型
setResultType(ms, entityClass);
StringBuilder sql = new StringBuilder();
sql.append(SqlHelper.selectAllColumns(entityClass));
sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));
// 逻辑删除的未删除查询条件
sql.append("<where>");
sql.append(SqlHelper.whereLogicDelete(entityClass, false));
sql.append("</where>");
sql.append(SqlHelper.orderByDefault(entityClass));
return sql.toString();
}
在该setResultType()方法前的getEntityClass()方法中,会创建并缓存EntityTable,EntityTable会将实体属性字段和数据库字段进行映射。映射逻辑在tk.mybatis.mapper.mapperhelper.resolve#processField方法中
protected void processField(EntityTable entityTable, EntityField field, Config config, Style style) {
if (field.isAnnotationPresent(Transient.class)) {
return;
}
EntityColumn entityColumn = new EntityColumn(entityTable);
entityColumn.setUseJavaType(config.isUseJavaType());
entityColumn.setEntityField(field);
if (field.isAnnotationPresent(Id.class)) {
entityColumn.setId(true);
}
//1. 实体字段是否使用@Column注解,使用@Column注解value作为数据库字段名
String columnName = null;
if (field.isAnnotationPresent(Column.class)) {
Column column = field.getAnnotation(Column.class);
columnName = column.name();
entityColumn.setUpdatable(column.updatable());
entityColumn.setInsertable(column.insertable());
}
if (field.isAnnotationPresent(ColumnType.class)) {
ColumnType columnType = field.getAnnotation(ColumnType.class);
entityColumn.setBlob(columnType.isBlob());
if (StringUtil.isEmpty(columnName) && StringUtil.isNotEmpty(columnType.column())) {
columnName = columnType.column();
}
if (columnType.jdbcType() != JdbcType.UNDEFINED) {
entityColumn.setJdbcType(columnType.jdbcType());
}
if (columnType.typeHandler() != UnknownTypeHandler.class) {
entityColumn.setTypeHandler(columnType.typeHandler());
}
}
//2. 如果未使用@Column注解,默认使用驼峰转下划线 作为数据库字段名
if (StringUtil.isEmpty(columnName)) {
columnName = StringUtil.convertByStyle(field.getName(), style);
}
if (StringUtil.isNotEmpty(config.getWrapKeyword()) && SqlReservedWords.containsWord(columnName)) {
columnName = MessageFormat.format(config.getWrapKeyword(), columnName);
}
entityColumn.setProperty(field.getName());
entityColumn.setColumn(columnName);
entityColumn.setJavaType(field.getJavaType());
if (field.getJavaType().isPrimitive()) {
log.warn("通用 Mapper 警告信息: <[" + entityColumn + "]> 使用了基本类型,基本类型在动态 SQL 中由于存在默认值,因此任何时候都不等于 null,建议修改基本类型为对应的包装类型!");
}
processOrderBy(entityTable, field, entityColumn);
processKeyGenerator(entityTable, field, entityColumn);
entityTable.getEntityClassColumns().add(entityColumn);
if (entityColumn.isId()) {
entityTable.getEntityClassPKColumns().add(entityColumn);
}
}
EntityColumn类中,property属性对应实体字段名,column属性对应数据库字段名。EntityTable中都包含了这些信息。
最后将这些信息保存到resultMap的resultMappings中,每个mapper方法对应一个resultMap。
自定义xml 方法
程序启动时,创建SqlSessionFactory,并解析xml文件,将对应的sql映射到mapper方法中,resultMap中保存的映射关系为 xml中resultMap标签对应的信息,如果未定义此标签,则resultMap中resultMappings的为空,result Map中type属性 对应 xml中的select标签中的resultType。
执行查询后
查询会使用到executor.query()方法
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
executor.query()方法使用org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets()方法进行结果封装
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
//存放查询结果的集合
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
//封装ResultSetWrapper,这是会查询数据库对应的所有字段,添加到columnNames列表中
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
//处理结果集的详细过程,真正的处理结果集的地方
handleResultSet(rsw, resultMap, multipleResults, null);
//获取下一个结果集
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
handleResultSet(rsw, resultMap, multipleResults, null):执行具体的结果集封装操作
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
if (resultHandler == null) {
//第一步:如果结果处理器为空,创建一个默认的DefaultResultHandler结果集处理器
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
//第二步:处理每一行的值
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
//第三步:将结果集添加到集合中
multipleResults.add(defaultResultHandler.getResultList());
} else {
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}
接下来handleRowValues()方法处理每一行的值的:
//org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValues
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
//判断是否有嵌套结果集
if (resultMap.hasNestedResultMaps()) {
ensureNoRowBounds();
checkResultHandler();
//处理含有嵌套ResultMap的结果
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
//处理不含有嵌套ResultMap的结果
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
以 处理不含有嵌套ResultMap的结果为例
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
//获取ResultSet
ResultSet resultSet = rsw.getResultSet();
skipRows(resultSet, rowBounds);
//主要判断上下文是否已关闭、resultSet是否关闭以及结果集是否还有元素
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
//获取行数据
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
getRowValue()获取行数据
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
//第一步:通过反射获取到需要封装的结果集实体类的构造方法,然后调用constructor.newInstance()创建一个对象
//此时返回的rowValue: User{id='null', username='null'} 里面的属性都是null
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
//行值不为空,并且结果对象有类型处理器
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
//第二步:创建元数据对象MetaObject,方便后面直接设置属性的值
//将行值包装成元数据对象MetaObject
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, false)) {
//第三步:驼峰转下划线 自动映射查询出来的数据到前面创建好的Java对象属性中
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
//第四步:@Column注解 自动映射查询出来的数据到前面创建好的Java对象属性中
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
第三步:applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix)
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
//第一步:建立好数据库列名和实体类属性名的映射关系
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
boolean foundValues = false;
if (!autoMapping.isEmpty()) {
for (UnMappedColumnAutoMapping mapping : autoMapping) {
//第二步:根据mapping.column数据库列名,从查询结果集中获取到具体某一列的值
//底层实际上就是调用的TypeHandler的:rs.getString("id")、rs.getString("name")
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
//第三步:拿到值之后,那就需要动态设置属性的值为刚刚获取到的值,通过metaObject元数据对象直接修改属性的值
metaObject.setValue(mapping.property, value);
}
}
}
return foundValues;
}
createAutomaticMappings()
private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
final String mapKey = resultMap.getId() + ":" + columnPrefix;
List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
if (autoMapping == null) {
autoMapping = new ArrayList<>();
// 获取在程序启动时未映射到实体字段的数据库字段 column
final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
for (String columnName : unmappedColumnNames) {
String propertyName = columnName;
if (columnPrefix != null && !columnPrefix.isEmpty()) {
// When columnPrefix is specified,
// ignore columns without the prefix.
if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
propertyName = columnName.substring(columnPrefix.length());
} else {
continue;
}
}
//如果mybatis.configuration.map-underscore-to-camel-case=true,则将数据库字段转成驼峰命名并检查实体中是否包含该属性
//否则property=null
final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
//如果存在该属性,并提供了setter方法
if (property != null && metaObject.hasSetter(property)) {
if (resultMap.getMappedProperties().contains(property)) {//如果程序启动时的resultMap中包含了该字段的映射
continue;
}
final Class<?> propertyType = metaObject.getSetterType(property);
if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
//将property和column进行映射,此处为 驼峰到下划线的映射
autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
} else {
configuration.getAutoMappingUnknownColumnBehavior()
.doAction(mappedStatement, columnName, property, propertyType);
}
} else {
configuration.getAutoMappingUnknownColumnBehavior()
.doAction(mappedStatement, columnName, (property != null) ? property : propertyName, null);
}
}
autoMappingsCache.put(mapKey, autoMapping);
}
return autoMapping;
}
rsw.getUnmappedColumnNames(resultMap, columnPrefix);
public List<String> getUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
List<String> unMappedColumnNames = unMappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
if (unMappedColumnNames == null) {
loadMappedAndUnmappedColumnNames(resultMap, columnPrefix);
unMappedColumnNames = unMappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
}
return unMappedColumnNames;
}
loadMappedAndUnmappedColumnNames(resultMap, columnPrefix);
private void loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
List<String> mappedColumnNames = new ArrayList<>();
List<String> unmappedColumnNames = new ArrayList<>();
final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH);
// xml方法,resultMap 中的映射关系为xml中resultMap标签定义的值,没定义的话,就为空
//通用mapper方法,resultMap 中的映射关系为@Column定义的值
final Set<String> mappedColumns = prependPrefixes(resultMap.getMappedColumns(), upperColumnPrefix);
//columnNames 为数据库对应的所有字段,在第一步封装ResultSetWrapper时创建的
for (String columnName : columnNames) {
final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);
//判断程序启动时,保存的resultMap中是否包含该数据库字段映射
if (mappedColumns.contains(upperColumnName)) {
mappedColumnNames.add(upperColumnName);
} else {
unmappedColumnNames.add(columnName);
}
}
mappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames);
unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames);
}
第四步:applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix)
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
//与第三步不同,这里是获取在程序启动时映射到实体字段的数据库字段 column,即通过@Column注解设置的
final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
if (propertyMapping.getNestedResultMapId() != null) {
column = null;
}
if (propertyMapping.isCompositeResult()
|| (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
|| propertyMapping.getResultSet() != null) {
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
final String property = propertyMapping.getProperty();
if (property == null) {
continue;
} else if (value == DEFERRED) {
foundValues = true;
continue;
}
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
//使用属性值setter方法设置属性值,需要注意的是setter方法名称需要为setXXX,XXX为属性名称,例如:setAttachmentName
//否则可能导致属性值设置错误
metaObject.setValue(property, value);
}
}
}
return foundValues;
}
到这里,Mybatis查询结果集封装的步骤基本就完成了。
注意点
如果使用@Column注解,value必须与数据库字段一致,末尾不允许存在空格!
如果末尾存在空格,例如:
@Column(name="CUSTOMER_PROVINCE_CITY_DISTRICT ")//末尾多个空格,数据库字段为 CUSTOMER_PROVINCE_CITY_DISTRICT
String CustomerProvinceCityDistrict ;
- 程序启动时,resultMap中建立的映射关系为 CustomerProvinceCityDistrict—>CUSTOMER_PROVINCE_CITY_DISTRICT空格
- 在封装结果集时,loadMappedAndUnmappedColumnNames,resultMap中不包含CUSTOMER_PROVINCE_CITY_DISTRICT的映射关系,会将CUSTOMER_PROVINCE_CITY_DISTRICT添加到unMappedColumnNamesMap中,unMappedColumnNamesMap只会在第三步applyAutomaticMappings()中被使用
- 在第三步中,applyAutomaticMappings()方法驼峰转下划线自动映射时,又会检查到 resultMap中包含了该驼峰字段的映射,导致无法将数据库字段CUSTOMER_PROVINCE_CITY_DISTRICT映射到实体属性值上
- 最终查询出来的结果无法封装到实体上