使用模板方法模式和策略模式封装jdbc操作

        GoF在比喻描写模板方法模式时使用了著名的"好莱坞原则"----- “不要给我们打电话,我们会给你打电话(don‘t call us, we‘ll call you)”这是著名的好莱坞原则。好莱坞原则的关键之处是演艺公司对整个娱乐项的完全控制,应聘的演员只能被动地服从总项目流程的安排,在需要的时候完成流程中得一个具体环节。好莱坞原则的体现了模板模式的关键:子类可以置换掉父类的可变部分,但是子类却不可以改变模板方法所代表的顶级逻辑。

      模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤的实现延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中某些步骤的具体实现,HttpServlet技术就是建立在模板方法模式的基础之上的。

 模板方法模式适合场景

  • 一次性实现一个算法的不变部分,并将可变的行为留给子类来实现。
  • 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。
  • 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。

       上一节案例中,我们使用JDBC的dao模式完成图书的crud时,会重复的写很多重复的代码,比如JDBC访问数据库的步骤:

  1. 注册驱动(只做一次,在新的jdbc规范中,可以根据其META-INF信息自动创建其驱动)
  2. 创建数据库连接
  3. 根据连接创建语句(预编译语句对象)
  4. 对预编译语句中的?占位符进行设置具体值
  5. 执行sql语句
  6. 处理执行结果
  7. 释放资源

       这些步骤就是jdbc处理数据库的顶级逻辑。我们可以采用模板方法模式来改写一些拥有相同功能的相关类,将可复用的一般性行为代码移到基类里面,而把特殊化的行为代码移到子类里面。咋前一节案例介绍中,发现执行sql语句的两个公共操作:executeUpdate(增删改操作)和executeQuery(查询操作),里面的每一个业务操作都会涉及到上面的JDBC步骤,这样出现大量重复代码而造成代码的不友好以及代码失控,比如某些方法里资源没有释放或者没有正确释放都会导致我们的代码失控,在这样的情况下我们可以采用模板方法设计模式来对器进行改造,使得对数据库的操作更加方便、友好及可控。

此处我们针对jdbc的公共操作公共行为提供统一流程处理模板方法:

package com.wise.dao;  
  
//****************** import *************************//  
public class JdbcTemplate {  
    /** 
     *  执行dml语句模板方法 
     * @param sql 传递的sql语句 
     * @param params 预编译语句的站位参数 
     * @return 影响数据行数 
     */  
    public int executeUpdate(String sql,Object... params){  
        var ret = 0;  
        try(var conn = DBHelper.getConnection();  
            var ptst = conn.prepareStatement(sql)){  
            for(int i = 0; params.length > 0 && i < params.length; i++)  
                ptst.setObject(i + 1,params[i]);  
            ptst.executeUpdate();  
        }catch (SQLException e){  
            e.printStackTrace();  
        }  
        return ret;  
    }  
}

     当然,针对executeUpdate数据库操作方法中算法步骤都会有。因此我们可以把这部分不变的内容提取出来,作为一个公用的方法。那么对于查询方法我们也可以提取出公共的算法步骤,但是返回的结果集的处理方式是不一样的(不同的结果集封装成不同的返回对象),这样,我们应该如何处理呢?这里的话我们可以利用策略模式进行改造。

       策略模式把行为和环境分割开来。环境类负责维持和查询行为类,各种算法则在具体的策略中提供。由于算法和环境独立开来,算法的各类增减、修改都不会影响环境和客户端。是对算法的包装,把使用算法的责任和算法本身分割开,委派给不同的对象管理。比如TreeSet等所定义的排序算法责任和其算法本身进行分离。在我们的使用案例中,我们需要对查询到的结果集确立算法责任(处理结果集):

/** 
 * 结果集处理Handler 
 * @Author: <a href="mailto:1020zhaodan@163.com">Adan</a> 
 * @Date: 2019/4/15 0015 11:24 
 * @version: 1.0-SNAPSHOT 
 */  
@FunctionalInterface  
public interface ResultSetHandler<T> {  
    /** 
     * 处理结果集,将结果集转为T的实例 
     * @param rs 结果集 
     * @return T的实例(具体类型) 
     * @throws SQLException 
     */  
    T handler(ResultSet rs) throws SQLException;  
}  

    这种类型的设计模式属于行为型模式一个类的行为或其算法可以在运行时更改。比如我们使用TreeSet时我们给其指定排序策略。这里我们需要指定结果集处理策略ResultSetHandler

结合模板方法模式:

public class JdbcTemplate {  
    /** 
     *  执行dml语句模板方法 
     * @param sql 传递的sql语句 
     * @param params 预编译语句的站位参数 
     * @return 影响数据行数 
     */  
    public int executeUpdate(String sql,Object... params){  
        var ret = 0;  
        try(var conn = DBHelper.getConnection();  
            var ptst = conn.prepareStatement(sql)){  
            for(int i = 0; params.length > 0 && i < params.length; i++)  
                ptst.setObject(i + 1,params[i]);  
            ptst.executeUpdate();  
        }catch (SQLException e){  
            e.printStackTrace();  
        }  
        return ret;  
    }  
  
    /** 
     * 执行dql语句模板方法,封装公共步骤 
     * @param sql 传递的sql语句 
     * @param handler 结果集处理handler(策略模式:处理结果集的策略,在运行时指定) 
     * @param params 预编译语句的站位参数 
     * @param <T> 返回类型 
     * @return 查询结果 
     */  
    public <T> T executeQuery(String sql, ResultSetHandler<T> handler, Object... params){  
        T ret = null;  
        try(var conn = DBHelper.getConnection();  
            var ptst = conn.prepareStatement(sql)){  
            for(int i = 0; params.length > 0 && i < params.length; i++)  
                ptst.setObject(i + 1,params[i]);  
            var rs = ptst.executeQuery();  
            ret = handler.handler(rs);  
        }catch (SQLException e){  
            e.printStackTrace();  
        }  
        return ret;  
    }  
}  

        对结果集的处理在运行时提供相应的策略。在此处,我提供一组常用处理结果集算法

将结果集封装为map(一个map代表一条记录)

/** 
 * 单条结果集处理为Map 
 * @Author: <a href="mailto:1020zhaodan@163.com">Adan</a> 
 * @Date: 2019/4/15 0015 11:36 
 * @version: 1.0-SNAPSHOT 
 */  
public class MapHandler implements ResultSetHandler<Map<String,Object>> {  
    @Override  
    public Map<String, Object> handler(ResultSet rs) throws SQLException {  
        Map<String, Object> map = null;  
        if(rs.next()){  
            map = new HashMap<>();  
            var meta = rs.getMetaData();//获取结果集元数据  
            for(int i = 1; i <= meta.getColumnCount();i++)  
                map.put(meta.getColumnName(i),rs.getObject(i));  
        }  
        return map;  
    }  
}  

将结果集封装为一个具体的Bean

/** 
 * @Author: <a href="mailto:1020zhaodan@163.com">Adan</a> 
 * @Date: 2019/4/15 0015 11:33 
 * @version: 1.0-SNAPSHOT 
 */  
public class BeanHandler<T> implements ResultSetHandler<T> {  
    private Class<T> clazz;  
    public BeanHandler(Class<T> clazz){  
        this.clazz = clazz;  
    }  
    @Override  
    public T handler(ResultSet rs) throws SQLException {  
        var handler = new MapHandler();//将单个结果集转为map  
        var map = handler.handler(rs);  
       //再将map转为具体的Bean实例(可采用BeanUtils工具类,下面给出大致算法(BeanUtil))  
        return map == null ? null : BeanUtil.map2bean(clazz,map);  
    }  
}  

 将一个Map转为一个实体Bean,map中得key即为数据表字段对应为实体的相关属性。

public class BeanUtil {  
    /** 
     * 将一个map转为一个javaBean实例(object),只能处理基本类型 
     * @param clazz 具体的javaBean类型[Book?User?Topic?Replay] 
     * @param result map 
     *                key       value 
     *                id          1 
     *               title      射雕英雄传 
     *               author     金庸 
     *               publisher  三联出版社 
     *               intro      这是武侠小说里里程碑 
     *               publish_date/publishDate 
     * 
     * @param <T> 类型化参数,类型的确定 
     * @return object 
     *         Book[id:1,title:射雕英雄传,author:金庸] 
     */  
    public static <T> T map2bean(Class<T> clazz,Map<String,?> result){  
        T ret = null;  
        try {  
            //通过反射调用clazz的默认构造方法创建该clazz类型的实例:Book ret = new Book();  
            ret = clazz.getDeclaredConstructor().newInstance();  
            var beanInfo = Introspector.getBeanInfo(clazz,Object.class);  
            var pds = beanInfo.getPropertyDescriptors();  
            for(var pd : pds){  
                //获取属性名(属性名对应map的key,map的key为数据库字段)  
                var readMethod = pd.getReadMethod();  
                var key = readMethod.getAnnotation(Column.class) == null ?  
                        readMethod.getAnnotation(Id.class) == null ? pd.getName() : readMethod.getAnnotation(Id.class).value()  
                        : readMethod.getAnnotation(Column.class).value();  
                //获取该属性对应的setter  
                var method = pd.getWriteMethod();  
                if(result.containsKey(key) && method != null){  
                    var value = result.get(key);  
                    //判断属性的类型和value属性是否一致(value.getClass()是否是pd.getPropertyType()的类型以及子类型)  
                    if(value != null) {//需要对各种类型进行判断,此处省略,只是基本原理的介绍,还有复杂的类型处理需要借助第三方工具类  
                        if (!pd.getPropertyType().isAssignableFrom(value.getClass())) {  
                            if (pd.getPropertyType() == float.class || pd.getPropertyType() == Float.class)  
                                value = Float.valueOf(value.toString());  
                            if(pd.getPropertyType() == LocalDate.class)  
                                //将数据库date转为LocalDate  
                                value = LocalDate.ofInstant(new Date(((java.sql.Date)value).getTime()).toInstant(),ZoneId.systemDefault());  
                        }  
                    }  
                    method.invoke(ret,value);  
                }  
            }  
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException |IntrospectionException e) {  
            e.printStackTrace();  
        }  
        return ret;  
    }  
}  

        当然大家注意到了取key的时候读取了属性身上的注解,该注解是自定义的用于映射属性和表字段不一致的情况。

@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.METHOD)  
@Documented  
public @interface Column {  
    /** 
     * 属性名和数据库字段的映射 
     * @return 数据库表字段名称 
     */  
    String value();  
}  

多条记录将结果集封装成一个List的算法

public class ListBeanHandler<T> implements ResultSetHandler<List<T>> {  
    private Class<T> clazz;  
    public ListBeanHandler(Class<T> clazz){  
        this.clazz = clazz;  
    }  
    @Override  
    public List<T> handler(ResultSet rs) throws SQLException {  
        var ret = new ArrayList<T>();  
        while(rs.next()){  
           var map = new HashMap<String,Object>();  
           var meta = rs.getMetaData();  
           for (int i = 1; i <= meta.getColumnCount(); i++)  
                map.put(meta.getColumnName(i),rs.getObject(i));  
           ret.add(BeanUtil.map2bean(clazz,map));  
        }  
        return ret;  
    }  
}  

BookDaoImpl

public class BookDaoImpl implements BookDao {  
    private JdbcTemplate template = new JdbcTemplate();  
    @Override  
    public void persistent(Book book) {  
        var sql = "INSERT INTO tb_book(title,author,price,publisher,intro) VALUES (?,?,?,?,?)";  
        template.executeUpdate(sql,  
                book.getTitle(),book.getAuthor(),book.getPrice(),book.getPublisher(),book.getIntro());  
    }  
  
    @Override  
    public void delete(Integer id) {  
       var sql = "DELETE FROM tb_book WHERE id = ?";  
       template.executeUpdate(sql,id);  
    }  
  
    @Override  
    public void update(Book book) {  
        var sql = "UPDATE tb_book SET title = ?,author=?,price=?,publisher=?,intro=? WHERE id = ?";  
        template.executeUpdate(sql,  
                book.getTitle(),book.getAuthor(),book.getPrice(),book.getPublisher(),book.getIntro(),book.getId());  
    }  
  
    @Override  
    public Book search(Integer id) {  
        var sql = "SELECT id,title,author,price,publisher,intro FROM tb_book WHERE id = ?";  
        return template.executeQuery(sql,new BeanHandler<>(Book.class),id);  
    }  
  
    @Override  
    public List<Book> search() {  
        var sql = "SELECT id,title,author,price,publisher,intro FROM tb_book";  
        return template.executeQuery(sql,new ListBeanHandler<>(Book.class));  
    }  
  
    @Override  
    public long getCount() {  
        var sql = "SELECT COUNT(1) FROM tb_book";  
        return template.executeQuery(sql,rs->rs.next() ? rs.getLong(1) : 0);  
    }  
}  

针对单行单列的结果集:运行期间使用lambda表达式即可

@Override  
public long getCount() {  
    var sql = "SELECT COUNT(1) FROM tb_book";  
    return template.executeQuery(sql,rs->rs.next() ? rs.getLong(1) : 0);  
}  

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值