淺談Spring JdbcTemplate模板方法設計模式

  • 以下例子代碼可在github或者在gitee下載
    github:代碼鏈接
    gitee:代碼鏈接
  • 有關模板方法設計模式赡艰,可以參看前面寫的文章:模板方法模式&lambda重構模板方法模式
  • 本文主要分為四部分:(1)JDBC規(guī)范與連接MySQL(2)JdbcTemplate連接MySQL(3)分析JdbcTemplate模板方法設計模式(4)手寫簡單版JdbcTemplate示例模板方法設計模式
  • 示例代碼可以通過啟動項目調用JdbcController類originalJdbc方法(原生jdbc連接MySQL)、jdcbTemplate方法(jdbc連接MySQL)笔诵、defineJcbcTemplate方法(手寫jdbcTemplate)查看結果谅将。
  • 代碼結構


    項目結構.png
一、JDBC規(guī)范與JDBC連接MySQL數(shù)據(jù)庫

(1)JDBC的全稱是Java Database Connectivity鸠蚪,設計初衷是提供一套能夠應用于各種數(shù)據(jù)庫的統(tǒng)一標準今阳,這套標準需要不同數(shù)據(jù)庫廠家之間共同遵守,并提供各自的實現(xiàn)方案供 JDBC應用程序調用茅信,JDBC規(guī)范架構圖如下圖所示盾舌。


JDBC規(guī)范架構圖.png

(2)JDBC 規(guī)范中的核心編程對象包括 DriverManger、DataSource蘸鲸、Connection妖谴、Statement,及 ResultSet酌摇。

  • DriverManger窖维,負責加載各種不同的驅動程序(Driver)
  • DataSource主要是從數(shù)據(jù)庫連接池中獲取數(shù)據(jù)庫連接對象Connection,如果沒有DataSource和數(shù)據(jù)庫連接池ConnectionPoolDataSource妙痹,通過DriverManger獲取數(shù)據(jù)庫連接對象Connection铸史,建立連接和銷毀連接的過程產生的開銷較大,故需要有數(shù)據(jù)庫連接池和DataSource怯伊,與線程池類似琳轿。
  • Connection數(shù)據(jù)庫連接對象,可以理解為會話耿芹,代表一個數(shù)據(jù)庫連接崭篡,負責完成與數(shù)據(jù)庫直連的通信。
  • Statement負責執(zhí)行sql語句吧秕。
  • ResultSet執(zhí)行sql之后的結果琉闪。

(3)Jdbc連接MySQL數(shù)據(jù)庫查詢id為1的學生

  • 領域對象Student類
public class Student {

    private Long id;

    private String name;

    private Integer age;
}
  • 添加c3p0 datasource作為數(shù)據(jù)庫連接池
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
  • 連接MySQL數(shù)據(jù)庫
        try {
            //獲取連接
            Connection connection = dataSource.getConnection();
            //執(zhí)行查詢
            PreparedStatement preparedStatement = connection.prepareStatement("select * from student where id = 1");
            //獲取執(zhí)行結果
            ResultSet resultSet = preparedStatement.executeQuery();
            if (resultSet.next()) {
                Student student = new Student();
                student.setId(resultSet.getLong("id"));
                student.setName(resultSet.getString("name"));
                student.setAge(resultSet.getInt("age"));
                log.info("student:{}", JSONUtil.toJsonStr(student));
            }
            //關閉資源
            preparedStatement.close();
            resultSet.close();
            connection.close();
        } catch (Exception e) {
            log.error("e:", e);
        }

(4)基于 JDBC 規(guī)范進行數(shù)據(jù)庫訪問的整個開發(fā)流程:
創(chuàng)建DataSource -> 獲取Connection -> 創(chuàng)建Statment -> 執(zhí)行sql語句 -> 處理ResultSet -> 關閉資源對象。
(5)用jdbc查詢數(shù)據(jù)庫的過程中砸彬,主要有兩個部分颠毙,一部分是需要準備和釋放資源,另一部分是處理結果砂碉,對于業(yè)務來說蛀蜜,我們只需要處理數(shù)據(jù)庫查詢結果,而準備和釋放資源增蹭,是多余的部分且重復的滴某,如果用jdbc在實際開發(fā)中,每次都需要寫額外的代碼,故Spring框架的JdbcTemplate工具應運而生霎奢。

二户誓、JdbcTemplate連接MySQL
  • 添加jdbc starter起步依賴
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
  • yaml文件配置MySQL數(shù)據(jù)庫連接池
spring:
  datasource:
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/exercisegroup?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
  • 注入jdbcTemplate模板工具,使用jdbcTemplate進行數(shù)據(jù)庫查詢幕侠,queryForObject方法第二個參數(shù)是一個函數(shù)式接口帝美,接口參數(shù)是傳入jdbc查詢結果ResultSet,返回處理結果橙依,這部分交由調用者處理,我寫了rowMapper處理此次查詢結果硕旗。
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void jdcbTemplate() {
        //省去建立數(shù)據(jù)庫連接與連接釋放資源
        Student student = jdbcTemplate.queryForObject("select * from student where id = 1", this::rowMapper);
        log.info("student:{}", JSONUtil.toJsonStr(student));
    }

    private Student rowMapper(ResultSet resultSet, int rowNum) {
        Student student = new Student();
        try {
            student.setId(resultSet.getLong("id"));
            student.setName(resultSet.getString("name"));
            student.setAge(resultSet.getInt("age"));
            return student;
        } catch (SQLException e) {
            log.error("e:", e);
        }
        return null;
    }

使用jdbcTemplate可以看到只需寫sql窗骑,然后對執(zhí)行結果進行處理,省去了建立數(shù)據(jù)庫連接與連接釋放資源的重復操作漆枚,其中用的是模板方法設計模式创译,把重復的動作封裝起來,需要處理的部分開放接口墙基,讓調用者去實現(xiàn)软族。

三、JdbcTemplate模板方法設計模式與回調方法
  • RowMapper函數(shù)式接口残制,方法入參是jdbc查詢結果ResultSet立砸,返回的是我們實現(xiàn)完該函數(shù)式接口的返回結果T,在jdbcTemplate中充當回調方法初茶,最后是調用mapRow方法颗祝,以此返回處理完的結果。
@FunctionalInterface
public interface RowMapper<T> {
    @Nullable
    T mapRow(ResultSet rs, int rowNum) throws SQLException;
}
  • JdbcTemplate的queryForObject方法
    public <T> T queryForObject(String sql, RowMapper<T> rowMapper) throws DataAccessException {
        List<T> results = query(sql, rowMapper);
        return DataAccessUtils.nullableSingleResult(results);
    }
  • 繼續(xù)點進queryForObject方法的query(sql, rowMapper)恼布,這里是再封裝一次RowMapper到RowMapperResultSetExtractor螺戳,可以回頭再看,主要是封裝遍歷處理完的結果折汞,方法是extractData
    @Override
    public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
        return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper)));
    }
  • 繼續(xù)點進query(sql, new RowMapperResultSetExtractor<>(rowMapper)))倔幼,這里QueryStatementCallback實現(xiàn)StatementCallback接口,核心邏輯是stmt.executeQuery(sql)執(zhí)行sql爽待,rse.extractData(rs)處理結果,即上一步說到的處理結果鸟款,最后調用的是execute(new QueryStatementCallback(), true)揖庄,把實現(xiàn)好StatementCallback接口的QueryStatementCallback類傳進去,jdbcTemplate最核心的方法是execute欠雌。
    @Override
    @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 (logger.isDebugEnabled()) {
            logger.debug("Executing SQL query [" + sql + "]");
        }

        /**
         * Callback to execute the query.
         */
        class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
            @Override
            @Nullable
            public T doInStatement(Statement stmt) throws SQLException {
                ResultSet rs = null;
                try {
                    rs = stmt.executeQuery(sql);
                    return rse.extractData(rs);
                }
                finally {
                    JdbcUtils.closeResultSet(rs);
                }
            }
            @Override
            public String getSql() {
                return sql;
            }
        }

        return execute(new QueryStatementCallback(), true);
    }
  • 繼續(xù)點進execute方法蹄梢,可以看到跟步驟一用jdbc連接數(shù)據(jù)庫一樣,可以回顧步驟一的基于 JDBC 規(guī)范進行數(shù)據(jù)庫訪問的整個開發(fā)流程,也是創(chuàng)建DataSource禁炒,獲取Connection對象連接而咆,創(chuàng)建Statment,關閉資源對象幕袱,這些額外重復的操作都在execute方法封裝起來了暴备,這也是模板方法設計模式的思想,把重復的们豌、額外的操作封裝起來涯捻,定義好開放接口,交由子類實現(xiàn)望迎,在execute方法中需要實現(xiàn)的接口方法是action.doInStatement(stmt)障癌,上一步的StatementCallback已經實現(xiàn)好了該接口類的doInStatement方法,其余都是封裝好的辩尊,準備資源以及釋放資源涛浙。
private <T> T execute(StatementCallback<T> action, boolean closeResources) 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) {
            // Release Connection early, to avoid potential connection pool deadlock
            // in the case when the exception translator hasn't been initialized yet.
            String sql = getSql(action);
            JdbcUtils.closeStatement(stmt);
            stmt = null;
            DataSourceUtils.releaseConnection(con, getDataSource());
            con = null;
            throw translateException("StatementCallback", sql, ex);
        }
        finally {
            if (closeResources) {
                JdbcUtils.closeStatement(stmt);
                DataSourceUtils.releaseConnection(con, getDataSource());
            }
        }
    }
四、運用模板方法手寫簡單版JdbcTemplate
  • DefineJcbcTemplate自定義jdbcTemplate接口
public interface DefineJcbcTemplate {

    <T> T queryForObject(String sql, DefineRowMapper<T> defineRowMapper);

}
  • DefineRowMapper函數(shù)式接口摄欲,回調作用轿亮,處理jdbc查詢結果ResultSet,返回泛型T對象
@FunctionalInterface
public interface DefineRowMapper <T>{

    T mapRow(ResultSet rs) throws SQLException;
}
  • DefineJcbcTemplateImpl實現(xiàn)類胸墙,該實現(xiàn)類的連接數(shù)據(jù)庫我注、釋放資源等操作都是從步驟一負責粘貼進來的,唯一不同的是把處理結果解耦出來了迟隅,定義好處理結果的接口DefineRowMapper仓手,交由調用者去實現(xiàn)對結果的處理,最后回調該接口的方法mapRow并返回結果玻淑,其他的把連接數(shù)據(jù)庫嗽冒、釋放資源封裝起來,這樣一來不用每次進行數(shù)據(jù)庫查詢都需要連接數(shù)據(jù)庫补履、釋放資源添坊。
@Service
@Slf4j
public class DefineJcbcTemplateImpl implements DefineJcbcTemplate {

    @Autowired
    private DataSource dataSource;

    @Override
    public <T> T queryForObject(String sql, DefineRowMapper<T> defineRowMapper) {
        //一部分是準備和釋放資源以及執(zhí)行 SQL 語句,另一部分則是處理 SQL 執(zhí)行結果
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            //創(chuàng)建dataSource,獲取連接
            connection = dataSource.getConnection();
            //執(zhí)行查詢
            preparedStatement = connection.prepareStatement(sql);
            //獲取執(zhí)行結果
            resultSet = preparedStatement.executeQuery();
            return defineRowMapper.mapRow(resultSet);
        } catch (Exception e) {
            log.error("e:", e);
        } finally {
            //關閉資源
            if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    log.error("e:", e);
                }
            }
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    log.error("e:", e);
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    log.error("e:", e);
                }
            }
        }
        return null;
    }
}
  • 最后是調用自定義的jdbcTemplate的queryForObject查詢數(shù)據(jù)庫箫锤。
    @Override
    public void defineJcbcTemplate() {
        defineJcbcTemplate.queryForObject("select * from student where id = 1", this::defineRowMapper);
    }

    private Student defineRowMapper(ResultSet resultSet) {
        try {
            //ResultSet是一個結果集贬蛙,想讀出來,必須要next方法才行
            if (resultSet.next()) {
                Student student = new Student();
                student.setId(resultSet.getLong("id"));
                student.setName(resultSet.getString("name"));
                student.setAge(resultSet.getInt("age"));
                log.info("student:{}", JSONUtil.toJsonStr(student));
                return student;
            }
        } catch (SQLException e) {
            log.error("e:", e);
        }
        return null;
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末谚攒,一起剝皮案震驚了整個濱河市阳准,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌馏臭,老刑警劉巖野蝇,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡绕沈,警方通過查閱死者的電腦和手機锐想,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乍狐,“玉大人赠摇,你說我怎么就攤上這事∏瞅剑” “怎么了藕帜?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長惜傲。 經常有香客問我洽故,道長,這世上最難降的妖魔是什么操漠? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任收津,我火速辦了婚禮饿这,結果婚禮上浊伙,老公的妹妹穿的比我還像新娘。我一直安慰自己长捧,他們只是感情好嚣鄙,可當我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著串结,像睡著了一般哑子。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上肌割,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天卧蜓,我揣著相機與錄音,去河邊找鬼把敞。 笑死弥奸,一個胖子當著我的面吹牛,可吹牛的內容都是我干的奋早。 我是一名探鬼主播盛霎,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼耽装!你這毒婦竟也來了愤炸?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤掉奄,失蹤者是張志新(化名)和其女友劉穎规个,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡绰姻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年枉侧,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狂芋。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡榨馁,死狀恐怖,靈堂內的尸體忽然破棺而出帜矾,到底是詐尸還是另有隱情翼虫,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布屡萤,位于F島的核電站珍剑,受9級特大地震影響,放射性物質發(fā)生泄漏死陆。R本人自食惡果不足惜招拙,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望措译。 院中可真熱鬧别凤,春花似錦、人聲如沸领虹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽塌衰。三九已至诉稍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間最疆,已是汗流浹背杯巨。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留努酸,地道東北人服爷。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像蚊逢,于是被迫代替她去往敵國和親层扶。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內容

  • 設計模式是在特定場景下對特定問題的解決方案烙荷,這些解決方案是經過反復論證和測試總結出來的镜会。實際上,除了軟件設計终抽,設計...
    tony關東升閱讀 1,527評論 0 0
  • 模板方法(Template Method)模式的定義:定義一個操作中的算法骨架戳表,而將算法的一些步驟延遲到子類中桶至,使...
    _muggle閱讀 113評論 0 1
  • 1、什么是 AOP (1)面向切面編程(方面)匾旭,利用 AOP 可以對業(yè)務邏輯的各個部分進行隔離镣屹,從而使得業(yè)務邏輯各...
    鄙人_阿K閱讀 585評論 0 0
  • Spring 框架針對數(shù)據(jù)庫開發(fā)中的應用提供了 JDBCTemplate 類,該類是 Spring 對 JDBC ...
    Zal哥哥閱讀 248評論 0 0
  • 表情是什么价涝,我認為表情就是表現(xiàn)出來的情緒女蜈。表情可以傳達很多信息。高興了當然就笑了色瘩,難過就哭了伪窖。兩者是相互影響密不可...
    Persistenc_6aea閱讀 124,966評論 2 7