一、出现情况和解决方法
- 报错信息
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
### The error may exist in file [C:\Users\56342\Desktop\irest\irest-reports\irest-reports-server\target\classes\com\shht\irest\reports\server\mapper\oms\xml\OrderTransactionMapper.xml]
### The error may involve com.shht.irest.reports.server.mapper.oms.OrderTransactionMapper.countConsumeCount
### The error occurred while handling results
### SQL: SELECT COUNT(DISTINCT(ot.id)) as `count` FROM oms_order_transaction ot JOIN oms_order_transaction_detail otd ON ot.id = otd.transaction_id WHERE ot.tenant_id = 1 AND ot.pay_time BETWEEN ? AND ? AND ot.type = 'CONSUME' and ot.state = 'SUCCESS' AND ot.delete_flag = 0 AND otd.merchant_id = ?
### Cause: java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:92)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:440)
at com.sun.proxy.$Proxy124.selectOne(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate.java:159)
at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:90)
at com.baomidou.mybatisplus.core.override.MybatisMapperProxy$PlainMethodInvoker.invoke(MybatisMapperProxy.java:148)
at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89)
at com.sun.proxy.$Proxy128.countConsumeCount(Unknown Source)
- XML的SQL语句
SELECT
COUNT(DISTINCT(ot.id)) as `count`
FROM
oms_order_transaction ot
JOIN
oms_order_transaction_detail otd ON ot.id = otd.transaction_id
出现场景:在使用MyBatis进行SQL查询的时候,会抛出异常java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
出现原因:项目中Mapper查询结果映射的Result实体类没有提供无参构造器
解决方法:映射的实体类加上@NoArgsConstructor
@Data
@NoArgsConstructor
public class OrderTransactionMerchantSalesVO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "统计日期(日/月)")
private Date statisDate;
@ApiModelProperty(value = "统计日期(日/月)")
private String staticsDateText;
@ApiModelProperty(value = "商户ID")
private Long merchantId;
@ExcelProperty(value = "商户名称", index = 1)
@ApiModelProperty(value = "商户名称")
private String merchantName;
@ApiModelProperty(value = "项目ID")
private Long projectId;
@ExcelProperty(value = "所属项目", index = 2)
@ApiModelProperty(value = "项目名称")
private String projectName;
@ExcelProperty(value = "销售数量", index = 3)
@ApiModelProperty(value = "销量")
private Integer count;
@ExcelProperty(value = "营业额", index = 4)
@ApiModelProperty(value = "营业额")
private BigDecimal totalAmount;
@ExcelProperty(value = "充值真实金额实收", index = 5)
@ApiModelProperty(value = "充值真实金额实收")
private BigDecimal realAmount;
@ExcelProperty(value = "充值赠送金额实收", index = 6)
@ApiModelProperty(value = "充值赠送金额实收")
private BigDecimal discountAmount;
@ExcelProperty(value = "补贴实收", index = 7)
@ApiModelProperty(value = "补贴实收")
private BigDecimal allowanceAmount;
@ExcelProperty(value = "微信实收", index = 8)
@ApiModelProperty(value = "微信实收")
private BigDecimal weChatAmount;
@ExcelProperty(value = "支付宝实收", index = 9)
@ApiModelProperty(value = "支付宝实收")
private BigDecimal alipayAmount;
@ExcelProperty(value = "其他实收", index = 10)
@ApiModelProperty(value = "其他实收")
private BigDecimal otherAmount;
@ExcelProperty(value = "消费券实收", index = 11)
@ApiModelProperty(value = "消费券实收")
private BigDecimal voucherAmount;
@ApiModelProperty(value = "充值真实金额实收")
private BigDecimal actualAmount;
@ApiModelProperty(value = "平均均销售额")
private BigDecimal averageAmount;
@ApiModelProperty(value = "平均销量")
private BigDecimal averageCount;
@ApiModelProperty(value = "退单数")
private Integer refundOrderAmount;
@ApiModelProperty(value = "手续费")
private BigDecimal commission;
public OrderTransactionMerchantSalesVO(Long merchantId, String merchantName) {
this.merchantId = merchantId;
this.merchantName = merchantName;
this.count = 0;
this.totalAmount = BigDecimal.ZERO;
this.averageAmount = BigDecimal.ZERO;
this.averageCount = BigDecimal.ZERO;
this.refundOrderAmount = 0;
this.realAmount = BigDecimal.ZERO;
this.discountAmount = BigDecimal.ZERO;
this.allowanceAmount = BigDecimal.ZERO;
this.voucherAmount = BigDecimal.ZERO;
this.weChatAmount = BigDecimal.ZERO;
this.alipayAmount = BigDecimal.ZERO;
this.otherAmount = BigDecimal.ZERO;
this.actualAmount = BigDecimal.ZERO;
this.commission = BigDecimal.ZERO;
}
}
二、源码分析
2.1 出现问题的原因
package org.apache.ibatis.executor.resultset;
public class DefaultResultSetHandler implements ResultSetHandler {
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++) {
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;
}
}
MyBatis在设置相应参数的时候,由于只有一个两参的构造器,此时方法会进行根据构造器的参数进行两次循环。而sql响应结构只有一个参数,因此会报出数组下标越界的异常。
Caused by: java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
报错后执行handleResultSet()
方法中执行代码的finally
块的方法
2.2 MyBatis选择Constructor的策略
public class DefaultResultSetHandler implements ResultSetHandler {
/**
* 构建响应的映射实体类
*/
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);
// 如果存在无参构造器metaType.hasDefaultConstructor()会返回true
} else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
return objectFactory.create(resultType);
// 如果仅配置了带参数的构造器则会进入到该方法中
} else if (shouldApplyAutomaticMappings(resultMap, false)) {
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();
// 返回设置的 带参数的构造器
final Constructor<?> defaultConstructor = findDefaultConstructor(constructors);
if (defaultConstructor != null) {
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) {
// 如果只有一个构造器那么直接使用。
// 所以按照我们上面配置了1个两参构造器的时候,MyBatis会直接默认使用
if (constructors.length == 1) {
return constructors[0];
}
for (final Constructor<?> constructor : constructors) {
if (constructor.isAnnotationPresent(AutomapConstructor.class)) {
return constructor;
}
}
return null;
}