上一篇中系统总结了模板模式的原理和使用,提到了模板方法和回调接口。回调接口和模板方法类之间的关系可以看作服务与被服务的关系,模板方法类想要回调接口做事,就要提供相应的资源,接口用提供的资源做事,完事后,模板方法类来处理公开的资源,回调接口不需要在关心这些。今天再记录一下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 Listquery(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实现基于存储过程的数据访问。