Spring JDBC采用模板的设计模式来完成设计。抽象类中定义模板方法,在模板方法中对处理过程进行描述,然后每个具体的过程实现则交由子类来实现。
1,设计原理
在Spring JDBC中,JdbcTemplate是一个主要的模板类,该类继承JdbcAccessor,实现JdbcOperation接口。
在JdbcAccessor中对DataSource进行管和配置。
JdbcOperation接口则定义了操作数据库的基本方法。
2,JdbcTemplate的基本使用
使用JdbcTemplate,可以直接进行对数据库操作的调用,忽略异常处理以及建立连接,数据结果处理等一系列操作。
3,JdbcTemplate的execute实现。
如下为JdbcTemplate的execute方法源码:
@Override
public void execute(final String sql) throws DataAccessException {
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL statement [" + sql + "]");
}
class ExecuteStatementCallback implements StatementCallback<Object>, SqlProvider {
@Override
public Object doInStatement(Statement stmt) throws SQLException {//重写
stmt.execute(sql);
return null;
}
@Override
public String getSql() {
return sql;
}
}
execute(new ExecuteStatementCallback());
}
@Override
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(getDataSource());//获取COnnection
Statement stmt = null;
try {
Connection conToUse = con;
if (this.nativeJdbcExtractor != null &&
this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
stmt = conToUse.createStatement();//Statement
applyStatementSettings(stmt);
Statement stmtToUse = stmt;
if (this.nativeJdbcExtractor != null) {
stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
}
T result = action.doInStatement(stmtToUse);//调用传入接口的子类的实现
handleWarnings(stmt);
return result;
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
}
finally {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
4,JdbcTemplate的query方法
@Override
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 {
@Override
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);
}
}
@Override
public String getSql() {
return sql;
}
}
return execute(new QueryStatementCallback());
}
我们查看在execute方法中的,发现最终调用的是doInStatement,该方法均在内部类中重写。
5,使用Connection。
Spring通过DataSourceUtils对Connection进行管理。在数据库应用中,数据库的Connection的使用往往和事务处理相关。
public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
try {
return doGetConnection(dataSource);
}
catch (SQLException ex) {
throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
}
}
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);//如果已经存在与当前线程绑定的数据源,则直接取出
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
conHolder.setConnection(dataSource.getConnection());
}
return conHolder.getConnection();
}
// Else we either got no holder or an empty thread-bound holder here.
logger.debug("Fetching JDBC Connection from DataSource");
Connection con = dataSource.getConnection();
if (TransactionSynchronizationManager.isSynchronizationActive()) {
logger.debug("Registering transaction synchronization for JDBC Connection");
// Use same Connection for further JDBC actions within the transaction.
// Thread-bound object will get removed by synchronization at transaction completion.
ConnectionHolder holderToUse = conHolder;
if (holderToUse == null) {
holderToUse = new ConnectionHolder(con);
}
else {
holderToUse.setConnection(con);
}
holderToUse.requested();
TransactionSynchronizationManager.registerSynchronization(
new ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse != conHolder) {
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
}
}
return con;
}
具体的dataSource对象则通过IoC容器实现注入。
1,SqlQuery的实现
Spring除了提供基本操作外,还提供一些O/R映射基本,比如MappingSqlQuery。在代码中,我们往往实现自己的类来实现数据库到POJO的映射,具体的实现省略。
public abstract class MappingSqlQuery<T> extends MappingSqlQueryWithParameters<T> {
/**
* Constructor that allows use as a JavaBean.
*/
public MappingSqlQuery() {
}
/**
* Convenient constructor with DataSource and SQL string.
* @param ds DataSource to use to obtain connections
* @param sql SQL to run
*/
public MappingSqlQuery(DataSource ds, String sql) {
super(ds, sql);
}
/**
* This method is implemented to invoke the simpler mapRow
* template method, ignoring parameters.
* @see #mapRow(ResultSet, int)
*/
@Override
protected final T mapRow(ResultSet rs, int rowNum, Object[] parameters, Map<?, ?> context)
throws SQLException {
return mapRow(rs, rowNum);
}
/**
* Subclasses must implement this method to convert each row of the
* ResultSet into an object of the result type.
* <p>Subclasses of this class, as opposed to direct subclasses of
* MappingSqlQueryWithParameters, don't need to concern themselves
* with the parameters to the execute method of the query object.
* @param rs ResultSet we're working through
* @param rowNum row number (from 0) we're up to
* @return an object of the result type
* @throws SQLException if there's an error extracting data.
* Subclasses can simply not catch SQLExceptions, relying on the
* framework to clean up.
*/
protected abstract T mapRow(ResultSet rs, int rowNum) throws SQLException;
}
以RdbmsOperation为例。
public void declareParameter(SqlParameter param) throws InvalidDataAccessApiUsageException {
if (isCompiled()) {
throw new InvalidDataAccessApiUsageException("Cannot add parameters once the query is compiled");
}
this.declaredParameters.add(param);
}
private final List<SqlParameter> declaredParameters = new LinkedList<SqlParameter>();
查看对应的compile操作
/**
* Compile this query.
* Ignores subsequent attempts to compile.
* @throws InvalidDataAccessApiUsageException if the object hasn't
* been correctly initialized, for example if no DataSource has been provided
*/
public final void compile() throws InvalidDataAccessApiUsageException {
if (!isCompiled()) {
if (getSql() == null) {
throw new InvalidDataAccessApiUsageException("Property 'sql' is required");
}
try {
this.jdbcTemplate.afterPropertiesSet();
}
catch (IllegalArgumentException ex) {
throw new InvalidDataAccessApiUsageException(ex.getMessage());
}
compileInternal();
this.compiled = true;
if (logger.isDebugEnabled()) {
logger.debug("RdbmsOperation with SQL [" + getSql() + "] compiled");
}
}
}
compileInternal操作在SqlOperation中完成。
* Overridden method to configure the PreparedStatementCreatorFactory
* based on our declared parameters.
*/
@Override
protected final void compileInternal() {
this.preparedStatementFactory = new PreparedStatementCreatorFactory(getSql(), getDeclaredParameters());
this.preparedStatementFactory.setResultSetType(getResultSetType());
this.preparedStatementFactory.setUpdatableResults(isUpdatableResults());
this.preparedStatementFactory.setReturnGeneratedKeys(isReturnGeneratedKeys());
if (getGeneratedKeysColumnNames() != null) {
this.preparedStatementFactory.setGeneratedKeysColumnNames(getGeneratedKeysColumnNames());
}
this.preparedStatementFactory.setNativeJdbcExtractor(getJdbcTemplate().getNativeJdbcExtractor());
onCompileInternal();
}
在compile之后,执行查询时,执行的是SqlQuery的executeByNamedParam方法。
public List<T> executeByNamedParam(Map<String, ?> paramMap, Map<?, ?> context) throws DataAccessException {
validateNamedParameters(paramMap);
ParsedSql parsedSql = getParsedSql();//获取到要执行的sql
MapSqlParameterSource paramSource = new MapSqlParameterSource(paramMap);
String sqlToUse = NamedParameterUtils.substituteNamedParameters(parsedSql, paramSource);
//配置好sql需要的parameter及RowMapper
Object[] params = NamedParameterUtils.buildValueArray(parsedSql, paramSource, getDeclaredParameters());
RowMapper<T> rowMapper = newRowMapper(params, context);
//JdbcTemplate进行查询
return getJdbcTemplate().query(newPreparedStatementCreator(sqlToUse, params), rowMapper);
}
以上就是基本的源码。其中还涉及到一些我们常用的其他源码,以及对Hibernate的封装。个人觉得Hibernate过于繁琐,以及HQL语句对sql的疯转反而过于严重,不是很喜欢,所以对Spring 对Hibernate的源码块跳过。
ps:Spring对数据库JdbcTemplate的封装较为简单。终于不再像看IoC和AOP那样子晕了。