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

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

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

public class JDBCTemplate {

   /**
    * 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 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 
  
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 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
    * @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实现基于存储过程的数据访问。

你可能感兴趣的:(模板方法模式)