- 以下例子代码可在github或者在gitee下载
github:代码链接
gitee:代码链接 - 有关模板方法设计模式,可以参看前面写的文章:模板方法模式&lambda重构模板方法模式
- 本文主要分为四部分:(1)JDBC规范与连接MySQL(2)JdbcTemplate连接MySQL(3)分析JdbcTemplate模板方法设计模式(4)手写简单版JdbcTemplate示例模板方法设计模式
- 示例代码可以通过启动项目调用JdbcController类originalJdbc方法(原生jdbc连接MySQL)、jdcbTemplate方法(jdbc连接MySQL)、defineJcbcTemplate方法(手写jdbcTemplate)查看结果。
-
代码结构
项目结构.png
一、JDBC规范与JDBC连接MySQL数据库
(1)JDBC的全称是Java Database Connectivity,设计初衷是提供一套能够应用于各种数据库的统一标准,这套标准需要不同数据库厂家之间共同遵守,并提供各自的实现方案供 JDBC应用程序调用,JDBC规范架构图如下图所示。
![](https://img.haomeiwen.com/i17109776/b700fab2ad5fbc48.png)
(2)JDBC 规范中的核心编程对象包括 DriverManger、DataSource、Connection、Statement,及 ResultSet。
- DriverManger,负责加载各种不同的驱动程序(Driver)
- DataSource主要是从数据库连接池中获取数据库连接对象Connection,如果没有DataSource和数据库连接池ConnectionPoolDataSource,通过DriverManger获取数据库连接对象Connection,建立连接和销毁连接的过程产生的开销较大,故需要有数据库连接池和DataSource,与线程池类似。
- Connection数据库连接对象,可以理解为会话,代表一个数据库连接,负责完成与数据库直连的通信。
- Statement负责执行sql语句。
- ResultSet执行sql之后的结果。
(3)Jdbc连接MySQL数据库查询id为1的学生
- 领域对象Student类
public class Student {
private Long id;
private String name;
private Integer age;
}
- 添加c3p0 datasource作为数据库连接池
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
- 连接MySQL数据库
try {
//获取连接
Connection connection = dataSource.getConnection();
//执行查询
PreparedStatement preparedStatement = connection.prepareStatement("select * from student where id = 1");
//获取执行结果
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 规范进行数据库访问的整个开发流程:
创建DataSource -> 获取Connection -> 创建Statment -> 执行sql语句 -> 处理ResultSet -> 关闭资源对象。
(5)用jdbc查询数据库的过程中,主要有两个部分,一部分是需要准备和释放资源,另一部分是处理结果,对于业务来说,我们只需要处理数据库查询结果,而准备和释放资源,是多余的部分且重复的,如果用jdbc在实际开发中,每次都需要写额外的代码,故Spring框架的JdbcTemplate工具应运而生。
二、JdbcTemplate连接MySQL
- 添加jdbc starter起步依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
- yaml文件配置MySQL数据库连接池
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进行数据库查询,queryForObject方法第二个参数是一个函数式接口,接口参数是传入jdbc查询结果ResultSet,返回处理结果,这部分交由调用者处理,我写了rowMapper处理此次查询结果。
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void jdcbTemplate() {
//省去建立数据库连接与连接释放资源
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,然后对执行结果进行处理,省去了建立数据库连接与连接释放资源的重复操作,其中用的是模板方法设计模式,把重复的动作封装起来,需要处理的部分开放接口,让调用者去实现。
三、JdbcTemplate模板方法设计模式与回调方法
- RowMapper函数式接口,方法入参是jdbc查询结果ResultSet,返回的是我们实现完该函数式接口的返回结果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);
}
- 继续点进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)));
}
- 继续点进query(sql, new RowMapperResultSetExtractor<>(rowMapper))),这里QueryStatementCallback实现StatementCallback接口,核心逻辑是stmt.executeQuery(sql)执行sql,rse.extractData(rs)处理结果,即上一步说到的处理结果,最后调用的是execute(new QueryStatementCallback(), true),把实现好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);
}
- 继续点进execute方法,可以看到跟步骤一用jdbc连接数据库一样,可以回顾步骤一的基于 JDBC 规范进行数据库访问的整个开发流程,也是创建DataSource,获取Connection对象连接,创建Statment,关闭资源对象,这些额外重复的操作都在execute方法封装起来了,这也是模板方法设计模式的思想,把重复的、额外的操作封装起来,定义好开放接口,交由子类实现,在execute方法中需要实现的接口方法是action.doInStatement(stmt),上一步的StatementCallback已经实现好了该接口类的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函数式接口,回调作用,处理jdbc查询结果ResultSet,返回泛型T对象
@FunctionalInterface
public interface DefineRowMapper <T>{
T mapRow(ResultSet rs) throws SQLException;
}
- DefineJcbcTemplateImpl实现类,该实现类的连接数据库、释放资源等操作都是从步骤一负责粘贴进来的,唯一不同的是把处理结果解耦出来了,定义好处理结果的接口DefineRowMapper,交由调用者去实现对结果的处理,最后回调该接口的方法mapRow并返回结果,其他的把连接数据库、释放资源封装起来,这样一来不用每次进行数据库查询都需要连接数据库、释放资源。
@Service
@Slf4j
public class DefineJcbcTemplateImpl implements DefineJcbcTemplate {
@Autowired
private DataSource dataSource;
@Override
public <T> T queryForObject(String sql, DefineRowMapper<T> defineRowMapper) {
//一部分是准备和释放资源以及执行 SQL 语句,另一部分则是处理 SQL 执行结果
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//创建dataSource,获取连接
connection = dataSource.getConnection();
//执行查询
preparedStatement = connection.prepareStatement(sql);
//获取执行结果
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查询数据库。
@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;
}
网友评论