/*
* Copyright 2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.jdbc.core;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.dao.DataAccessException;
/**
* An abstract template for row mapping operations that map multiple rows to a single object.
* This is useful when joining a one-to-many relationship where there can be multiple rows returned per root (or aggregate).
* For example, consider the relationship: "a Presentation has one-to-many Speakers".
* When joining with the Speaker table to build a Presentation object, multiple rows will be returned for a Presentation if it has more than one Speaker.
* This template is useful in that case.
* This class has been submitted for contribution to Spring JDBC; see SPR-7698.
* @author Keith Donald
* @param <R> the root, or aggregate, entity type
* @param <I> the root's id property type
*/
public abstract class JoinRowMapper<R, I> {
/**
* Return a {@link RowMapper} that maps exactly one root object R, where there may be multiple R rows for each child in a join with a one-to-many relationship.
*/
public RowMapper<R> single() {
return singleMapper;
}
/**
* Return a {@link ResultSetExtractor} that 1..n root objects R into a List where there may be multiple R rows for each joined child.
*/
public ResultSetExtractor<List<R>> list() {
return listMapper;
}
// subclassing hooks
/**
* Map the ID property I for the root entity out of the current row in the ResultSet.
*/
protected abstract I mapId(ResultSet rs) throws SQLException;
/**
* Map root object R out of the current row in the ResultSet, including its direct properties and excluding child association properties.
*/
protected abstract R mapRoot(I id, ResultSet rs) throws SQLException;
/**
* Map the next child object and add it to root object R.
*/
protected abstract void addChild(R root, ResultSet rs) throws SQLException;
// internal helpers
private final RowMapper<R> singleMapper = new RowMapper<R> () {
public R mapRow(ResultSet rs, int rowNum) throws SQLException {
return map(rs);
}
};
private final ResultSetExtractor<List<R>> listMapper = new ResultSetExtractor<List<R>>() {
public List<R> extractData(ResultSet rs) throws SQLException, DataAccessException {
return mapInto(new ArrayList<R>(), rs);
}
};
private R map(ResultSet rs) throws SQLException {
I id = mapId(rs);
R root = mapRoot(id, rs);
addChild(root, rs);
while (rs.next() && mapId(rs).equals(id)) {
addChild(root, rs);
}
return root;
}
private <C extends Collection<R>> C mapInto(C collection, ResultSet rs) throws SQLException {
R root = null;
I previousId = null;
while (rs.next()) {
I id = mapId(rs);
if (!id.equals(previousId)) {
root = mapRoot(id, rs);
collection.add(root);
}
addChild(root, rs);
previousId = id;
}
return collection;
}
}