抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

写在前面

上一篇讲了策略模式,知道了可以使用策略模式对多重if-else进行优化,而且符合开闭原则。那么除了策略模式,还有什么设计模式比较好用而且常用的呢。这就是今天要讲的模板模式。

模板模式解决什么问题呢?

正片开始

首先我们使用SpringBoot来搭建一个工程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- maven配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

application.yml配置如下:

1
2
3
4
5
6
7
8
server:
port: 8888
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8
username: 账号
password: 密码
driver-class-name: com.mysql.jdbc.Driver

创建一个全局配置类GlobalProperties,我们通过这个类可以获取yml的配置信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component("globalProperties")
public class GlobalProperties {

@Value("${spring.datasource.driver-class-name}")
private String driverClass;

@Value("${spring.datasource.url}")
private String url;

@Value("${spring.datasource.username}")
private String username;

@Value("${spring.datasource.password}")
private String password;

//字段对应的getter、setter方法...

创建一个连接工厂类ConnectFactory,获取数据库连接

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ConnectFactory {

public static Connection getConnection() throws Exception{
//获取yml文件的配置,SpringContextUtil工具类在上一篇文章有介绍,可以参考上一篇文章的代码
GlobalProperties properties = SpringContextUtil
.getBean("globalProperties", GlobalProperties.class);
//加载数据驱动
Class.forName(properties.getDriverClass());
//获取数据库连接,返回数据库连接对象
return DriverManager.getConnection(properties.getUrl(),
properties.getUsername(), properties.getPassword());
}
}

创建实体类User

1
2
3
4
5
6
7
8
9
10
11
public class User {

private Integer id;

private String name;

private Integer age;

private String job;

//字段对应的getter、setter方法...

接着在mysql对应的数据库创建数据表tb_user,sql语句如下:

1
2
3
4
5
6
7
CREATE TABLE `tb_user` (
`id` bigint(10) NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(255) NOT NULL COMMENT '名称',
`age` tinyint(4) NOT NULL COMMENT '年龄',
`job` varchar(255) DEFAULT NULL COMMENT '工作',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4

插入一些测试数据

1
2
3
4
5
INSERT INTO tb_user(`name`,`age`,`job`) VALUES('大司马',36,'厨师');
INSERT INTO tb_user(`name`,`age`,`job`) VALUES('朴老师',36,'主播');
INSERT INTO tb_user(`name`,`age`,`job`) VALUES('王刚',30,'厨师');
INSERT INTO tb_user(`name`,`age`,`job`) VALUES('大sao',32,'美食up主');
INSERT INTO tb_user(`name`,`age`,`job`) VALUES('姚大秋',35,'主持人');

假设我们有一张user表,我们通过原生的JDBC来进行数据库操作,那么需要在dao层完成以下几步。

1.装载相应的数据库的JDBC驱动并进行初始化

2.建立JDBC和数据库之间的Connection连接

3.创建Statement或者PreparedStatement接口,执行SQL语句

4.处理和显示结果

5.释放资源

例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
private static final String GET_USER_BY_NAME_SQL = "SELECT `id`,`name`,`age`,`job` FROM `tb_user` WHERE `name` = '%s'";

@Override
public User getUserByName(String name) throws Exception {
User user = new User();
//获取数据连接
try (Connection connection = ConnectFactory.getConnection();
Statement statement = connection.createStatement();
ResultSet resultSet = statement
.executeQuery(String.format(GET_USER_BY_NAME_SQL, name))
) {
while (resultSet.next()) {
//获取id
user.setId(resultSet.getInt("id"));
//获取名称
user.setName(resultSet.getString("name"));
//获取年龄
user.setAge(resultSet.getInt("age"));
//获取工作
user.setJob(resultSet.getString("job"));
}
} catch (Exception e) {
e.printStackTrace();
}
return user;
}

private static final String GET_USER_BY_ID_SQL = "SELECT `id`,`name`,`age`,`job` FROM `tb_user` WHERE `id` = '%s'";
@Override
public User getUserById(Integer id) throws Exception {
User user = new User();
//获取数据库连接
try (Connection connection = ConnectFactory.getConnection();
Statement statement = connection.createStatement();
ResultSet resultSet =
statement.executeQuery(String.format(GET_USER_BY_ID_SQL, id))
) {
while (resultSet.next()) {
//获取id
user.setId(resultSet.getInt("id"));
//获取名称
user.setName(resultSet.getString("name"));
//获取年龄
user.setAge(resultSet.getInt("age"));
//获取工作
user.setJob(resultSet.getString("job"));
}
} catch (Exception e) {
e.printStackTrace();
}
return user;
}

PS:这里为了简单一点就直接把参数拼接sql语句,不采用预编译来处理sql的参数。

问题分析

通过上面的代码,我们很明显可以看到是有很大的问题的。

1.每次在进行数据库操作都需要获取Connection对象,创建Statement对象。

2.每次获取结果后,都要进行结果处理,而且如果是同一张表的查询,会很重复。每次都需要把结果值set回到对象的字段中。

模板模式就可以解决这个问题!

使用模板模式重构代码

第一步

创建一个模板类DaoTemplate,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/**
* @author Ye Hongzhi
* @program DaoTemplate
* @description
* @date 2020-04-12 17:04
**/
@Component
public class DaoTemplate {

public <T> T query(String sql, Class<T> clazz) throws Exception {
//通过clazz创建返回值对象
T t = clazz.newInstance();
//获取数据库连接
try (Connection connection = ConnectFactory.getConnection();
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql)
) {
while (resultSet.next()) {
//获取所有字段
Field[] fields = clazz.getDeclaredFields();
//获取所有方法
Method[] methods = clazz.getDeclaredMethods();
Map<String, Method> methodNameMap = Arrays.stream(methods)
.collect(Collectors.toMap(Method::getName, Function.identity()));
//把数据库对应的列的值赋值给 泛型T对象的 对应的字段
for (Field field : fields) {
//获取字段名
String fieldName = field.getName();
//获取set方法
Method method = methodNameMap.get("set" + change(fieldName));
//获取数据库的列的值
Object fieldValue = null;
if (field.getType() == String.class) {
fieldValue = resultSet.getString(fieldName);
}
if (field.getType() == Integer.class) {
fieldValue = resultSet.getInt(fieldName);
}
if (field.getType() == Boolean.class) {
fieldValue = resultSet.getBoolean(fieldName);
}
if (field.getType() == Long.class) {
fieldValue = resultSet.getLong(fieldName);
}
if(field.getType() == Double.class){
fieldValue = resultSet.getDouble(fieldName);
}
if(field.getType() == BigDecimal.class){
fieldValue = resultSet.getBigDecimal(fieldName);
}
if (field.getType() == Date.class) {
fieldValue = resultSet.getDate(fieldName);
}
//设置更多的字段类型...
//利用反射执行对象的set方法,把数据库的值设置到对象的字段中
method.invoke(t, fieldValue);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return t;
}

/**
* 将一个字符串首字母大写,其它字母小写
*
* @param str 字符串
* @return
*/
private static String change(String str) {
return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase();
}
}

第二步

创建模板后,可以在DAO层引入模板,然后使用。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//引入模板	
@Resource
private DaoTemplate daoTemplate;

private static final String GET_USER_BY_ID_SQL = "SELECT `id`,`name`,`age`,`job` FROM `tb_user` WHERE `id` = '%s'";

@Override
public User getUserById(Integer id) throws Exception {
//使用模板的方法,查询
return daoTemplate.query(String.format(GET_USER_BY_ID_SQL, id), User.class);
}

private static final String GET_USER_BY_NAME_SQL = "SELECT `id`,`name`,`age`,`job` FROM `tb_user` WHERE `name` = '%s'";

@Override
public User getUserByName(String name) throws Exception {
//使用模板的方法,查询
return daoTemplate.query(String.format(GET_USER_BY_NAME_SQL, name), User.class);
}

哇喔!突然间代码就显得清爽很多了!

小伙伴们看到这里,get到新的技能了吗?

扩展知识

实际上在Spring框架就有提供JDBC模板

我们可以在MAVEN中引入以下配置:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

创建一个映射类UserRowMapper

1
2
3
4
5
6
7
8
9
10
11
public class UserRowMapper implements RowMapper<User> {
@Override
public User mapRow(ResultSet resultSet, int i) throws SQLException {
User user = new User();
user.setId(resultSet.getInt("id"));
user.setName(resultSet.getString("name"));
user.setAge(resultSet.getInt("age"));
user.setJob(resultSet.getString("job"));
return user;
}