首页 > 代码库 > Spring JDBC查询返回对象代码跟踪

Spring JDBC查询返回对象代码跟踪

  在封装方法的时候突然发现通过 ResultSetMetaData的getColumnCount()获取到的列明会多一列(ROWSTAT),而且每次的值都是1,目前没有找到相关信息,在国外网站上看到有类似的情况,但是都没有人回答。于是想到spring 的JDBC部分是怎么实现映射的,于是通过spring的源代码发现了大致的流程:

(这里先说明一下自己得到收获:spring的query查询返回对象T的方法是首先获取要返回对象的所有的writeMethod,也就是set方法,然后存放在一个PropertyDescriptor的数组中,然后把有set方法的字段存放在一个类型为Map(String,String)的mappedFields变量中,通过ResultSetMetaData的getColumnCount获取列数,然后遍历列数获取列的名称,通过列的名称从mappedFields中获取该字段的set方法进行对象属性赋值。)

这里我是的方法入口是query(sql, new BeanPropertyRowMapper(voClass))

于是跟踪到

public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {		Assert.notNull(sql, "SQL must not be null");		Assert.notNull(rse, "ResultSetExtractor must not be null");		if (logger.isDebugEnabled()) {			logger.debug("Executing SQL query [" + sql + "]");		}		class QueryStatementCallback implements StatementCallback<T>, SqlProvider {			public T doInStatement(Statement stmt) throws SQLException {				ResultSet rs = null;				try {					rs = stmt.executeQuery(sql);					ResultSet rsToUse = rs;					if (nativeJdbcExtractor != null) {						rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);					}					return rse.extractData(rsToUse);				}				finally {					JdbcUtils.closeResultSet(rs);				}			}			public String getSql() {				return sql;			}		}		return execute(new QueryStatementCallback());	}

  该方法返回就已经是T对象了,可以看到是return rse.extractData(rsToUse);这个代码起了作用,于是继续跟踪extractData方法,这里发现参数ResultSetExtractor<T> rse是一个接口,于是返回调用query方法的上一层发现代码如下:

public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {		return query(sql, new RowMapperResultSetExtractor<T>(rowMapper));	}

  参数是RowMapperResultSetMapper对象,继续进入该对象内部查看extractData方法:

public List<T> extractData(ResultSet rs) throws SQLException {		List<T> results = (this.rowsExpected > 0 ? new ArrayList<T>(this.rowsExpected) : new ArrayList<T>());		int rowNum = 0;		while (rs.next()) {			results.add(this.rowMapper.mapRow(rs, rowNum++));		}		return results;	}

  发现是RowMapper的mapRow方法把一行记录映射成一个对象了,这里的rowMaper是一个接口,于是需要我们返回上层看看谁实现了该接口,于是发现在我们调用的地方传入了一个new BeanPropertyRowMapper(voClass)对象,到这个对象内部看看,首先看到构造函数:

public BeanPropertyRowMapper(Class<T> mappedClass) {		initialize(mappedClass);	}protected void initialize(Class<T> mappedClass) {		this.mappedClass = mappedClass;		this.mappedFields = new HashMap<String, PropertyDescriptor>();		this.mappedProperties = new HashSet<String>();		PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(mappedClass);		for (PropertyDescriptor pd : pds) {			if (pd.getWriteMethod() != null) {				this.mappedFields.put(pd.getName().toLowerCase(), pd);				String underscoredName = underscoreName(pd.getName());				if (!pd.getName().toLowerCase().equals(underscoredName)) {					this.mappedFields.put(underscoredName, pd);				}				this.mappedProperties.add(pd.getName());			}		}	}

  从构造函数中看出是通过initialize方法来实现voClass中字段和set方法保存在mappedFields和pds的变量中,然后我们再找mapRow这个方法,该方法就是设置vo的属性值

public T mapRow(ResultSet rs, int rowNumber) throws SQLException {		Assert.state(this.mappedClass != null, "Mapped class was not specified");		T mappedObject = BeanUtils.instantiate(this.mappedClass);		BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(mappedObject);		initBeanWrapper(bw);		ResultSetMetaData rsmd = rs.getMetaData();		int columnCount = rsmd.getColumnCount();		Set<String> populatedProperties = (isCheckFullyPopulated() ? new HashSet<String>() : null);		for (int index = 1; index <= columnCount; index++) {			String column = JdbcUtils.lookupColumnName(rsmd, index);			PropertyDescriptor pd = this.mappedFields.get(column.replaceAll(" ", "").toLowerCase());			if (pd != null) {				try {					Object value = http://www.mamicode.com/getColumnValue(rs, index, pd);"Mapping column ‘" + column + "‘ to property ‘" +								pd.getName() + "‘ of type " + pd.getPropertyType());					}					try {						bw.setPropertyValue(pd.getName(), value);					}					catch (TypeMismatchException e) {						if (value =http://www.mamicode.com/= null && primitivesDefaultedForNullValue) {"Intercepted TypeMismatchException for row " + rowNumber +									" and column ‘" + column + "‘ with value " + value +									" when setting property ‘" + pd.getName() + "‘ of type " + pd.getPropertyType() +									" on object: " + mappedObject);						}						else {							throw e;						}					}					if (populatedProperties != null) {						populatedProperties.add(pd.getName());					}				}				catch (NotWritablePropertyException ex) {					throw new DataRetrievalFailureException(							"Unable to map column " + column + " to property " + pd.getName(), ex);				}			}		}		if (populatedProperties != null && !populatedProperties.equals(this.mappedProperties)) {			throw new InvalidDataAccessApiUsageException("Given ResultSet does not contain all fields " +					"necessary to populate object of class [" + this.mappedClass + "]: " + this.mappedProperties);		}		return mappedObject;	}

  从该方法中可以看到spring是先获取列明,根据列明找到字段,通过字段的set方法为vo设置值。这个就是spring返回对象的流程。

Spring JDBC查询返回对象代码跟踪