SimpleJdbcInsert使用executeAndReturnKeyHolder方法返回主键时需要注意的一个地方

Spring的SimpleJdbcInsert发挥了Simple风格,与SimpleJdbcTemplate同属于Simple体系。该类为向数据库中插入数据提供了一个非常快捷的方式,另外它还提供了一套用于返回插入数据的主键的方法:executeAndReturnKeyHolder、executeAndReturnKey。

查看API的时候可以看到executeAndReturnKey这个方法的返回类型是Number类型,当时我就再想如果主键的类型是String类型呢,比如UUID。后来看到还有一个executeAndReturnKeyHolder方法,返回的是一个KeyHolder对象,可以通过keyHolder#getKeys()获取主键的值,另外还有一个getKeyList()方法用于复合主键的情况,这里先撇开不说。

看完API之后那就可以动手了,代码如下:

// jdbcInsert是SimpleJdbcInsert对象
Map data = Maps.newHashMap();
data.put("id", "t0001");
data.put("name", "Tom");
data.put("age", 24);
KeyHolder keyHolder = jdbcInsert.withTableName("t_tablename")
.usingColumns("id", "name", "age")
.usingGeneratedKeyColumns("id")
.executeAndReturnKeyHolder(data);
// 下面主要是对keyHoder进行分析
if(keyHolder == null) {
return null;
}
Map keys = keyHolder.getKeys();
if(keys == null || keys.size() == 0 || keys.values().size() == 0) {
return null;
}
Object key = keys.values().toArray()[0];
if(key == null || !(key instanceof Serializable)) {
return null;
}
if(key instanceof Number) {
Long k = (Long)key;
return (idType == int.class || idType == Integer.class) ?
k.intValue() : k;
} else if(key instanceof String) {
return (String)key;
} else {
return (Serializable)key;
} // end of if(key instanceof Number)

如果主键id的类型是int或long上面的代码没有任何问题,但是如果是自定义的UUID等String类型则问题出现了,提示下面的错误:

org.springframework.jdbc.UncategorizedSQLException: PreparedStatementCallback; uncategorized SQLException for SQL []; SQL state [HY000]; error code [1364]; Field 'id' doesn't have a default value; nested exception is java.sql.SQLException: Field 'id' doesn't have a default value

有些奇怪了吧?明明id的值已经传入了,但是错误的提示应该是没有传入id的值。进入到Spring的源代码中,发现代码里面有一些debug信息,于是在log4j中将debug打开:

log4j.logger.org.springframework.jdbc.core=debug

从打印出来的信息中看,Spring自动生成的Insert语句中竟然没有id字段!!!继续最终源代码,先在org.springframework.jdbc.core.simple.AbstractJdbcInsert类中找到protected void compileInternal()方法,在代码前加上一个debug信息:

protected void compileInternal() {
logger.debug("getGeneratedKeyNames: " + getColumnNames());
tableMetaDataContext.processMetaData(getJdbcTemplate().getDataSource(), getColumnNames(), getGeneratedKeyNames());
...
}

这个时候打印的列表中有id字段,继续最终,最后终于在TableMetaDataContext#createInsertString(java.lang.String[])方法里面找到,关键的代码片段如下:

for (String columnName : this.getTableColumns()) {
// 这里将SimpleJdbcInsert#usingGeneratedKeyColumns方法中所设置的字段去除了
if (!keys.contains(columnName.toUpperCase())) {
columnCount++;
if (columnCount > 1) {
insertStatement.append(", ");
}
insertStatement.append(columnName);
}
}

看到这里,我也猛然恍然大悟了。既然在INSERT INTO语句中设置了UUID的值,那这里就不需要再使用KeyHolder进行返回了,直接获取就是了。这也是为什么KeyHolder中的getKey()方法的返回类型是Number的原因了,因为通常来说需要Spring返回的就是插入数据库中的自增类型的主键值。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注