问题描述

使用 mybatis 的结果集自动映射,发生了实体属性类型与表字段类型不一致;但实际上,相关实体的属性与表结构完全匹配。
关于实体类,是使用代码生成读取表结构生成的,但使用了公共父类,其中抽取出了一些通用的字段(包括主键、创建时间等)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Data
@Accessors(chain = true)
public class BaseEntity<Id extends Serializable> {

@TableId
protected Id id;

@TableField(value = "create_by", fill = INSERT, updateStrategy = NEVER)
protected String createBy;

@TableField(value = "create_time", fill = INSERT, updateStrategy = NEVER)
protected Date createTime;

@TableField(value = "update_by", fill = FieldFill.UPDATE)
protected String updateBy;

@TableField(value = "update_time", fill = FieldFill.UPDATE)
protected Date updateTime;

@TableField("ext1")
protected String ext1;

@TableField("ext2")
protected String ext2;

@TableField("ext3")
protected String ext3;

@TableField("ext4")
protected String ext4;

}

不必计较父类中设置主键的合理性。

BaseEntity.java 是基于 mybatis-plus,因此所有字段添加了相应的注解。
实体中依赖 lombok 隐藏 getter、setter 的实现细节,并使用了 @Builder 辅助实体创建。

解决方案

原因

  1. 使用 mybatis plus 的注解式开发,默认启用了 mybatis 的结果集自动映射,会通过构造器创建实体;
  2. 使用 lombok @Builder,导致实体生成了不包含父类属性的全参构造器;
  3. mybatis 反射获取实体属性,构建属性类型、字段类型以及字段名称的独立列表;
  4. mybatis 反射获取到实体构造器,当没有默认构造器时,会根据构造器参数索引顺序匹配字段,由于构造器参数长度是小于字段长度的,导致参数类型不匹配;

解决

在代码中添加默认构造器(通过反射获取,与可见性无关)。

建议

  1. 不要在表实体中使用 lombok 的 @Builder,@AllArgsConstructor,防止在继承父类时,产生不完全的“全参”构造器;
  2. 或者强制在表实体中启用 lombok 的 @NoArgsConstructor,保证一定存在无参构造器;
  3. 也可以在表实体目录中强制禁止相关 lombok 注解,防止其他开发无意中使用;
1
2
lombok.allArgsConstructor.flagUsage = ERROR
lombok.builder.flagUsage = ERROR

代码分析

DefaultResultSetHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 1、创建结果对象
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, false)) {
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}

private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
this.useConstructorMappings = false; // reset previous mapping result
final List<Class<?>> constructorArgTypes = new ArrayList<>();
final List<Object> constructorArgs = new ArrayList<>();
// 2、创建结果对象(使用可能存在的构造参数)
Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
// issue gcode #109 && issue #149
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
break;
}
}
}
this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
return resultObject;
}

private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
throws SQLException {
final Class<?> resultType = resultMap.getType();
final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
if (hasTypeHandlerForResultObject(rsw, resultType)) {
return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
} else if (!constructorMappings.isEmpty()) {
return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
} else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
// 2.1、使用无参构造器创建对象
return objectFactory.create(resultType);
} else if (shouldApplyAutomaticMappings(resultMap, false)) {
// 2.2、使用有参的构造器创建对象
return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);
}
throw new ExecutorException("Do not know how to create an instance of " + resultType);
}

private Object createByConstructorSignature(ResultSetWrapper rsw, Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) throws SQLException {
final Constructor<?>[] constructors = resultType.getDeclaredConstructors();
// 3、获取构造器
final Constructor<?> defaultConstructor = findDefaultConstructor(constructors);
if (defaultConstructor != null) {
// 4、使用构造器创建对象
return createUsingConstructor(rsw, resultType, constructorArgTypes, constructorArgs, defaultConstructor);
} else {
for (Constructor<?> constructor : constructors) {
if (allowedConstructorUsingTypeHandlers(constructor, rsw.getJdbcTypes())) {
return createUsingConstructor(rsw, resultType, constructorArgTypes, constructorArgs, constructor);
}
}
}
throw new ExecutorException("No constructor found in " + resultType.getName() + " matching " + rsw.getClassNames());
}

private Constructor<?> findDefaultConstructor(final Constructor<?>[] constructors) {
// 3.1、仅包含一个构造器直接返回
if (constructors.length == 1) {
return constructors[0];
}

// 3.2、返回包含 @AutomapConstructor 的构造器
for (final Constructor<?> constructor : constructors) {
if (constructor.isAnnotationPresent(AutomapConstructor.class)) {
return constructor;
}
}
return null;
}

private Object createUsingConstructor(ResultSetWrapper rsw, Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor) throws SQLException {
boolean foundValues = false;
for (int i = 0; i < constructor.getParameterTypes().length; i++) {
// 4.1、通过构造器参数进行匹配参数类型
Class<?> parameterType = constructor.getParameterTypes()[i];
String columnName = rsw.getColumnNames().get(i);
TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);
Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
constructorArgTypes.add(parameterType);
constructorArgs.add(value);
foundValues = value != null || foundValues;
}
return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
}

评论