- 以下例子代碼可在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)查看結果谅将。
-
代碼結構
一、JDBC規(guī)范與JDBC連接MySQL數(shù)據(jù)庫
(1)JDBC的全稱是Java Database Connectivity鸠蚪,設計初衷是提供一套能夠應用于各種數(shù)據(jù)庫的統(tǒng)一標準今阳,這套標準需要不同數(shù)據(jù)庫廠家之間共同遵守,并提供各自的實現(xiàn)方案供 JDBC應用程序調用茅信,JDBC規(guī)范架構圖如下圖所示盾舌。
(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;
}