软件版本 java8 spring-jdbc-4.1.6.RELEASE.jar

简单查询

首先我们来看这样一段代码。

Map map = jdbcTemplate.queryForMap("SELECT * FROM t_user WHERE user_id = '123'");

看起来只是非常简单的一句查询,它使用jdbcTemplate#queryForMap方法去查询一个user_id = '123'的用户,并将查询结果返回到Map

看似一切都很正常合理。但是,事情真的有这么简单吗?

如果有人review了你的代码突然这么反问你,那么在这简单的背后往往都暗藏了一些猫腻。

背后那些不为人知的秘密

我们先进queryForMap方法去看一下。

public Map<String, Object> queryForMap(String sql) throws DataAccessException {
	return (Map)this.queryForObject(sql, this.getColumnMapRowMapper());
}

果然,从queryForMap的方法能看到,

  1. 它抛出了一个非检查性异常DataAccessException
  2. 并且我们能发现queryForMap内部,实际是调用的queryForObject方法。

我们再进queryForObject方法去看一下。

public <T> T queryForObject(String sql, RowMapper<T> rowMapper) throws DataAccessException {
	List<T> results = this.query(sql, rowMapper);
	return DataAccessUtils.requiredSingleResult(results);
}

queryForObject的方法我们能得到一下信息,

  1. 它抛出了一个非检查性异常DataAccessException
  2. 根据方法的返回参数判断,它的返回值为单个实体,也就是说queryForMap方法的返回值也只是一条数据。
  3. 对返回值做了requiredSingleResult检查。

我们再进requiredSingleResult方法去看一下。

spring-tx-4.1.6.RELEASE.jar

public static <T> T requiredSingleResult(Collection<T> results) throws IncorrectResultSizeDataAccessException {
	int size = results != null ? results.size() : 0;
	if (size == 0) {
		throw new EmptyResultDataAccessException(1);
	} else if (results.size() > 1) {
		throw new IncorrectResultSizeDataAccessException(1, size);
	} else {
		return results.iterator().next();
	}
}

好家伙!原来坑就在这里,

  1. 如果查询结果集为空,则会抛出EmptyResultDataAccessException,
  2. 如果查询结果集大于1,则会抛出IncorrectResultSizeDataAccessException,
  3. 只有当查询结果集等于一条数据时,才能正常返回数据。

spring-tx-5.1.14.RELEASE.jar

public static <T> T requiredSingleResult(@Nullable Collection<T> results) throws IncorrectResultSizeDataAccessException {
	if (CollectionUtils.isEmpty(results)) {
		throw new EmptyResultDataAccessException(1);
	} else if (results.size() > 1) {
		throw new IncorrectResultSizeDataAccessException(1, results.size());
	} else {
		return results.iterator().next();
	}
}

@Nullable
public static <T> T nullableSingleResult(@Nullable Collection<T> results) throws IncorrectResultSizeDataAccessException {
	if (CollectionUtils.isEmpty(results)) {
		throw new EmptyResultDataAccessException(1);
	} else if (results.size() > 1) {
		throw new IncorrectResultSizeDataAccessException(1, results.size());
	} else {
		return results.iterator().next();
	}
}

这里我还贴出了 5.x 版本的源码。在 5.x 版本的queryForObject方法,是使用的nullableSingleResult方法进行结果集的校验。

nullableSingleResult翻译为可为空的单一结果,我个人觉得这个方法的命名是具有误导性的,更推荐使用requiredSingleResult

推荐的使用方式

其实对于查询结果集为空时,我们并不希望它直接抛出异常,所以我们可以把EmptyResultDataAccessException捕获一下,并返回一个空的Map。

Map<String, Object> map = null;
try {
	map = jdbcTemplate.queryForMap("SELECT * FROM t_user WHERE user_id = '123'");
} catch (EmptyResultDataAccessException e) {
	map = Collections.EMPTY_MAP;
}

小结一下

我们在使用JdbcTemplate的这两个查询API时必须要加try {} catch () {}进行处理:

  1. queryForMap
  2. queryForObject

想想,如果我们不进行异常捕获或处理,当查询结果集为空时,是很容易导致线上事故的。

彩蛋

今天对我来说是特别的一天,因为今天是我的生日!

今天我还邀请了两个同事,一起去大井巷的秀孃孃串串打了个卡,生日过的也算是蛮开心的呢。

宝贝给我说,我又老了一岁了!我永远都会比她大一岁,她好开心啊!

哈哈,确实是酱紫。

祝自己25岁生日快乐呀🎂🎂🎂!Happy birthday to me!