`
Adan-Chiu
  • 浏览: 21676 次
社区版块
存档分类
最新评论

使用模板方法模式和策略模式简化JDBC操作

    博客分类:
  • 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);
    }

 

分享到:
评论

相关推荐

    数据库访问层的实现(一)——模板方法模式

    首先,我们需要定义一个抽象的模板类,比如`DBAccessTemplate`,在这个类中声明并实现基本的数据库操作方法。这些方法通常是抽象的或保护的,以供子类继承和扩展。例如: ```java public abstract class ...

    spring-jdbc jar包.rar

    1. **JdbcTemplate**:这是Spring JDBC的核心类,它通过模板方法模式将常见的JDBC操作进行了封装,如执行SQL查询、更新、调用存储过程等。开发者只需要关注SQL语句和参数,而无需处理连接创建、关闭、异常处理等繁琐...

    Spring+JDBC实例

    JdbcTemplate通过模板方法模式简化了事务管理、异常处理和结果集的映射,从而减轻了开发者的工作负担。 以下是一些关键知识点: 1. **依赖注入**:Spring的核心特性之一是依赖注入(DI),它允许我们通过配置文件...

    java设计模式大全

    行为型模式如策略模式、模板方法模式、观察者模式、迭代器模式、访问者模式、命令模式、责任链模式、备忘录模式和状态模式,它们关注对象之间的交互和职责分配。 例如,单例模式确保一个类只有一个实例,并提供全局...

    java(jdbc)学习

    封装JDBC操作可以简化数据库访问代码,常见的封装形式包括将查询结果封装为Map、Bean或List,以及应用策略模式和模板模式来提高代码的可扩展性和可维护性。 #### 十五、高级JDBC主题 深入探讨JDBC的高级主题,包括...

    Spring整合JDBC实现转账业务-动态代理模式

    通过Spring的JdbcTemplate或NamedParameterJdbcTemplate,我们可以简化SQL操作,避免大量的手动资源关闭和异常处理,提高代码的可读性和可维护性。这些模板类帮助我们封装了数据库连接的创建、关闭以及异常处理,...

    hualinux spring 3.16:Spring对JDBC的支持.pdf

    简化JDBC模板查询通常涉及到使用RowMapper和ResultSetExtractor接口。这些接口可以让开发者以更加灵活的方式从数据库中提取和转换数据。尽管这种做法可以提供更多控制,但是文档中提到它并不是推荐的做法,因为它...

    化简jdbc 编写

    4. **使用JdbcTemplate**:Spring框架提供的JdbcTemplate是简化JDBC操作的一个好工具,它自动处理连接、关闭资源,支持批处理,提供事务管理等功能。 5. **使用MyBatis**:MyBatis是一个更高级的持久层框架,允许...

    jdbc学习文档

    - 示例代码展示了如何使用模板模式简化JDBC操作。 #### 结论 通过本文档的学习,不仅可以深入了解JDBC的基本概念和技术细节,还能够掌握一些高级用法和最佳实践,例如数据库连接池的使用、DAO设计模式的应用等。这...

    JdbcTemplate,自己封装的jdbc小框架

    综上所述,自封装的JdbcTemplate是一个简化JDBC操作的实用工具,它通过设计模式和面向对象编程,将复杂的数据库交互转化为简单的API调用,降低了开发难度,提高了代码质量。对于初学者而言,这是一个很好的学习和...

    JDBCDemo:自定义 jdbc 模板

    本项目"JDBCDemo:自定义jdbc模板"旨在提供一个自定义的JDBC模板,以简化数据库操作,并引入了数据库连接池和连接代理的概念,以提高应用程序的性能和可维护性。 首先,让我们深入了解一下JDBC模板。JDBC模板是...

    java jdbcTemplate 资源及实例代码

    Java JDBC (Java Database Connectivity) 是Java编程语言中用于与数据库交互的一组接口和类,它提供了标准的方法来连接、查询和操作数据库。Spring JDBC是Spring框架的一个模块,它简化了JDBC的操作,提供了更高级别...

    Spring源代码解析3:SpringJDBC[归纳].pdf

    总的来说,`JdbcTemplate`通过模板方法模式和回调接口,极大地简化了数据库操作,减少了代码量,提高了可维护性。它不仅提供了基本的SQL执行,还包括了异常转换、连接管理等复杂功能,使得开发人员可以更专注于业务...

    shejimoshi.rar_设计模式

    策略模式让算法的变化独立于使用它的客户。 14. 命令模式(Command Pattern):命令模式将请求封装为一个对象,以便使用不同的请求、队列请求、或者支持可撤销的操作。在Java中,命令模式常用于实现 undo/redo 功能...

    二十三种设计模式【PDF版】

    设计模式之 Template(模板方法) 实际上向你介绍了为什么要使用 Java 抽象类,该模式原理简单,使用很普遍. 设计模式之 Strategy(策略) 不同算法各自封装,用户端可随意挑选需要的算法. 设计模式之 Chain of ...

    Java 企业设计模式(PDG格式)

    3. **策略模式**:策略模式允许在运行时选择算法或策略,这在处理不同业务规则或优化算法时非常有用。比如在价格计算策略中,可以根据用户类型或购买量动态选择不同的折扣策略。 4. **观察者模式**:在事件驱动的...

    Java EE设计模式:Spring企业级开发最佳实践_JavaEE_企业应用开发_

    7. **策略模式(Strategy)**:Spring的事务管理策略,如编程式事务管理和声明式事务管理,就是策略模式的体现,用户可以选择不同的策略来处理事务。 8. **状态模式(State)**:Spring MVC的ModelAndView或者...

    Java_JDBC学习教程 由浅入深.doc

    - 使用设计模式(如策略模式和模板模式)来增强代码的灵活性和可扩展性。 #### 十六、后续发展 - 文档提到将来还会继续探讨更高级的主题,如 DbUtils 源码分析、Spring 对 JDBC 的强大封装等。 - 这些内容将进一步...

    Spring mvc、 Spring、 Spring jdbc 整合实例源码

    Spring JDBC是Spring提供的数据访问层,它简化了JDBC操作,消除了手动管理连接、准备语句和结果集等繁琐任务。通过使用Spring JDBC,开发者可以专注于SQL语句的编写,而无需关注底层的数据库交互细节。它支持事务...

    Spring 5 Design Patterns.pdf

    比如,在处理数据库操作时,Spring提供的JdbcTemplate就是一个典型的模板方法模式的应用案例,它可以大大简化JDBC编码工作,同时提高了代码的可读性和可维护性。 #### 六、其他设计模式 除了上述几种模式外,...

Global site tag (gtag.js) - Google Analytics