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