От шаблона метода шаблона к JDBCTemplate

Java

Чтобы засунуть слона в холодильник, нужно сделать три шага, а как насчет тигра? Как изящно поместить слонов в холодильник?

положить слонов в холодильник

Step Слон Тигр ...
First Откройте дверь холодильника Откройте дверь холодильника Откройте дверь холодильника
Second положить слона в посадить тигра ...
Third Закройте дверь холодильника Закройте дверь холодильника Закройте дверь холодильника

Слоны

    public class Elephant {
        public void putRefrigerator() {
            openDoor();
            putElephant();
            closeDoor();
        }
        public void openDoor() {
            System.out.println("open the door");
        }
        public void putElephant() {
            System.out.println("put in the Elephant");
        }
        public void closeDoor() {
            System.out.println("close the door");
        }
    }

Тигр

    public class Tiger {
        public void putRefrigerator() {
            openDoor();
            putTiger();
            closeDoor();
        }
        public void openDoor() {
            System.out.println("open the door");
        }
        public void putTiger() {
            System.out.println("put in the Tiger");
        }
        public void closeDoor() {
            System.out.println("close the door");
        }
    }

Видно, что в процессе помещения слонов и тигров в холодильник много повторяющегося кода.Это явно не удачный дизайн.Что, если в будущем нам нужно будет поместить жирафов в процесс обновления системы?Что следует мы делаем?Удалить этот повторяющийся код из наших дизайнов? Путем наблюдения мы обнаружили, что есть много общего между увеличением слона и выпуском тигра. Оба требуют операции открытия и закрытия двери, но процесс выпуска отличается. Можем ли мы выделить общие моменты? Давайте попробуем вместе

абстрактный суперкласс

    public abstract class AbstractPutAnyAnimal {
        //这是一个模板方法,它是一个算法的模板,描述我们将动物放进冰箱的步骤,每一个方法代表了一个步骤
        public void putRefrigerator() {
            openDoor();
            putAnyAnimal();
            closeDoor();
        }
        //在超类中实现共同的方法,由超类来处理
        public void openDoor() {
            System.out.println("open the door");
        }
        public void closeDoor() {
            System.out.println("close the door");
        }
        //每个子类可能有不同的方法,我们定义成抽象方法让子类去实现
        abstract void putAnyAnimal();
    }

Слоны

    public class Elephant extends AbstractPutAnyAnimal {
        //子类实现自己的业务逻辑
        @Override
        void putAnyAnimal() {
            System.out.println("put in the Elephant");
        }
    }

тигр

    public class Tiger extends AbstractPutAnyAnimal {
        //子类实现自己的业务逻辑
        @Override
        void putAnyAnimal() {
            System.out.println("put in the Tiger");
        }
    }

Извлекая тот же метод в суперкласс и определяя абстрактный метод для подклассов, чтобы обеспечить другую реализацию, мы фактически только что реализовали шаблон шаблонного метода.

Определение шаблона метода шаблона?

Режим шаблонного метода определяет шаги алгоритма и позволяет подклассам обеспечивать реализацию одного или нескольких шагов., метод putRefrigerator определяет наш шаг по помещению слона в холодильник, это шаблонный метод.Шаблон метода шаблона определяет скелет алгоритма в методе и передает некоторые шаги подклассам.Метод шаблона позволяет подклассам переопределять некоторые шаги алгоритма без изменения структуры алгоритма.(подклассы предоставляют свои собственные реализации)

Хуки в паттерне шаблонного метода

Мы можем определить пустой метод в суперклассе, мы называем этот метод хуком. Подклассы могут выбрать переопределение в зависимости от ситуации, а наличие ловушек позволяет подклассам иметь возможность монтировать разные точки алгоритма;Ловушка может составлять необязательную часть алгоритма реализации подкласса, и хук также может заставить подкласс принимать некоторые решения.После того, как мы поместим слона в холодильник, мы можем захотеть отрегулировать температуру холодильника, или мы можем ничего не делать, чтобы использовать температуру по умолчанию.Мы можем определить крючок и позволить подклассу выбирать, регулировать ли температуру, следующим образом:

абстрактный родительский класс

    public abstract class AbstractPutAnyAnimal {
        public void putRefrigerator() {
            openDoor();
            putAnyAnimal();
            closeDoor();
            //默认为false,重新这个方法决定是否执行addTemperature();方法
            if (isAdd()) {
                addTemperature();
            }
        }
        public void openDoor() {
            System.out.println("open the door");
        }
        public void closeDoor() {
            System.out.println("close the door");
        }
        abstract void putAnyAnimal();
        void addTemperature(){
            System.out.println("plus one");
        };
        //定义一个空实现,由子类决定是否对其进行实现
        boolean isAdd(){
            return false;
        }
    }

Слоны

    public class Elephant extends AbstractPutAnyAnimal {
        @Override
        void putAnyAnimal() {
            System.out.println("put in the Elephant");
        }
        //子类实现钩子方法
        @Override
        boolean isAdd() {
            return true;
        }
    }

Мы решаем, регулировать ли температуру, определяя метод-ловушку, а подкласс решает, следует ли реализовать этот метод-ловушку; конечно, метод-ловушка используется не только для этого.Это также дает подклассам возможность реагировать на шаги, которые должны произойти или только что произошли в шаблоне., Который находится в JDK множества примеров, даже в области фронтальной разработки, есть много примеров, я не запущен конкретный код, демонстрируемый, дополнительные приложения развернуты за рисунком метода шаблона.

В JDK и Spring используется множество шаблонов проектирования. Давайте сравним традиционное программирование JDBC и JDBCTemplate, чтобы увидеть, как шаблон метода шаблона может помочь нам избавиться от шаблонного кода.

Традиционное программирование JDBC

Новое программирование JDBC

        String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8&useSSL=true";
        String username = "root";
        String password = "1234";
        Connection connection = null;
        Statement statement = null;
        try {
            Class.forName(driver);
            connection = DriverManager.getConnection(url, username, password);
            String sql = "insert into users(nickname,comment,age) values('小小谭','I love three thousand times', '21')";
            statement = connection.createStatement();
            int i = statement.executeUpdate(sql);
            return i;
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != statement) {
                    statement.close();
                }
                if (null != connection) {
                    connection.close();
                }
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        return 0;

Запрос программирования JDBC

        String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8&useSSL=true";
        String username = "root";
        String password = "1234";
        Connection connection = null;
        Statement statement = null;
        try{
            Class.forName(driver);
            connection = DriverManager.getConnection(url, username, password);
            String sql = "select nickname,comment,age from users";
            statement = connection.createStatement();
            ResultSet resultSet = statement.executeQuery(sql);
            List<Users> usersList = new ArrayList<>();
            while (resultSet.next()) {
                Users users = new Users();
                users.setNickname(resultSet.getString(1));
                users.setComment(resultSet.getString(2));
                users.setAge(resultSet.getInt(3));
                usersList.add(users);
            }
            return usersList;
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != statement) {
                    statement.close();
                }
                if (null != connection) {
                    connection.close();
                }
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        return null;

Два случая нашего традиционного программирования JDBC приведены выше.Мы можем видеть много недостатков традиционного JDBC.Конечно, в реальных проектах мы можем быть не такими примитивными в разработке баз данных, и можем в определенной степени инкапсулировать JDBC в наше использование. Spring официально выпустил JDBCTemplate, чтобы упростить разработку JDBC.Давайте посмотрим, как он упрощает разработку и применение в нем паттерна шаблонного метода.

Что такое JDBCTemplate и что он упрощает?

Из названия JDBCTemplate нетрудно понять, что он упрощает разработку нашего JDBC и, вероятно, применяет большое количество шаблонов шаблонных методов.Что он нам дает?Он предоставляет платформу для механизма обработки исключений и Мэтта. Студенты, которые использовали нативную разработку JDBC, могут столкнуться с тем, что почти все коды операций требуют от нас принудительного перехвата исключений, но когда возникает исключение, мы часто не можем прочитать ошибку через исключение. Весна решила нашу проблемуПредоставляет несколько исключений доступа к данным и описывает соответствующие проблемы при их возникновении.В то же время обертывание исключения не заставляет нас его перехватывать и предоставляет нам доступ к данным по шаблону., из приведенного выше традиционного программирования JDBC мы можем обнаружить, что многие операции фактически повторяются и не изменяются, такие как управление транзакциями, получение и закрытие ресурсов, а также обработка исключений.В то же время привязка объектов обработки набора результатов и привязка параметров вещи уникальны. следовательноSpring разделяет фиксированную часть и переменную часть процесса доступа к данным на два разных класса (Template) и обратный вызов (Callback), которые не становятся частью процесса обработки шаблона, а обратный вызов обрабатывает пользовательский код доступа; Давайте узнаем об этом через исходный код

Режим метода шаблона в jdbcTemplate

Я использую версию 5.1.5.RELEASE.

Открываем класс JdbcTemplate (здесь скриншоты делать не буду, скриншоты могут быть нечеткими, скопирую код напрямую):

JdbcTemplate

    public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
        //查询前缀
        private static final String RETURN_RESULT_SET_PREFIX = "#result-set-";
        //计数前缀
        private static final String RETURN_UPDATE_COUNT_PREFIX = "#update-count-";
        //是否跳过警告
        private boolean ignoreWarnings = true;
        //查询大小
        private int fetchSize = -1;
        //最大行
        private int maxRows = -1;
        //查询超时
        private int queryTimeout = -1;
        //是否跳过结果集处理
        private boolean skipResultsProcessing = false;
        //是否跳过非公共结果集处理
        private boolean skipUndeclaredResults = false;
        //map结果集是否大小写敏感
        private boolean resultsMapCaseInsensitive = false;
    
        public JdbcTemplate() {
        }
        //调用父类方法设置数据源和其他参数
        public JdbcTemplate(DataSource dataSource) {
            this.setDataSource(dataSource);
            this.afterPropertiesSet();
        }
        //调用父类方法设置数据源,懒加载策略和其他参数
        public JdbcTemplate(DataSource dataSource, boolean lazyInit) {
            this.setDataSource(dataSource);
            this.setLazyInit(lazyInit);
            this.afterPropertiesSet();
        }
    }

JdbcTemplate наследует JdbcAccessor и реализует JdbcOperations.JdbcAccessor в основном инкапсулирует работу источника данных, а JdbcOperations в основном определяет некоторые интерфейсы операций. Давайте взглянем на класс JdbcOperations;

    public abstract class JdbcAccessor implements InitializingBean {
        protected final Log logger = LogFactory.getLog(this.getClass());
        //数据源
        @Nullable
        private DataSource dataSource;
        //异常翻译
        @Nullable
        private volatile SQLExceptionTranslator exceptionTranslator;
        //懒加载策略
        private boolean lazyInit = true;
        public JdbcAccessor() {
        }
        public void setDataSource(@Nullable DataSource dataSource) {
            this.dataSource = dataSource;
        }
        @Nullable
        public DataSource getDataSource() {
            return this.dataSource;
        }
        protected DataSource obtainDataSource() {
            DataSource dataSource = this.getDataSource();
            Assert.state(dataSource != null, "No DataSource set");
            return dataSource;
        }
        public void setDatabaseProductName(String dbName) {
            this.exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dbName);
        }
        public void setExceptionTranslator(SQLExceptionTranslator exceptionTranslator) {
            this.exceptionTranslator = exceptionTranslator;
        }
    }

ЗачемКак упоминалось ранее, spring упрощает обработку исключений, и именно здесь он обертывает SQLExceptionTranslator., другой код предназначен для установки источника данных, например для проверки источника данных, давайте рассмотрим метод getExceptionTranslator()

    public SQLExceptionTranslator getExceptionTranslator() {
        SQLExceptionTranslator exceptionTranslator = this.exceptionTranslator;
        if (exceptionTranslator != null) {
            return exceptionTranslator;
        } else {
            synchronized(this) {
                SQLExceptionTranslator exceptionTranslator = this.exceptionTranslator;
                if (exceptionTranslator == null) {
                    DataSource dataSource = this.getDataSource();
                    if (dataSource != null) {
                        exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource);
                    } else {
                        exceptionTranslator = new SQLStateSQLExceptionTranslator();
                    }
                    this.exceptionTranslator = (SQLExceptionTranslator)exceptionTranslator;
                }
                return (SQLExceptionTranslator)exceptionTranslator;
            }
        }
    }

Это стандартный паттерн singleton, дикий синглтон мы захватили на пути к изучению паттерна шаблонного метода, продолжаем смотреть на интерфейс JdbcOperations и вызываем один из интерфейсов для парсинга;

    @Nullable
    <T> T execute(StatementCallback<T> var1) throws DataAccessException;

Интерфейс StatementCallback

    @FunctionalInterface
    public interface StatementCallback<T> {
        @Nullable
        T doInStatement(Statement var1) throws SQLException, DataAccessException;
    }

выполнить реализацию

    @Nullable
    public <T> T execute(StatementCallback<T> action) throws DataAccessException {
        //参数检查
        Assert.notNull(action, "Callback object must not be null");
        //获取连接
        Connection con = DataSourceUtils.getConnection(this.obtainDataSource());
        Statement stmt = null;
        Object var11;
        try {
            //创建一个Statement
            stmt = con.createStatement();
            //设置查询超时时间,最大行等参数(就是一开始那些成员变量)
            this.applyStatementSettings(stmt);
            //执行回调方法获取结果集
            T result = action.doInStatement(stmt);
            //处理警告
            this.handleWarnings(stmt);
            var11 = result;
        } catch (SQLException var9) {
            //出现错误优雅退出
            String sql = getSql(action);
            JdbcUtils.closeStatement(stmt);
            stmt = null;
            DataSourceUtils.releaseConnection(con, this.getDataSource());
            con = null;
            throw this.translateException("StatementCallback", sql, var9);
        } finally {
            JdbcUtils.closeStatement(stmt);
            DataSourceUtils.releaseConnection(con, this.getDataSource());
        }
        return var11;
    }

Этот метод можно охарактеризовать как самый яркий, это типичный шаблонный метод + режим обратного вызова, нам не нужно писать слишком много повторяющегося кода, нам просто нужно реализовать свой метод для получения результата (StatementCallback) На самом деле, нам не нужно Реализовать этот метод, продолжаем искать, как мы вызываем метод execute, возьмем запрос в качестве примера, давайте посмотрим, как он вызывается пошагово:

метод запроса

    public List<Users> findAll() {
        JdbcTemplate jdbcTemplate = DataSourceConfig.getTemplate();
        String sql = "select nickname,comment,age from users";
        return jdbcTemplate.query(sql, new BeanPropertyRowMapper<Users>(Users.class));
    }

реализация запроса

    public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
        return (List)result(this.query((String)sql, (ResultSetExtractor)(new RowMapperResultSetExtractor(rowMapper))));
    }

RowMapper здесь отвечает за преобразование строки данных в результирующем наборе в сущность и ее возврат. Используется технология отражения. Здесь она не будет раскрываться. Заинтересованные учащиеся могут открыть исходный код, чтобы прочитать и продолжить вниз:

реализация запроса

@Nullable
    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 (this.logger.isDebugEnabled()) {
            this.logger.debug("Executing SQL query [" + sql + "]");
        }
        //实现回调接口
        class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
            QueryStatementCallback() {
            }
            @Nullable
            public T doInStatement(Statement stmt) throws SQLException {
                ResultSet rs = null;
                Object var3;
                try {
                    //这里真正的执行我们的sql语句
                    rs = stmt.executeQuery(sql);
                    //处理对象映射
                    var3 = rse.extractData(rs);
                } finally {
                    JdbcUtils.closeResultSet(rs);
                }
                return var3;
            }
            public String getSql() {
                return sql;
            }
        }
        //调用execute接口
        return this.execute((StatementCallback)(new QueryStatementCallback()));
    }

Увидев это, я считаю, что вы не должны быть удивлены. Spring справляется с этим очень умно. Пожалуйста, продолжайте читать:

Подробное объяснение обновления

    protected int update(PreparedStatementCreator psc, @Nullable PreparedStatementSetter pss) throws DataAccessException {
        this.logger.debug("Executing prepared SQL update");
        return updateCount((Integer)this.execute(psc, (ps) -> {
            Integer var4;
            try {
                if (pss != null) {
                    pss.setValues(ps);
                }
                int rows = ps.executeUpdate();
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("SQL update affected " + rows + " rows");
                }
                var4 = rows;
            } finally {
                if (pss instanceof ParameterDisposer) {
                    ((ParameterDisposer)pss).cleanupParameters();
                }
            }
            return var4;
        }));
    }

Почему я упомянул функцию обновления, потому что обновление использует здесь лямбда-функцию. Напомним, что наш StatementCallback определяет интерфейс только с одним методом. Это функция и интерфейс, поэтому это функциональный интерфейс, поэтому синтаксис лямбда используется напрямую. здесь. ,Лямбда-функции позволяют вам встраиваться напрямую, предоставляя реализации для абстрактных методов функционального интерфейса и всего выражения как экземпляра функционального интерфейса.. Мы можем знать синтаксис лямбда в нашем обычном обучении, но мы можем использовать его реже, или мы не знаем, как использовать его в реальном бою.Чтение исходного кода определенно улучшит ваши боевые способности. Мы видим, что JDBCTemplate использует много обратных вызовов. Зачем использовать Обратный звонок?Если родительский класс имеет несколько абстрактных методов, для подкласса очень сложно реализовать их все, а иногда подклассу нужно только настроить определенный метод в родительском классе? В настоящее время нам нужно использовать обратный вызов обратного вызова, чтобы идеально решить эту проблему., можно обнаружить, что JDBCTemplate не полностью ограничен методами шаблона и очень гибок. Мы также можем извлечь уроки из этого метода в реальном развитии.

Дополнительные применения паттерна «Шаблонный метод»

На самом деле многие классы, связанные с жизненным циклом, используют шаблон метода шаблона, наиболее типичным из которых, вероятно, является тот, с которым мы лучше всего знакомы, чем с сервлетом.

    public abstract class HttpServlet extends GenericServlet
    {
    }

Все методы HttpServlet, мы видим, что HttpServlet наследует GenericServlet, мы продолжаем видеть:

public abstract class GenericServlet 
    implements Servlet, ServletConfig, java.io.Serializable
{
    private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
    private static ResourceBundle lStrings =
        ResourceBundle.getBundle(LSTRING_FILE);

    private transient ServletConfig config;
    
    public GenericServlet() { }
    
    //没有实现钩子
    public void destroy() {
    }
    
    public String getInitParameter(String name) {
        ServletConfig sc = getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(
                lStrings.getString("err.servlet_config_not_initialized"));
        }

        return sc.getInitParameter(name);
    }
    
    public Enumeration<String> getInitParameterNames() {
        ServletConfig sc = getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(
                lStrings.getString("err.servlet_config_not_initialized"));
        }

        return sc.getInitParameterNames();
    }   
     
    public ServletConfig getServletConfig() {
	return config;
    }
    
    public ServletContext getServletContext() {
        ServletConfig sc = getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(
                lStrings.getString("err.servlet_config_not_initialized"));
        }

        return sc.getServletContext();
    }

    public String getServletInfo() {
	return "";
    }

    public void init(ServletConfig config) throws ServletException {
	this.config = config;
	this.init();
    }
    
    public void init() throws ServletException {

    }
    
    public void log(String msg) {
	getServletContext().log(getServletName() + ": "+ msg);
    }
  
    public void log(String message, Throwable t) {
	getServletContext().log(getServletName() + ": " + message, t);
    }
    
    public abstract void service(ServletRequest req, ServletResponse res)
	throws ServletException, IOException;
    
    public String getServletName() {
        ServletConfig sc = getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(
                lStrings.getString("err.servlet_config_not_initialized"));
        }

        return sc.getServletName();
    }
}

Видно, что это типичный класс методов-шаблонов, и здесь также наглядно отображаются функции-ловушки, такие как методы init, destroy и т. д. Многие классы в JDK используют методы-шаблоны, ожидая, пока вы их обнаружите.

Применение шаблона метода шаблона в Vue.js

Паттерн метода шаблона также реализован в других языках, таких как Vue.js и React, например, жизненный цикл Vue должен использовать метод шаблона, поэтому я не буду анализировать исходный код.

Суммировать

Шаблоны проектирования широко используются в Spring. Заинтересованные студенты могут ознакомиться с исходным кодом Spring, чтобы изучить его. Если вы считаете, что то, что я написал, неплохо, ставьте лайк. Если вы обнаружите ошибки или недочеты, вы также можете сообщить мне об этом в пора исправлять, спасибо! Ваша оценка и критика — хорошие партнеры на пути к прогрессу.