Application of Template Pattern in Spring

foreword

The template mode is widely used in Spring. Here, we will combine the source code of JdbcTemplate to learn with you and get a deeper understanding of the template mode, so that we can use the template mode flexibly in daily development to reduce repetitive code and enhance the scalability of the code.

What is template mode

Classic template method definition:

The parent class defines the skeleton (which methods are called and in which order), and some specific methods are implemented by the subclass. The biggest benefit: code reuse, reducing duplication of code. Except for the specific methods to be implemented by the subclass, other methods and the order of method calls are pre-written in the parent class. So there are two types of methods in the parent class template method:

Common method:  code used by all subclasses

Different methods:  The methods to be overridden by subclasses are divided into two types:

  • Abstract method: the parent class is an abstract method, and the subclass must override it
  • Hook method: It is an empty method in the parent class, and the subclass inherits the default and is also empty

Note: Why is it called a hook? Subclasses can control the parent class through this hook (method), because this hook is actually a method (empty method) of the parent class!

public abstract class AbstractTemplate {
    
    public void templateMethod() {
        // 执行一些通用的操作
        
        step1();
        step2();
        step3();
        
        // 执行一些通用的操作
    }
    
    protected abstract void step1();
    
    protected abstract void step2();
    
    protected abstract void step3();
}

public class ConcreteTemplate extends AbstractTemplate {
    
    protected void step1() {
        // 实现具体的业务逻辑
    }
    
    protected void step2() {
        // 实现具体的业务逻辑
    }
    
    protected void step3() {
        // 实现具体的业务逻辑
    }
}

public class Client {
    public static void main(String[] args) {
        AbstractTemplate template = new ConcreteTemplate();
        template.templateMethod();
    }
}

In the above example, AbstractTemplate is a template class that defines a templateMethod() method as a template method. This method contains common operations and calls three abstract methods (step1(), step2(), step3()) to complete specific business logic. ConcreteTemplate is a concrete template class that implements three abstract methods. In the Client class, we instantiate the ConcreteTemplate object and call the templateMethod() method, thus completing the use of the entire template method pattern. By using the template method pattern, we can customize specific business logic by implementing different callback methods without changing the template class in Spring, so as to achieve the purpose of code reuse and extension.

How does Spring use the template pattern?

The essence of the Spring template method pattern: it is the combination of the template method pattern and the callback pattern, and it is another implementation of Template Method that does not need to be inherited. Almost all of Spring's add-in extensions use this mode. Let's take a look at the class diagram of JdbcTemplate

 Define the execute abstract method in the JdbcOperations interface

The execute method is implemented in JdbcTemplate

@Override
	public <T> T execute(StatementCallback<T> action) throws DataAccessException {
		Assert.notNull(action, "Callback object must not be null");

		Connection con = DataSourceUtils.getConnection(getDataSource());
		Statement stmt = null;
		try {
			Connection conToUse = con;
			if (this.nativeJdbcExtractor != null &&
					this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
				conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
			}
			stmt = conToUse.createStatement();
			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());
		}
	}

 In the above method, some common codes for establishing a link, creating a Statement object, releasing the Statement object, and closing the link are all fixed logic. The actual changed logic is in T result = action.doInStatement(stmtToUse); pay attention to the action here, which is a callback object, we can look at the definition of this object

public interface StatementCallback<T> {

	/**
	 * Gets called by {@code JdbcTemplate.execute} with an active JDBC
	 * Statement. Does not need to care about closing the Statement or the
	 * Connection, or about handling transactions: this will all be handled
	 * by Spring's JdbcTemplate.
	 * <p><b>NOTE:</b> Any ResultSets opened should be closed in finally blocks
	 * within the callback implementation. Spring will close the Statement
	 * object after the callback returned, but this does not necessarily imply
	 * that the ResultSet resources will be closed: the Statement objects might
	 * get pooled by the connection pool, with {@code close} calls only
	 * returning the object to the pool but not physically closing the resources.
	 * <p>If called without a thread-bound JDBC transaction (initiated by
	 * DataSourceTransactionManager), the code will simply get executed on the
	 * JDBC connection with its transactional semantics. If JdbcTemplate is
	 * configured to use a JTA-aware DataSource, the JDBC connection and thus
	 * the callback code will be transactional if a JTA transaction is active.
	 * <p>Allows for returning a result object created within the callback, i.e.
	 * a domain object or a collection of domain objects. Note that there's
	 * special support for single step actions: see JdbcTemplate.queryForObject etc.
	 * A thrown RuntimeException is treated as application exception, it gets
	 * propagated to the caller of the template.
	 * @param stmt active JDBC Statement
	 * @return a result object, or {@code null} if none
	 * @throws SQLException if thrown by a JDBC method, to be auto-converted
	 * to a DataAccessException by a SQLExceptionTranslator
	 * @throws DataAccessException in case of custom exceptions
	 * @see JdbcTemplate#queryForObject(String, Class)
	 * @see JdbcTemplate#queryForRowSet(String)
	 */
	T doInStatement(Statement stmt) throws SQLException, DataAccessException;

}

You can see where execute(StatementCallback<T> action) is called here, corresponding to query, update and batch update operations

 Let's go to UpdateStatementCallback to see

	@Override
	public int update(final String sql) throws DataAccessException {
		Assert.notNull(sql, "SQL must not be null");
		if (logger.isDebugEnabled()) {
			logger.debug("Executing SQL update [" + sql + "]");
		}

		class UpdateStatementCallback implements StatementCallback<Integer>, SqlProvider {
			@Override
			public Integer doInStatement(Statement stmt) throws SQLException {
				int rows = stmt.executeUpdate(sql);
				if (logger.isDebugEnabled()) {
					logger.debug("SQL update affected " + rows + " rows");
				}
				return rows;
			}
			@Override
			public String getSql() {
				return sql;
			}
		}

		return execute(new UpdateStatementCallback());
	}

It can be seen that UpdateStatementCallback implements the StatementCallback interface, and puts the specific update operation logic in the outer layer of execute, so that the change is passed into the execute method as a callback object. You can go to QueryStatementCallback to see, the same is true

	@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());
	}

Summarize

Why is JdbcTemplate not using inheritance? Because there are too many methods in this class, but we still want to use the existing stable and public database connection of JdbcTemplate, so what should we do?

 We can extract the changed things and pass them into the JdbcTemplate method as a parameter. But what changes is a piece of code, and this code will use variables in JdbcTemplate. what to do? Then we use the callback object. In this callback object, define a method for manipulating variables in JdbcTemplate. When we implement this method, we will concentrate the changes here. Then we pass this callback object to the JdbcTemplate to complete the call.

Guess you like

Origin blog.csdn.net/qq_28165595/article/details/131751192