《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那樣子暈了。




注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com