spring JdbcTemplate 一

今天在学springboot的时候,学到了数据访问这块,然后在写了一个demo,随手就用springboot-data-starter中自带的JdbcTemplate模板。用了之后发现总是查询不到所有的数据。

实体类:

public class User {

    private Long id;
    private String username;
    private String password;
    private Integer score;
    //省略get,set方法
    //省略toString方法
}

Dao层:

@Component
public class UserDaoImpl implements IUserDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public List findList(){
        String sql = "select * from user";
        List users = jdbcTemplate.queryForList(sql,User.class);
        return users;
    }

}

然后发现执行sql语句,总是出错:

org.springframework.jdbc.IncorrectResultSetColumnCountException: Incorrect column count: expected 1, actual 4
	at org.springframework.jdbc.core.SingleColumnRowMapper.mapRow(SingleColumnRowMapper.java:92) ~[spring-jdbc-4.3.23.RELEASE.jar:4.3.23.RELEASE]
	at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:93) ~[spring-jdbc-4.3.23.RELEASE.jar:4.3.23.RELEASE]

异常显示  错误的列数:预期的1,实际的4

本来刚开始看到这个异常的时候,一时没反应过来什么错误,因为一直是用惯了MyBaits来操作数据库,看到JdbcTemplate模板有个queryForList方法,就简单的以为是把查询结果转成List集合,用了(sql,entityClass)这个方法。然后就出异常了。

翻了翻源码,才发现这里是用于从单个列读取结果对象。

/**
	 * Create a new RowMapper for reading result objects from a single column.
	 * @param requiredType the type that each result object is expected to match
	 * @return the RowMapper to use
	 * @see SingleColumnRowMapper
     * 注释翻译:创建一个新的行映射器,用于从单个列读取结果对象。
	 */
	protected  RowMapper getSingleColumnRowMapper(Class requiredType) {
		return new SingleColumnRowMapper(requiredType);
	}

那么就清楚了,单个列只能是基础类型或者String复杂类型,比如Integer.class,String.class。

那么在看看源码,找到了这个:
 

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

 

这个是查询数据,然后返回值是个Object对象。而DataAccessUtils.requiredSingleResult(results)方法是干什么的呢?看注释:

/**
	 * Return a single result object from the given Collection.
	 * 

Throws an exception if 0 or more than 1 element found. * @param results the result Collection (can be {@code null}) * @return the single result object * @throws IncorrectResultSizeDataAccessException if more than one * element has been found in the given Collection * @throws EmptyResultDataAccessException if no element at all * has been found in the given Collection * 从给定集合中返回单个result对象。 * 抛出一个异常,如果发现0或超过1个元素 */ public static T requiredSingleResult(Collection results) throws IncorrectResultSizeDataAccessException { if (CollectionUtils.isEmpty(results)) { throw new EmptyResultDataAccessException(1); } if (results.size() > 1) { throw new IncorrectResultSizeDataAccessException(1, results.size()); } return results.iterator().next(); }

那么我们就明白了,这个方法只能返回一条数据。所以这个方法是先查询全部的数据,然后在判断结果集里数据的长度是否为0或者大于1,就抛异常。

所以我们要查询只查询一条数据就可以用这个

queryForObject(String sql, RowMapper rowMapper)

方法,而查询全部呢,肯定是这个方法

List results = query(sql, rowMapper);

那么这个rowMapper是什么对象呢,继续分析源码:

public interface RowMapper {

	/**
	 * Implementations must implement this method to map each row of data
	 * in the ResultSet. This method should not call {@code next()} on
	 * the ResultSet; it is only supposed to map values of the current row.
	 * @param rs the ResultSet to map (pre-initialized for the current row)
	 * @param rowNum the number of the current row
	 * @return the result object for the current row (may be {@code null})
	 * @throws SQLException if a SQLException is encountered getting
	 * column values (that is, there's no need to catch SQLException)
     * 实现必须实现此方法来映射每一行数据结果集。此方法不应在上调用结果集;它只应该映射当前行的值。
	 */
	T mapRow(ResultSet rs, int rowNum) throws SQLException;

}

RowMapper是一个接口,里面只有一个方法,注释上给咱们写的清清楚楚:映射每一行数据结果集。

那么我们就需要自己来实现这个接口,这个接口只有这一个方法,而这个方法的参数大家肯定感到非常熟悉。

Java的原生JDBC在执行完sql语句,所返回的结果集就是一个java.sql.ResultSet对象,我们对这个结果集可以根据下标或者字段名的形式来取值。

public class UserRowMapper implements RowMapper {
    @Override
    public User mapRow(ResultSet rs, int rowNum) throws SQLException {
        User user = new User();
        user.setId(rs.getLong(1));
        user.setUsername(rs.getString(2));
        user.setPassword(rs.getString(3));
        user.setScore(rs.getInt(4));
        return user;
    }
}

写完这个类之后,然后我们再改一下dao层的方法。

public List findList(){
        String sql = "select * from user";
        List users = jdbcTemplate.query(sql,new UserRowMapper());
        return users;
    }

然后再次重启程序,就可以得到数据了。

[{"id":1,"username":"tom","password":"123","score":81},{"id":2,"username":"root","password":"123456","score":80}]

因为我正在学springboot这个约定大于配置的框架。就想看看spring有没有写好的rowMapper实现类以供我们使用呢?

打开RowMapper这个接口的继承树,发现还真有。。。(我自己都没想到会有)

spring JdbcTemplate 一_第1张图片

实现类还不少。

RowMapper有一个实现类是SingleColumnRowMapper。

public class SingleColumnRowMapper implements RowMapper {
	@Override
	@SuppressWarnings("unchecked")
	public T mapRow(ResultSet rs, int rowNum) throws SQLException {
		// Validate column count.
		ResultSetMetaData rsmd = rs.getMetaData();
		int nrOfColumns = rsmd.getColumnCount();
		if (nrOfColumns != 1) {
			throw new IncorrectResultSetColumnCountException(1, nrOfColumns);
		}
               //列数只要不等于1就抛异常
		// Extract column value from JDBC ResultSet.
		Object result = getColumnValue(rs, 1, this.requiredType);
		if (result != null && this.requiredType != null && !this.requiredType.isInstance(result)) {
			// Extracted value does not match already: try to convert it.
			try {
				return (T) convertValueToRequiredType(result, this.requiredType);
			}
			catch (IllegalArgumentException ex) {
				throw new TypeMismatchDataAccessException(
						"Type mismatch affecting row number " + rowNum + " and column type '" +
						rsmd.getColumnTypeName(1) + "': " + ex.getMessage());
			}
		}
		return (T) result;
	}

看类名就大概可以猜到只能返回单一的列。多的少的都不行,都会抛出异常。而ColumnMapRowMapper这个类指的是把结果集映射成一个Map对象来返回:

public class ColumnMapRowMapper implements RowMapper> {

	@Override
	public Map mapRow(ResultSet rs, int rowNum) throws SQLException {
		ResultSetMetaData rsmd = rs.getMetaData();
		int columnCount = rsmd.getColumnCount();
		Map mapOfColumnValues = createColumnMap(columnCount);
		for (int i = 1; i <= columnCount; i++) {
			String column = JdbcUtils.lookupColumnName(rsmd, i);
			mapOfColumnValues.put(getColumnKey(column), getColumnValue(rs, i));
		}
		return mapOfColumnValues;
	}

mapRow方法里是new了一个Map对象,然后一个一个填充key和value值。

而我们需要的返回值是个实体类,是这个类BeanPropertyRowMapper(看类名就看出来啦,返回值肯定是JavaBean)。

修改代码:

public List findList(){
        String sql = "select * from user";
        List users = jdbcTemplate.query(sql,new BeanPropertyRowMapper());
        return users;
    }

然后就报错了。。。

java.lang.IllegalStateException: Mapped class was not specified

意思是说未指定映射类,可是我们不是加了泛型为User类的么,在看看这个类所实现的mapRow方法具体实现:

/**
	 * Extract the values for all columns in the current row.
	 * 

Utilizes public setters and result set meta-data. * @see java.sql.ResultSetMetaData */ @Override public T mapRow(ResultSet rs, int rowNumber) throws SQLException { Assert.state(this.mappedClass != null, "Mapped class was not specified"); T mappedObject = BeanUtils.instantiateClass(this.mappedClass); BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(mappedObject); initBeanWrapper(bw); ResultSetMetaData rsmd = rs.getMetaData(); int columnCount = rsmd.getColumnCount(); Set populatedProperties = (isCheckFullyPopulated() ? new HashSet() : null); //省略下面的代码,太多了 }

这个方法第一行就是断言。

我们发现异常就是这里抛出的。是因为

this.mappedClass != null

那么我们就需要给这个参数传递一个映射类。

哟,看了看构造方法,刚好有这个参数的构造器。

改代码:

public List findList(){
        String sql = "select * from user";
        List users = jdbcTemplate.query(sql,new BeanPropertyRowMapper(User.class));
        return users;
    }

运行结果:

[{"id":1,"username":"tom","password":"123","score":81},{"id":2,"username":"root","password":"123456","score":80}]

nice,总结几点:

1.也算是给我自己的建议吧,以后看到错误,最好是多看源码,多分析,多思考,不能毛毛躁躁的了。

2.在映射类的结果上,归根究底还是RowMapper这个接口,底层数据库表和Java实体类映射,还是ResultSet游标来操作的。

3.spring为我们实现了多种情况的实现类,我们可以自己实现这个接口,也可能用spring为我们实现的。

你可能感兴趣的:(Spring大家族,JdbcTemplate)