08 Data Access How to Parse the Data Access Implementation Principles of Jdbc Template

08 Data Access - How to Parse the Data Access Implementation Principles of JdbcTemplate #

In lecture 07, we introduced the detailed implementation process of relational database access using the JdbcTemplate utility class. JdbcTemplate not only simplifies database operations, but also avoids the complexity and redundancy issues caused by using native JDBC.

So, how does JdbcTemplate encapsulate the JDBC functionality? Today, I will take you through the evolution process from the design idea, discuss the evolution from JDBC API to JdbcTemplate, and analyze some core source code of JdbcTemplate.

Starting with Template Method Pattern and Callback Mechanism #

From the name, JdbcTemplate is obviously a template class, which reminds us of the template method pattern in design patterns. To help you better understand the implementation principles of JdbcTemplate, let’s briefly explain this design pattern.

Template Method Design Pattern #

The principle of the template method pattern is very simple. It mainly utilizes the inheritance mechanism in object-oriented programming and is widely used in combination with abstract classes in practice. For example, the Spring framework also extensively applies the template method pattern to achieve the separation of responsibilities and collaboration between base classes and subclasses.

According to the definition, when a series of steps need to be completed, and these steps need to follow a unified workflow (except for implementation details of individual steps), we need to consider using the template method pattern. The structural diagram of the template method design pattern is shown below:

图7.png

Structural diagram of the template method design pattern

In the above diagram, the abstract template class AbstractClass defines a set of workflow, and specific implementation classes ConcreteClassA and ConcreteClassB provide implementation for specific steps in the workflow.

Callback Mechanism #

In software development, callback is a common implementation technique. The meaning of callback is illustrated in the following diagram:

图8.png

Diagram of callback mechanism

In the above diagram, the operation1() method of ClassA calls the operation2() method of ClassB, and after the operation2() method of ClassB is executed, it actively calls the callback() method of ClassA. This is the callback mechanism, which represents a bidirectional calling method.

From the above description, it can be seen that callbacks do not cause any blocking during task execution. Once the task result is ready, the callback will be executed. Clearly, this is an asynchronous execution method in terms of method calls. At the same time, callbacks are a simple and direct pattern for implementing extensibility.

In the above diagram, we can see that when executing the callback, the code jumps from a method in one class to a method in another class. This idea can also be extended to the component level, that is, the code jumps from one component to another as long as the callback contract is reserved. In principle, we can dynamically achieve the jump between components at runtime based on the calling relationship, thereby meeting the requirements of extensibility.

In fact, it is because JdbcTemplate is based on the template method pattern and callback mechanism that it truly solves the complexity problem in the native JDBC.

Next, let’s discuss the evolution process from JDBC’s native API to JdbcTemplate, based on the SpringCSS example scenario given in lecture 07.

Evolution from JDBC API to JdbcTemplate #

In lecture 06 “Basic Specification: How to Understand the JDBC Relational Database Access Specification?”, we provided the development process of accessing databases using the native JDBC API. So how do we go from JDBC to JdbcTemplate?

Throughout the process, we can see that the steps of creating a DataSource, obtaining a Connection, creating a Statement, etc. are actually repetitive. Only the handling of the ResultSet needs to be customized based on different SQL statements and results, because each result set has a different mapping to business entities.

In this way, we came up with the idea of how to build an abstract class to implement the template method.

Adding the Template Method Pattern to the JDBC API #

Assuming we name this abstract class AbstractJdbcTemplate, the structure of the class code should be like this:

public abstract class AbstractJdbcTemplate{

    @Autowired
    private DataSource dataSource;

    public final Object execute(String sql) {  
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            connection = dataSource.getConnection();
            statement = connection.createStatement();
            resultSet = statement.executeQuery(sql);
            Object object = handleResultSet(resultSet);         
            return object;
        } catch (SQLException e) {
            System.out.print(e);
        } finally {
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                }
            }
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                }
            }
            if (connection != null) {
                try {

                    connection.close();

                } catch (SQLException e) {

                }

            }

        }

        return null;

    }

}


现在,我们可以使用 CallbackJdbcTemplate 来执行 SQL 语句,并通过实现 StatementCallback 接口,完成对业务逻辑的处理。下面是如何使用 CallbackJdbcTemplate 的示例代码:

CallbackJdbcTemplate callbackJdbcTemplate = new CallbackJdbcTemplate();

List<Order> orders = (List<Order>) callbackJdbcTemplate.execute(new StatementCallback() {

    @Override

    public Object handleStatement(Statement statement) throws SQLException {

        ResultSet rs = statement.executeQuery("select * from Order");

        List<Order> orders = new ArrayList<Order>();

        while (rs.next()) {

            Order order = new Order(rs.getLong("id"), rs.getString("order_number"), rs.getString("delivery_address"));

            orders.add(order);

        }

        return orders;

    }

});

这里通过传入 StatementCallback 对象,并在其中实现了业务逻辑的处理。通过这种方式,我们几乎不需要维护多个实现类,而且可以根据不同的业务需求,随时编写不同的回调方法。这样做不仅减少了类的数量,而且让代码具有更好的灵活性和可扩展性。

相信你现在已经对回调机制的使用比较熟悉了。实际上,这种模式在 Java 中有很多应用,并不仅限于 JDBC。比如在工具类方法中,我们有时候也会遇到类似的情况。下面是一个文件操作工具类 FileUtils,其中包含一个复制文件的方法 copyFile,如下代码所示:

public class FileUtils {

    

    public void copyFile(String srcPath, String destPath){

        // 执行复制文件的逻辑

    }  


    public static void main(String[] args) {

        FileUtils fileUtils = new FileUtils();

        fileUtils.copyFile("srcPath", "destPath");

    }

}


在复制文件的方法中,我们需要编写复制文件的逻辑代码。但是有时候,我们可能希望在复制文件前后做一些其他操作,比如在复制前备份文件、复制后打印日志等。这时候我们可以将这些操作抽取出来,将其封装成一个接口,并将其作为参数传入复制文件的方法中,这就是回调(Callback)。下面是使用回调的 FileUtils 的示例代码:

public interface FileCallback {

    

    void beforeCopy(String srcPath, String destPath);


    void afterCopy(String srcPath, String destPath);


}


public class FileUtils {

    

    public void copyFile(String srcPath, String destPath, FileCallback callback){

        callback.beforeCopy(srcPath, destPath);

        // 执行复制文件的逻辑

        callback.afterCopy(srcPath, destPath);

    }


    public static void main(String[] args) {

        FileUtils fileUtils = new FileUtils();

        fileUtils.copyFile("srcPath", "destPath", new FileCallback() {

            @Override

            public void beforeCopy(String srcPath, String destPath) {

                // 执行复制前的逻辑

            }


            @Override

            public void afterCopy(String srcPath, String destPath) {

                // 执行复制后的逻辑

            }

        });

    }

}


在上面的示例中,我们将复制文件的逻辑代码抽取为一个接口 FileCallback,并将其作为参数传入 copyFile 方法中。这样一来,在调用 copyFile 方法时,我们可以根据自己的需求灵活定制 beforeCopy 和 afterCopy 的逻辑。这种方式可以使代码更加灵活且易于扩展。

至此,我们已经学习并实践过了模板方法模式以及回调机制的使用。希望这两种设计模式能够帮助你更好地理解面向对象编程的核心思想,并应用到实际的开发中。
// Exception handling is omitted

}

return null;
}

}

Note that there are two differences between CallbackJdbcTemplate and AbstractJdbcTemplate. First, CallbackJdbcTemplate is not an abstract class. Second, the execute method in CallbackJdbcTemplate takes a StatementCallback object as input, and the specific customization is done through the Statement passed into the callback object. We can think of this as transferring the functionality that originally required the implementation of abstract methods in subclasses to the StatementCallback object.

Based on CallbackJdbcTemplate and StatementCallback, we can build the execution process for specific database access, as shown in the following code:

public Object queryOrder(final String sql) {

class OrderStatementCallback implements StatementCallback {

public Object handleStatement(Statement statement) throws SQLException {

ResultSet rs = statement.executeQuery(sql);

List<Order> orders = new ArrayList<Order>();

while (rs.next()) {

Order order = new Order(rs.getLong("id"), rs.getString("order_number"),

rs.getString("delivery_address"));

orders.add(order);

}

return orders;

}

}

CallbackJdbcTemplate jdbcTemplate = new CallbackJdbcTemplate();

return jdbcTemplate.execute(new OrderStatementCallback());

}

Here, we define a method queryOrder and pass in the SQL statement for querying the Order table.

Note that in the queryOrder method, we create an inner class called OrderStatementCallback which implements the StatementCallback interface and provides the customized code for manipulating the SQL statement. Then we create a CallbackJdbcTemplate object and pass the inner class OrderStatementCallback into the execute method of that object.

In this scenario, we could actually skip creating the inner class OrderStatementCallback because it’s only used in this specific scenario. In such cases, a simpler approach is to use an anonymous class, as shown in the following code:

public Object queryOrder(final String sql) {

CallbackJdbcTemplate jdbcTemplate = new CallbackJdbcTemplate();

return jdbcTemplate.execute(new StatementCallback() {

public Object handleStatement(Statement statement) throws SQLException {

ResultSet rs = statement.executeQuery(sql);

List<Order> orders = new ArrayList<Order>();

while (rs.next()) {

Order order = new Order(rs.getLong("id"), rs.getString("order_number"),

rs.getString("delivery_address"));

orders.add(order);

}

return orders;

}

});

}

The implementation with an anonymous class is more concise, and in everyday development, we often use this approach to implement callback interfaces.

Analysis of JdbcTemplate Source Code #

Having understood the evolution from the JDBC API to JdbcTemplate, let’s dive into the source code of the JdbcTemplate template utility class provided by Spring Boot to see if it adopts the same design approach.

Let’s take a look at the execute(StatementCallback action) method in JdbcTemplate directly:

public <T> T execute(StatementCallback<T> action) throws DataAccessException {

Assert.notNull(action, "Callback object must not be null");

Connection con = DataSourceUtils.getConnection(obtainDataSource());

Statement stmt = null;

try {

stmt = con.createStatement();

applyStatementSettings(stmt);

T result = action.doInStatement(stmt);

handleWarnings(stmt);

return result;

}

catch (SQLException ex) {

String sql = getSql(action);

JdbcUtils.closeStatement(stmt);

stmt = null;

DataSourceUtils.releaseConnection(con, getDataSource());

con = null;

throw translateException("StatementCallback", sql, ex);

}

finally {

JdbcUtils.closeStatement(stmt);

DataSourceUtils.releaseConnection(con, getDataSource());

}

}

From the above code, we can see that the execute method also accepts a StatementCallback callback interface and executes the SQL statement by passing in a Statement object. This is exactly the same as the implementation method we provided earlier.

The definition of the StatementCallback callback interface is as follows:

public interface StatementCallback<T> {

T doInStatement(Statement stmt) throws SQLException, DataAccessException;

}

Likewise, we find that the definition of the StatementCallback callback interface is quite similar. Now let’s take a look at how the execute(final String sql) method in JdbcTemplate actually uses the execute(StatementCallback action) method:

public void execute(final String sql) throws DataAccessException {

if (logger.isDebugEnabled()) {

logger.debug("Executing SQL statement [" + sql + "]");

}

class ExecuteStatementCallback implements StatementCallback<Object>, SqlProvider {

@Override

@Nullable

public Object doInStatement(Statement stmt) throws SQLException {

stmt.execute(sql);

return null;

}

@Override

public String getSql() {

return sql;

}

}

execute(new ExecuteStatementCallback());

}

Here, we use the same approach of creating an inner class to implement the StatementCallback callback interface, but this time we directly call the execute(StatementCallback action) method to accomplish the entire execution process.

From Source Code Analysis to Everyday Development #

Today’s content is not so much about the source code of JdbcTemplate as it is about the design ideas behind this class. Therefore, many of the knowledge points and implementation methods covered in Lesson 08 can be applied to everyday development.

Both the template method and callback mechanism are not difficult to implement from a technical standpoint. The difficulty lies in applying them to the right scenarios and abstracting the problems. JdbcTemplate combines the template method and callback mechanism to provide us with a concise and highly extensible implementation solution based on the native JDBC API, which is worth analyzing and applying.

Summary and Preview #

JdbcTemplate is a representative template utility class in Spring. In today’s lesson, we analyzed the evolution from the original JDBC specifications to JdbcTemplate starting from real-world application scenarios, and explained the roles played by the template method design pattern and the callback mechanism in this process. We provided an initial implementation solution for JdbcTemplate and then made an analogy with the source code of JdbcTemplate in Spring Boot.