写在前面 上一篇讲了策略模式,知道了可以使用策略模式对多重if-else进行优化,而且符合开闭原则。那么除了策略模式,还有什么设计模式比较好用而且常用的呢。这就是今天要讲的模板模式。
模板模式解决什么问题呢?
正片开始 首先我们使用SpringBoot来搭建一个工程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <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;
创建一个连接工厂类ConnectFactory,获取数据库连接
1 2 3 4 5 6 7 8 9 10 11 12 13 public class ConnectFactory { public static Connection getConnection () throws Exception { 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;
接着在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()) { 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()) { 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 @Component public class DaoTemplate { public <T> T query (String sql, Class<T> clazz) throws Exception { 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())); for (Field field : fields) { String fieldName = field.getName(); 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); } method.invoke(t, fieldValue); } } } catch (Exception e) { e.printStackTrace(); } return t; } 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; }