《Spring技术内幕》笔记-第五章 数据库操作组件的实现

Spring JDBC的设计与实现

    ​Spring JDBC采用模板的设计模式来完成设计。抽象类中定义模板方法,在模板方法中对处理过程进行描述,然后每个具体的过程实现则交由子类来实现。

Spring JDBC模板类的设计与实现

1,设计原理

    ​在Spring JDBC中,JdbcTemplate是一个主要的模板类,该类继承JdbcAccessor,实现JdbcOperation接口。

    ​在JdbcAccessor中对DataSource进行管和配置。

    ​JdbcOperation接口则定义了操作数据库的基本方法。

2,JdbcTemplate的基本使用

    ​使用JdbcTemplate,可以直接进行对数据库操作的调用,忽略异常处理以及建立连接,数据结果处理等一系列操作。

3,JdbcTemplate的execute实现。

如下为JdbcTemplate的execute方法源码:

   
   
   
   
  1. @Override
  2. public void execute(final String sql) throws DataAccessException {
  3. if (logger.isDebugEnabled()) {
  4. logger.debug("Executing SQL statement [" + sql + "]");
  5. }
  6. class ExecuteStatementCallback implements StatementCallback<Object>, SqlProvider {
  7. @Override
  8. public Object doInStatement(Statement stmt) throws SQLException {//重写
  9. stmt.execute(sql);
  10. return null;
  11. }
  12. @Override
  13. public String getSql() {
  14. return sql;
  15. }
  16. }
  17. execute(new ExecuteStatementCallback());
  18. }
   
   
   
   
  1. @Override
  2. public <T> T execute(StatementCallback<T> action) throws DataAccessException {
  3. Assert.notNull(action, "Callback object must not be null");
  4. Connection con = DataSourceUtils.getConnection(getDataSource());//获取COnnection
  5. Statement stmt = null;
  6. try {
  7. Connection conToUse = con;
  8. if (this.nativeJdbcExtractor != null &&
  9. this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
  10. conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
  11. }
  12. stmt = conToUse.createStatement();//Statement
  13. applyStatementSettings(stmt);
  14. Statement stmtToUse = stmt;
  15. if (this.nativeJdbcExtractor != null) {
  16. stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
  17. }
  18. T result = action.doInStatement(stmtToUse);//调用传入接口的子类的实现
  19. handleWarnings(stmt);
  20. return result;
  21. }
  22. catch (SQLException ex) {
  23. // Release Connection early, to avoid potential connection pool deadlock
  24. // in the case when the exception translator hasn't been initialized yet.
  25. JdbcUtils.closeStatement(stmt);
  26. stmt = null;
  27. DataSourceUtils.releaseConnection(con, getDataSource());
  28. con = null;
  29. throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
  30. }
  31. finally {
  32. JdbcUtils.closeStatement(stmt);
  33. DataSourceUtils.releaseConnection(con, getDataSource());
  34. }
  35. }

4,JdbcTemplate的query方法

   
   
   
   
  1. @Override
  2. public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
  3. Assert.notNull(sql, "SQL must not be null");
  4. Assert.notNull(rse, "ResultSetExtractor must not be null");
  5. if (logger.isDebugEnabled()) {
  6. logger.debug("Executing SQL query [" + sql + "]");
  7. }
  8. class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
  9. @Override
  10. public T doInStatement(Statement stmt) throws SQLException {//内部类重写此方法
  11. ResultSet rs = null;
  12. try {
  13. rs = stmt.executeQuery(sql);
  14. ResultSet rsToUse = rs;
  15. if (nativeJdbcExtractor != null) {
  16. rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
  17. }
  18. return rse.extractData(rsToUse);
  19. }
  20. finally {
  21. JdbcUtils.closeResultSet(rs);
  22. }
  23. }
  24. @Override
  25. public String getSql() {
  26. return sql;
  27. }
  28. }
  29. return execute(new QueryStatementCallback());
  30. }

我们查看在execute方法中的,发现最终调用的是doInStatement,该方法均在内部类中重写。

5,使用Connection。

    ​Spring通过DataSourceUtils对Connection进行管理。在数据库应用中,数据库的Connection的使用往往和事务处理相关。


   
   
   
   
  1. public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
  2. try {
  3. return doGetConnection(dataSource);
  4. }
  5. catch (SQLException ex) {
  6. throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
  7. }
  8. }

   
   
   
   
  1. public static Connection doGetConnection(DataSource dataSource) throws SQLException {
  2. Assert.notNull(dataSource, "No DataSource specified");
  3. ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);//如果已经存在与当前线程绑定的数据源,则直接取出
  4. if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
  5. conHolder.requested();
  6. if (!conHolder.hasConnection()) {
  7. logger.debug("Fetching resumed JDBC Connection from DataSource");
  8. conHolder.setConnection(dataSource.getConnection());
  9. }
  10. return conHolder.getConnection();
  11. }
  12. // Else we either got no holder or an empty thread-bound holder here.
  13. logger.debug("Fetching JDBC Connection from DataSource");
  14. Connection con = dataSource.getConnection();
  15. if (TransactionSynchronizationManager.isSynchronizationActive()) {
  16. logger.debug("Registering transaction synchronization for JDBC Connection");
  17. // Use same Connection for further JDBC actions within the transaction.
  18. // Thread-bound object will get removed by synchronization at transaction completion.
  19. ConnectionHolder holderToUse = conHolder;
  20. if (holderToUse == null) {
  21. holderToUse = new ConnectionHolder(con);
  22. }
  23. else {
  24. holderToUse.setConnection(con);
  25. }
  26. holderToUse.requested();
  27. TransactionSynchronizationManager.registerSynchronization(
  28. new ConnectionSynchronization(holderToUse, dataSource));
  29. holderToUse.setSynchronizedWithTransaction(true);
  30. if (holderToUse != conHolder) {
  31. TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
  32. }
  33. }
  34. return con;
  35. }

具体的dataSource对象则通过IoC容器实现注入。


Spring JDBC中的RDBMS操作

​1,SqlQuery的实现

    ​Spring除了提供基本操作外,还提供一些O/R映射基本,比如MappingSqlQuery。在代码中,我们往往实现自己的类来实现数据库到POJO的映射,具体的实现省略。

    ​

   
   
   
   
  1. public abstract class MappingSqlQuery<T> extends MappingSqlQueryWithParameters<T> {
  2. /**
  3. * Constructor that allows use as a JavaBean.
  4. */
  5. public MappingSqlQuery() {
  6. }
  7. /**
  8. * Convenient constructor with DataSource and SQL string.
  9. * @param ds DataSource to use to obtain connections
  10. * @param sql SQL to run
  11. */
  12. public MappingSqlQuery(DataSource ds, String sql) {
  13. super(ds, sql);
  14. }
  15. /**
  16. * This method is implemented to invoke the simpler mapRow
  17. * template method, ignoring parameters.
  18. * @see #mapRow(ResultSet, int)
  19. */
  20. @Override
  21. protected final T mapRow(ResultSet rs, int rowNum, Object[] parameters, Map<?, ?> context)
  22. throws SQLException {
  23. return mapRow(rs, rowNum);
  24. }
  25. /**
  26. * Subclasses must implement this method to convert each row of the
  27. * ResultSet into an object of the result type.
  28. * <p>Subclasses of this class, as opposed to direct subclasses of
  29. * MappingSqlQueryWithParameters, don't need to concern themselves
  30. * with the parameters to the execute method of the query object.
  31. * @param rs ResultSet we're working through
  32. * @param rowNum row number (from 0) we're up to
  33. * @return an object of the result type
  34. * @throws SQLException if there's an error extracting data.
  35. * Subclasses can simply not catch SQLExceptions, relying on the
  36. * framework to clean up.
  37. */
  38. protected abstract T mapRow(ResultSet rs, int rowNum) throws SQLException;
  39. }

以RdbmsOperation为例。

   
   
   
   
  1. public void declareParameter(SqlParameter param) throws InvalidDataAccessApiUsageException {
  2. if (isCompiled()) {
  3. throw new InvalidDataAccessApiUsageException("Cannot add parameters once the query is compiled");
  4. }
  5. this.declaredParameters.add(param);
  6. }

   
   
   
   
  1. private final List<SqlParameter> declaredParameters = new LinkedList<SqlParameter>();

查看对应的compile操作

   
   
   
   
  1. /**
  2. * Compile this query.
  3. * Ignores subsequent attempts to compile.
  4. * @throws InvalidDataAccessApiUsageException if the object hasn't
  5. * been correctly initialized, for example if no DataSource has been provided
  6. */
  7. public final void compile() throws InvalidDataAccessApiUsageException {
  8. if (!isCompiled()) {
  9. if (getSql() == null) {
  10. throw new InvalidDataAccessApiUsageException("Property 'sql' is required");
  11. }
  12. try {
  13. this.jdbcTemplate.afterPropertiesSet();
  14. }
  15. catch (IllegalArgumentException ex) {
  16. throw new InvalidDataAccessApiUsageException(ex.getMessage());
  17. }
  18. compileInternal();
  19. this.compiled = true;
  20. if (logger.isDebugEnabled()) {
  21. logger.debug("RdbmsOperation with SQL [" + getSql() + "] compiled");
  22. }
  23. }
  24. }

compileInternal操作在SqlOperation中完成。


   
   
   
   
  1. * Overridden method to configure the PreparedStatementCreatorFactory
  2. * based on our declared parameters.
  3. */
  4. @Override
  5. protected final void compileInternal() {
  6. this.preparedStatementFactory = new PreparedStatementCreatorFactory(getSql(), getDeclaredParameters());
  7. this.preparedStatementFactory.setResultSetType(getResultSetType());
  8. this.preparedStatementFactory.setUpdatableResults(isUpdatableResults());
  9. this.preparedStatementFactory.setReturnGeneratedKeys(isReturnGeneratedKeys());
  10. if (getGeneratedKeysColumnNames() != null) {
  11. this.preparedStatementFactory.setGeneratedKeysColumnNames(getGeneratedKeysColumnNames());
  12. }
  13. this.preparedStatementFactory.setNativeJdbcExtractor(getJdbcTemplate().getNativeJdbcExtractor());
  14. onCompileInternal();
  15. }

在compile之后,执行查询时,执行的是SqlQuery的executeByNamedParam方法。

   
   
   
   
  1. public List<T> executeByNamedParam(Map<String, ?> paramMap, Map<?, ?> context) throws DataAccessException {
  2. validateNamedParameters(paramMap);
  3. ParsedSql parsedSql = getParsedSql();//获取到要执行的sql
  4. MapSqlParameterSource paramSource = new MapSqlParameterSource(paramMap);
  5. String sqlToUse = NamedParameterUtils.substituteNamedParameters(parsedSql, paramSource);

  6. //配置好sql需要的parameter及RowMapper

  7. Object[] params = NamedParameterUtils.buildValueArray(parsedSql, paramSource, getDeclaredParameters());
  8. RowMapper<T> rowMapper = newRowMapper(params, context);

  9. //JdbcTemplate进行查询

  10. return getJdbcTemplate().query(newPreparedStatementCreator(sqlToUse, params), rowMapper);
  11. }

 以上就是基本的源码。其中还涉及到一些我们常用的其他源码,以及对Hibernate的封装。个人觉得Hibernate过于繁琐,以及HQL语句对sql的疯转反而过于严重,不是很喜欢,所以对Spring 对Hibernate的源码块跳过。

    ps:​Spring对数据库JdbcTemplate的封装较为简单。终于不再像看IoC和AOP那样子晕了。



你可能感兴趣的:(《Spring技术内幕》笔记-第五章 数据库操作组件的实现)