
本文共 9507 字,大约阅读时间需要 31 分钟。
写在前面
上一篇讲了策略模式,知道了可以使用策略模式对多重if-else进行优化,而且符合开闭原则。那么除了策略模式,还有什么设计模式比较好用而且常用的呢。这就是今天要讲的模板模式。
模板模式解决什么问题呢?
正片开始
首先我们使用SpringBoot来搭建一个工程。
<!-- 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配置如下:
server: port: 8888spring: 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的配置信息
@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
,获取数据库连接
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
public class User { private Integer id; private String name; private Integer age; private String job; //字段对应的getter、setter方法...
接着在mysql对应的数据库创建数据表tb_user
,sql语句如下:
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
插入一些测试数据
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.释放资源
例子如下:
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
,如下:
/** * @author Ye Hongzhi * @program DaoTemplate * @description * @date 2020-04-12 17:04 **/@Componentpublic 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
层引入模板,然后使用。如下:
//引入模板 @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
中引入以下配置:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>
创建一个映射类UserRowMapper
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; }}
然后在DAO
层的UserDaoImpl
,我们就可以引入JdbcTemplate
@Resource private JdbcTemplate jdbcTemplate; //使用jdbcTemplate查询 @Override public User getUserByName(String name) throws Exception { return jdbcTemplate.queryForObject(String.format(GET_USER_BY_NAME_SQL, name), new UserRowMapper()); }
从这里可以看出实际上Spring
框架就是采用这种思想来实现JdbcTemplate
模板。
结束语
所以在实际项目的开发中,我们有时候遇到某些代码块的前后都有重复操作时,可以采用模板模式去重构代码,使代码更加简洁,容易维护。
想第一时间看到我更新的文章,可以微信搜索公众号「java技术爱好者
」,拒绝做一条咸鱼,我是一个努力让大家记住的程序员。我们下期再见!!!
能力有限,如果有什么错误或者不当之处,请大家批评指正,一起学习交流!
发表评论
最新留言
关于作者
