作者: Jet Mah from Java堂
声明: 可以非商业性任意转载, 转载时请务必以超链接形式标明文章原始出处、作者信息及此声明!
Spring的SimpleJdbcInsert发挥了Simple风格,与SimpleJdbcTemplate同属于Simple体系。该类为向数据库中插入数据提供了一个非常快捷的方式,另外它还提供了一套用于返回插入数据的主键的方法:executeAndReturnKeyHolder、executeAndReturnKey。
查看API的时候可以看到executeAndReturnKey这个方法的返回类型是Number类型,当时我就再想如果主键的类型是String类型呢,比如UUID。后来看到还有一个executeAndReturnKeyHolder方法,返回的是一个KeyHolder对象,可以通过keyHolder#getKeys()获取主键的值,另外还有一个getKeyList()方法用于复合主键的情况,这里先撇开不说。
看完API之后那就可以动手了,代码如下:
- // jdbcInsert是SimpleJdbcInsert对象
- Map<String, Object> 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<String, Object> 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返回的就是插入数据库中的自增类型的主键值。