模板方法模式在JDBCTemplate中的应用

上一篇中系统总结了模板模式的原理和使用,提到了模板方法和回调接口。回调接口和模板方法类之间的关系可以看作服务与被服务的关系,模板方法类想要回调接口做事,就要提供相应的资源,接口用提供的资源做事,完事后,模板方法类来处理公开的资源,回调接口不需要在关心这些。今天再记录一下JDBCTemplate中模板方法模式的应用。

这里先给出JDBC的初级代码:

复制代码
public class JDBCTemplate<T> {

   /**
    * inset updata delect操作时调用此方法
    */

   public void update(String sql,Object... params){
      Connection con = null;
      PreparedStatement ps = null;
      int count = 0;
      try {
         con = JDBCUtil.getConnection();
         ps = con.prepareStatement(sql);
         
         if (params != null) {
            for (int i = 0; i < params.length; i++) {
               ps.setObject(i+1, params[i]);
            }
         }
         
         count = ps.executeUpdate();
      } catch (Exception e) {
         e.printStackTrace();
         
      } finally {
         //connection不能关闭,因为还要在Service中使用
//       JDBCUtil.close(con, ps);
         JDBCUtil.close(null, ps);
      }
   }
复制代码
    /**
    * 执行查询时用的方法
    * @param sql
    * @param rm
    * @param params
    * @return
    */
   public  List<T> query(String sql,RowMapper rm,Object... params){
      Connection con = null;
      PreparedStatement ps = null;
      ResultSet rs = null;
      List result = new ArrayList<>();
      
      try {
         con = JDBCUtil.getConnection();
         ps = con.prepareStatement(sql);
         
         if (params != null) {
            for (int i = 0; i < params.length; i++) {
               ps.setObject(i+1, params[i]);
            }
         }
         
         rs = ps.executeQuery();
         
         while(rs.next())
         {
            Object obj = rm.mapRow(rs);
            result.add(obj);
         }
         
      } catch (Exception e) {
         e.printStackTrace();
         
      } finally {
         JDBCUtil.close(null, ps, rs);
      }

      return result;
   }
复制代码
    public Object query4Object(String sql,RowMapper rm,Object... params){
      List data = query(sql, rm, params);
      return data.isEmpty() ? null :data.get(0);
   }
   
}

上面的代码把对数据库的访问操作封装成了两个方法,对于update(String sql,Object... params)方法,在实际的开发中,新增数据有时需要记录新增数据的主键,在JDBC中提供了这样一个方法:

复制代码
con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);

因此,con.prepareStatement(sql)的参数如果写死是不能满足需求的。基于模板模式的思路,定义专门创建PreparedStatement对象的接口:

复制代码
public interface PreparedStatementCreater {

    /**
     * 创建PreparedStatement对象
     * @param con
     * @return
     */
    public PreparedStatement createPreparedStatement(Connection con,KeyHolder keyHolder) throws Exception;
}

然后prepareStatement的创建和sql传参和赋值全部交给调用者实现,我只负责运行

复制代码
    /**
    * 当保存需要返回主键的时候调用此方法
    * @param psc 用于创建PreparedStatement对象
    * @param keyHolder 用于存放主键
    */
   public void update(PreparedStatementCreater psc,KeyHolder keyHolder){
      Connection con = null;
      PreparedStatement ps = null;
      ResultSet rs = null;
      int count = 0;
      try {
         con = JDBCUtil.getConnection();
//       ps = con.prepareStatement(sql);
//
//       if (params != null) {
//          for (int i = 0; i < params.length; i++) {
//             ps.setObject(i+1, params[i]);
//          }
//       }
         //prepareStatement交给调用者创建,我只负责运行,其他的不再关心
         ps = psc.createPreparedStatement(con, keyHolder);

         ps.executeUpdate();

         rs = ps.getGeneratedKeys();

         List keys = new ArrayList();

         //保存返回主键的代码
         ResultSetMetaData rsmd = rs.getMetaData();

         int columnCount = rsmd.getColumnCount();

         if (rs.next()){
            for (int i=0;i<columnCount;i++){
               keys.add(rs.getObject(i+1));
            }
         }
         //返回值是void,用keyHolder返回主键,调用者传空的keyHolder进来
         keyHolder.setList(keys);
      } catch (Exception e) {
         e.printStackTrace();

      } finally {
         JDBCUtil.close(null, ps , rs);
      }
   }
复制代码
public class KeyHolder {
    private List list;

    public KeyHolder() {
        list = new ArrayList();
    }

    public KeyHolder(List list) {
        this.list = list;
    }

    public void setList(List list) {
        this.list = list;
    }

    /**
     联合主键
     */
    public List getKeyList() {
        return list;
    }

    /**
     单个主键
     */
    public Object getKey(){
        if(list.isEmpty()){
            return null;
        }
        return list.get(0);
    }
}

此时当调用者调用update方法时,迫使实现PreparedStatementCreater 接口中的createPreparedStatement方法。

对于public List<T> query(String sql,RowMapper rm,Object... params)方法,如果我要查询一个部门对象,并把部门对应的员工映射进来时,根据查询结果逐条映射的逻辑是不对的

复制代码
while(rs.next())
{
   Object obj = rm.mapRow(rs);
   result.add(obj);
}
复制代码
select * from dept d inner join emp e on e.dept_id=d.id 

此时多条数据映射一个部门对象,根据查询结果集逐条映射的逻辑是不对的,因此查询方法也要进行改造,同样是通过的传入接口迫使调用者实现剩余逻辑的方法来做

复制代码
    /**
    * 执行查询时用的方法
    * 当进行一方查询多方数据时,使用此方法  一个部门对应多个员工 dept List<Emp>
    * @param sql
    * @param params
    * @return
    */
   public Object query(String sql,ResultSetExtractor rse, Object... params){
      Connection con = null;
      PreparedStatement ps = null;
      ResultSet rs = null;
      Object obj = new Object();

      try {
         con = JDBCUtil.getConnection();
         ps = con.prepareStatement(sql);

         if (params != null) {
            for (int i = 0; i < params.length; i++) {
               ps.setObject(i+1, params[i]);
            }
         }

         rs = ps.executeQuery();

         obj = rse.extractData(rs);

      } catch (Exception e) {
         e.printStackTrace();

      } finally {
         JDBCUtil.close(null, ps, rs);
      }

      return obj;
   }

以上就是JDBC封装中对模板方法的使用。

在spring中,正是根据这种思想实现各种JDBCTemplate中的各种模板方法,通过相应回调接口所公开的API自由度的大小,简单分为4组:

**面向Connection的模板方法。**通过ConnectionCallback接口公开java.sql.Connection进行数据访问,不需要关心连接的释放和获取,但自由度很大,一般避免直接使用面向Connection接口的模板方法进行数据访问。

**面向Statement的模板方法。**主要处理基于静态sql的数据访问请求。通过StatementCallback接口对外公开java.sql.Statement,缩小了回调接口内的权限范围,相比上一种提高了安全性。

**面向PreparedStatement的模板方法。**通过PreparedStatementCreater接口公开Connection允许PreparedStatement创建(创建需要传入包含参数的SQL),PreparedStatement创建之后,公开给PreparedStatementCallback回调接口。

**面向CallableStatement的模板方法。**通过CallableStatementCreater接口公开Connection以便创建用于调用存储过程的CallableStatement,再通过CallableStatementCallback公开创建的CallableStatement实现基于存储过程的数据访问。

相关推荐
Jack_abu5 天前
设计模式学习之——模板方法模式
设计模式·模板方法模式
孤华暗香6 天前
Python设计模式详解之13 —— 模板方法模式
python·设计模式·模板方法模式
澄澈i10 天前
设计模式学习[9]---模板方法模式
c++·学习·设计模式·模板方法模式
小白不太白95011 天前
设计模式之 模板方法模式
java·设计模式·模板方法模式
菜菜-plus13 天前
java 设计模式 模板方法模式
java·设计模式·模板方法模式
萨达大13 天前
23种设计模式-模板方法(Template Method)设计模式
java·c++·设计模式·软考·模板方法模式·软件设计师·行为型设计模式
CC大煊15 天前
【设计模式】模板方法模式 在java中的应用
java·设计模式·模板方法模式
南城花随雪。16 天前
Mybatis框架之模板方法模式 (Template Method Pattern)
java·mybatis·模板方法模式
G皮T21 天前
【设计模式】行为型模式(一):模板方法模式、观察者模式
java·观察者模式·设计模式·模板方法模式·template method·行为型模式·observer
zhouzhihao_0721 天前
程序代码设计模式之模板方法模式(1)
java·设计模式·模板方法模式