一、什么是模板设计模式?
在面向对象编程过程中,程序猿们经常会遇到这样的问题:
设计一个系统时,知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未确定,或者说某些步骤的实现与具体环境有关,这些未确定的步骤需要延迟到子类实现。
例如:
去银行办理业务一般需经过以下4个步骤:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。
1.1 模板模式(Template Pattern)的定义
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。
1.2 模板模式的结构
模板方法模式包含以下主要角色:
(1) 抽象类:负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下:
① 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法
② 基本方法:是整个算法中的一个步骤,包含以下几种类型
- 抽象方法:在抽象类中申明,由具体子类实现
- 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它
- 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种
(2) 具体子类:实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。
1.3 模板模式的优缺点
优点:
(1)具体细节步骤实现定义在子类中,子类定义详细处理算法是不会改变算法整体结构。
(2)代码复用的基本技术,在数据库设计中尤为重要。
(3)存在一种反向的控制结构,通过一个父类调用其子类的操作,通过子类对父类进行扩展增加新的行为,符合“开闭原则”。
缺点:
(1)每个不同的实现都需要定义一个子类,会导致类的个数增加,系统更加庞大。
二、糟糕的JDBC代码
public class Main {
public static void main(String[] args) {
//查询数据
read();
//数据插入
create();
//数据更新
update();
//删除数据
delete();
}
/**
* 查数据
*/
private static void read() {
// 载数据库驱动
loadDBDriver();
String sql = "select name, userid from user where userid > ? order by userid";
Connection connection = null;
PreparedStatement ps = null;
try {
// 创建数据库连接
connection = getConnection();
// 创建语句对象
ps = connection.prepareStatement(sql);
// 绑定参数
ps.setInt(1, 0);
ResultSet rs = ps.executeQuery();
//遍历结果集
while (rs.next()) {
System.out.printf("name: %s id: %d \n",
rs.getString("name"),
rs.getInt("userid"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
/**
* 插入数据
*/
private static void create() {
// 载数据库驱动
loadDBDriver();
String sql = "insert into user (userid, name) values (?, ?)";
Connection connection = null;
PreparedStatement ps = null;
try {
// 建数据库连接
connection = getConnection();
// 创建语句对象
ps = connection.prepareStatement(sql);
// 绑定参数
ps.setInt(1, 999);
ps.setString(2, "Tony999");
// 执行SQL语句
int count = ps.executeUpdate();
System.out.printf("成功插入%d条数据.\n", count);
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
/**
* 更新数据
*/
private static void update() {
// 载数据库驱动
loadDBDriver();
String sql = "update user set name=? where userid =?";
Connection connection = null;
PreparedStatement ps = null;
try {
// 创建数据库连接
connection = getConnection();
// 创建语句对象
ps = connection.prepareStatement(sql);
// 绑定参数
ps.setString(1, "Tom999");
ps.setInt(2, 999);
// 执行SQL语句
int count = ps.executeUpdate();
System.out.printf("成功更新%d条数据.\n", count);
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
/**
* 删除数据
*/
private static void delete() {
// 载数据库驱动
loadDBDriver();
String sql = "delete from user where userid = ?";
Connection connection = null;
PreparedStatement ps = null;
try {
// 创建数据库连接
connection = getConnection();
// 创建语句对象
ps = connection.prepareStatement(sql);
// 绑定参数
ps.setInt(1, 999);
// 执行SQL语句
int count = ps.executeUpdate();
System.out.printf("成功删除%d条数据.\n", count);
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
/**
* 建立数据库连接
*
* @return 返回数据库连接对象
* @throws SQLException
*/
private static Connection getConnection() throws SQLException {
String url = "jdbc:mysql://localhost:3306/mydb?verifyServerCertificate=false&useSSL=false";
String user = "root";
String password = "12345";
Connection connection = DriverManager.getConnection(url, user, password);
return connection;
}
/**
* 加载数据库驱动
*/
private static void loadDBDriver() {
// 1.
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
上述代码中访问数据的方法有4个read()、create()、update()和delete()。其中create()、update()和delete()三个方法代码非常相似,只是SQL语句和绑定参数不同而已。虽然read()方法与create()、update()和delete()方法不同,但是差别也不大。
JDBC代码主要的问题是:大量的重复代码!!!
三、模板设计模式实现JDBC开发
让我们来总结一下 JDBC实现步骤:
- 加载数据库驱动
- 建立数据库连接
- 编写SQL语句
- 创建操作命令
- 执行SQL语句
- 处理返回结果集
- 关闭结果集
- 关闭操作命令
- 关闭连接
以上步骤中只有3个步骤是需要更改的,即编写SQL语句、执行SQL语句、处理返回结果集
因此我们可以创建一个抽象父类 AbstractJDBCTemplate,对于固定不变的步骤,每个步骤单独封装为一个方法:
public abstract class AbstractJDBCTemplate {
private Connection connection;
private Statement statement;
private ResultSet resultSet;
//1.加载数据库驱动
private void loadDriver() throws ClassNotFoundException {
Class.forName("com.mysql.jdbc.Driver");
}
//2.建立数据库连接
private void createConnection() throws SQLException {
String url = "jdbc:mysql://127.0.0.1:3306/memo?useSSL=false";
String username = "root";
String password = "root";
this.connection = DriverManager.getConnection(url, username, password);
}
//4.创建操作命令
private void createStatement() throws SQLException {
this.statement = this.connection.createStatement();
}
//7.8.9 关闭资源
private void close() {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
接下来,对于那些需要更改的步骤,要定义为抽象方法,具体的实现由子类来完成
在这里,我们把第5步(执行SQL语句)定义为两个普通方法,分别用来执行SELECT语句和其他非查询语句
//5.执行SQL语句 R /CUD
//5.1 执行SELECT语句
private void callQuery(String sql) throws SQLException {
this.resultSet = this.statement.executeQuery(sql);
}
//5.2 执行update delete insert语句
private Integer callUpdate(String sql) throws SQLException {
return this.statement.executeUpdate(sql);
}
//6.处理返回结果集 ResultSet/Integer
public abstract void handlerResult(ResultSet resultSet) throws SQLException;
public abstract void handlerResult(Integer value) throws SQLException;
然后,在父类中定义一个执行方法execute(),用来定义各个步骤的执行顺序,异常处理以及判断执行SQL语句方法的条件
public final void execute(String sql) {
try {
this.loadDriver();
this.createConnection();
this.createStatement();
//SQL通过方法传入
//select返回类型为ResultSet update delete insert返回值类型为Integer
//select SELECT sELECT
if (sql.trim().toUpperCase().startsWith("SELECT")) {
this.callQuery(sql);
this.handlerResult(this.resultSet);
} else {
Integer effect = this.callUpdate(sql);
this.handlerResult(effect);
}
this.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
最后,我们需要编写一个子类继承AbstractJDBCTemplate类,然后实现父类抽象方法
class JDBCTemplateCase extends AbstractJDBCTemplate {
@Override
public void handlerResult(ResultSet resultSet) throws SQLException {
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
LocalDateTime createdTime = resultSet.getTimestamp("created_time").toLocalDateTime();
LocalDateTime modifyTime = resultSet.getTimestamp("modify_time").toLocalDateTime();
System.out.println(id + " " + name + " " + createdTime + " " + modifyTime);
}
}
@Override
public void handlerResult(Integer value) throws SQLException {
System.out.println("更新结果:" + value);
}
}
以上就是使用模板设计模式实现JDBC开发的过程,这样就实现了代码的复用,在这个基础上,我们还可以进一步优化:
即使用泛型接口代替“处理返回结果集”步骤的两个抽象方法
处理返回结果集方法传入的参数有两种类型:ResultSet和Integer,返回值也有两种类型
由此我们想到了使用泛型,即定义参数类型P,返回值类型R
public class JDBCTemplate {
private Connection connection;
private Statement statement;
private ResultSet resultSet;
//定义模版执行流程
public final <P, R> R execute(String sql, Handler<P, R> handler) {
R result =null; //处理结果分为两种类型ResultSet和Integer
try {
this.loadDriver();
this.createConnection();
this.createStatement();
//SQL通过方法传入
//R ResultSet
//CUD Integer
//select update delete insert
//select SELECT sELECT
if (sql.trim().toUpperCase().startsWith("SELECT")) {
this.callQuery(sql);
//参数分为ResultSet/Integer两种类型,返回值类型也不相同,想要使用同一个模板处理SQL语句,使用泛型
result = handler.handle((P) this.resultSet);
} else {
Integer effect = this.callUpdate(sql);
result = handler.handle((P) effect);
}
this.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return result;//返回结果集
}
//1.加载驱动
private void loadDriver() throws ClassNotFoundException {
Class.forName("com.mysql.jdbc.Driver");
}
//2. 创建连接
private void createConnection() throws SQLException {
String url = "jdbc:mysql://127.0.0.1:3306/memo?useSSL=false";
String username = "root";
String password = "root";
this.connection = DriverManager.getConnection(url, username, password);
}
//3.准备SQL
//SQL语句通过参数传入
//4.创建命令
private void createStatement() throws SQLException {
this.statement = this.connection.createStatement();
}
//5.执行命令CRUD R CUD
//public abstract void callStatement();
private void callQuery(String sql) throws SQLException {
this.resultSet = this.statement.executeQuery(sql);
}
private Integer callUpdate(String sql) throws SQLException {
return this.statement.executeUpdate(sql);
}
//6.处理返回结果集 ResultSet Integer
//7.8.9 关闭资源
private void close() {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
//泛型接口
//函数接口
@FunctionalInterface
interface Handler<P, R> {
//参数是P,返回值是R
R handle(P p) throws SQLException;
}