错误日志如下:
2020-07-29 17:46:47.767 ERROR 6976 --- [nio-8553-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: Error attempting to get column 'create_time' from result set. Cause: java.sql.SQLFeatureNotSupportedException
; null; nested exception is java.sql.SQLFeatureNotSupportedException] with root cause
java.sql.SQLFeatureNotSupportedException: null
产生原因: 在mybatisplus的代码生成器中将数据库的datetime类型转换成为LocalDateTime, 集成druid数据源,使用3.1.0之前版本没问题,升级mp到3.1.1+后,运行时报错:java.sql.SQLFeatureNotSupportedException, 原因: mp3.1.1+使用了新版jdbc,LocalDateTime等新日期类型处理方式升级,但druid不支持,参考:苞米豆的Git
依赖版本
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
解决方案(三种):
- 在全局配置中设置dateType:
gc.setDateType(DateType.ONLY_DATE);
- 在数据源配置中进行类型转换
DataSourceConfig dsc = new DataSourceConfig();
dsc.setTypeConvert(new MySqlTypeConvert() {
// 自定义数据库表字段类型转换【可选】
@Override
public DbColumnType processTypeConvert(GlobalConfig globalConfig, String fieldType) {
System.out.println("转换类型:" + fieldType);
//将数据库中datetime转换成date
if ( fieldType.toLowerCase().contains( "datetime" ) ) {
return DbColumnType.DATE;
}
return (DbColumnType) super.processTypeConvert(globalConfig, fieldType);
}
});
- 提高德鲁伊的版本1.1.18(不过因为一处有问题而更改项目依赖的版本总归是不太好的)
解析前两种解决方案
// 代码生成器对象
AutoGenerator mpg = new AutoGenerator();
// 数据源配置对象
DataSourceConfig dsc = new DataSourceConfig();
// 显示进行类型转换
dsc.setTypeConvert(new MySqlTypeConvert() {
// 自定义数据库表字段类型转换【可选】
@Override
public DbColumnType processTypeConvert(GlobalConfig globalConfig, String fieldType) {
System.out.println("转换类型:" + fieldType);
//将数据库中datetime转换成date
if ( fieldType.toLowerCase().contains( "datetime" ) ) {
return DbColumnType.DATE;
}
return (DbColumnType) super.processTypeConvert(globalConfig, fieldType);
}
});
// 设置数据源配置
mpg.setDataSource(dsc);
setDataSource(final DataSourceConfig dataSource)源码
// 返回一个AutoGenerator对象
public AutoGenerator setDataSource(final DataSourceConfig dataSource) {
this.dataSource = dataSource;
return this;
}
// AutoGenerator类的属性
private DataSourceConfig dataSource; // 数据源配置
// DataSourceConfig类中有类型转换的接口属性
private ITypeConvert typeConvert;
ITypeConvert接口源码
public interface ITypeConvert {
default IColumnType processTypeConvert(GlobalConfig globalConfig, TableField tableField) {
return this.processTypeConvert(globalConfig, tableField.getType());
}
IColumnType processTypeConvert(GlobalConfig globalConfig, String fieldType);
}
processTypeConvert方法实现
public IColumnType processTypeConvert(GlobalConfig globalConfig, String fieldType) {
String t = fieldType.toLowerCase();
if (t.contains("char")) {
return DbColumnType.STRING;
} else if (t.contains("bigint")) {
return DbColumnType.LONG;
} else if (t.contains("tinyint(1)")) {
return DbColumnType.BOOLEAN;
} else if (t.contains("int")) {
return DbColumnType.INTEGER;
} else if (t.contains("text")) {
return DbColumnType.STRING;
} else if (t.contains("bit")) {
return DbColumnType.BOOLEAN;
} else if (t.contains("decimal")) {
return DbColumnType.BIG_DECIMAL;
} else if (t.contains("clob")) {
return DbColumnType.CLOB;
} else if (t.contains("blob")) {
return DbColumnType.BLOB;
} else if (t.contains("binary")) {
return DbColumnType.BYTE_ARRAY;
} else if (t.contains("float")) {
return DbColumnType.FLOAT;
} else if (t.contains("double")) {
return DbColumnType.DOUBLE;
} else if (!t.contains("json") && !t.contains("enum")) {
if (t.contains("date") || t.contains("time") || t.contains("year")) {
byte var5;
switch(globalConfig.getDateType()) {
case ONLY_DATE:
return DbColumnType.DATE;
case SQL_PACK:
var5 = -1;
switch(t.hashCode()) {
case 3076014:
if (t.equals("date")) {
var5 = 0;
}
break;
case 3560141:
if (t.equals("time")) {
var5 = 1;
}
break;
case 3704893:
if (t.equals("year")) {
var5 = 2;
}
}
switch(var5) {
case 0:
return DbColumnType.DATE_SQL;
case 1:
return DbColumnType.TIME;
case 2:
return DbColumnType.DATE_SQL;
default:
return DbColumnType.TIMESTAMP;
}
case TIME_PACK:
var5 = -1;
switch(t.hashCode()) {
case 3076014:
if (t.equals("date")) {
var5 = 0;
}
break;
case 3560141:
if (t.equals("time")) {
var5 = 1;
}
break;
case 3704893:
if (t.equals("year")) {
var5 = 2;
}
}
switch(var5) {
case 0:
return DbColumnType.LOCAL_DATE;
case 1:
return DbColumnType.LOCAL_TIME;
case 2:
return DbColumnType.YEAR;
default:
return DbColumnType.LOCAL_DATE_TIME;
}
}
}
return DbColumnType.STRING;
} else {
return DbColumnType.STRING;
}
}
在processTypeConvert方法的实现中其实就是判断需要转换的类型是否存在, 是什么类型,如果不存在,mp的将默认将类型转换为DbColumnType.LOCAL_DATE_TIME;
“LocalDateTime”==> “java.time.LocalDateTime”