你是否写过这样的代码,当编写的时候总会感觉以前曾经这么写过。我的朋友,这不是似曾相识,这是样板式代码(boilerplate code)。 通常为了实现通用和简单的任务,你不得不一遍遍地重复编写这样的代码。
遗憾的是,它们中的很多都是因为使用Java API而导致的样板式代码。样板式代码的一个常见范例是使用JDBC访问数据库查询。举个例子,如果你曾经用过JDBC,那么你或许会写出类似下面的代码。
public Employee getEmployeebyId(long id){
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try{
conn = dataSource.getConnection();
stmt = conn.prepareStatement("select * from emplyoee where id=?");
stmt.setLong(1,id);
rs = stmt.executeQuery();
Employee emplyoee = null;
if(rs.next()){
emplyoee = new Employee();
emplyoee.setId(rs.getLong("id"));
emplyoee.setFirstName(rs.getString("firstName"));
emplyoee.setLastName(rs.getString("lastName"));
emplyoee.setSalary(rs.getBigDecimal("salary"));
}
return emplyoee;
}catch(SQLException e){
;
}finally{
if(rs != null){
try{
rs.close();
}catch(SQLException e){}
}
if(stmt != null){
try{
stmt.close();
}catch(SQLException e){}
}
if(conn != null){
try{
conn.close();
}catch(SQLException e){}
}
}
}
正如你所看到的,这段JDBC代码查询数据库获得员工姓名和薪水。从以上代码可以看到,少量查询员工的代码淹没在 一堆JDBC的样式代码中。首先,你要创建一个数据库连接,在创建一个语句对象,最后才能查询。中间还得捕捉SQLException, 这是一个检查型异常,即使它抛出后,你也做不了太多事情。
最后,该说的也说了,该做的也做了,你还不得不清理战场,关闭数据库连接、语句和结果集。同样地,你还是需要捕捉异常。
相同的,只有少量代码与查询员工逻辑有关系,其他代码都是JDBC样板代码。
JDBC不是产生样板式代码的唯一场景,在许多编程场景中往往都会导致类似的样板式代码, JMS,JNDI和使用REST服务通常也涉及大量重复代码。
Spring旨在通过模板封装来消除样板式代码。Spring的JdbcTemplate使得执行数据库操作时,避免传统的JDBC样板式代码成为了可能
public Employee getEmployeebyId(long id){
return jdbcTemplate.queryForObject("select * from emplyoee where id=?",
new RowMapper<Employee>(){
public Employee mapRow(ResultSet rs, int rowNum) throws SQLException {
Employee emplyoee = new Employee();
emplyoee.setId(rs.getLong("id"));
emplyoee.setFirstName(rs.getString("firstName"));
emplyoee.setLastName(rs.getString("lastName"));
emplyoee.setSalary(rs.getBigDecimal("salary"));
return emplyoee;
}
},id);
}
正如你所看到,新版本的 getEmployeebyId()简单多了。而且仅仅关注于从数据库中查询员工,模板的QueryForObject() 方法需要一个SQL查询语句和一个RowMapper对象,零个或多个查询参数。getEmployeebyId()方法再也看不到以前的JDBC样板式代码了,他们全部封装到了模板中。
下面看一下JdbcTemplate是如何封装的
在执行JdbcTemplate之前,先定义一个RowMapper
RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<Employee>(Employee.class);
执行查询的语句变得更简单了,
public Employee getEmployeebyId(long id){
String sql="select * from emplyoee where id=?";
return jdbcTemplate.queryForObject(sql,rowMapper,new Object[]{id});
}
下面看一下queryForObject是如何实现的
public <T> T queryForObject(String sql, RowMapper<T> rowMapper, Object... args) throws DataAccessException {
List<T> results = query(sql, args, new RowMapperResultSetExtractor<T>(rowMapper, 1));
return DataAccessUtils.requiredSingleResult(results);
}
跳过中间调用的各种query,直接最终query的实现
public <T> T query(
PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor<T> rse)
throws DataAccessException {
Assert.notNull(rse, "ResultSetExtractor must not be null");
logger.debug("Executing prepared SQL query");
return execute(psc, new PreparedStatementCallback<T>() {
public T doInPreparedStatement(PreparedStatement ps) throws SQLException {
ResultSet rs = null;
try {
if (pss != null) {
pss.setValues(ps);
}
rs = ps.executeQuery();
ResultSet rsToUse = rs;
if (nativeJdbcExtractor != null) {
rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
}
return rse.extractData(rsToUse);
}
finally {
JdbcUtils.closeResultSet(rs);
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
}
});
}
jdbc的样板代码就在这里实现了。
public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action)
throws DataAccessException {
Assert.notNull(psc, "PreparedStatementCreator must not be null");
Assert.notNull(action, "Callback object must not be null");
if (logger.isDebugEnabled()) {
String sql = getSql(psc);
logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : ""));
}
Connection con = DataSourceUtils.getConnection(getDataSource());
PreparedStatement ps = null;
try {
Connection conToUse = con;
if (this.nativeJdbcExtractor != null &&
this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()) {
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
ps = psc.createPreparedStatement(conToUse);
applyStatementSettings(ps);
PreparedStatement psToUse = ps;
if (this.nativeJdbcExtractor != null) {
psToUse = this.nativeJdbcExtractor.getNativePreparedStatement(ps);
}
T result = action.doInPreparedStatement(psToUse);
handleWarnings(ps);
return result;
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
if (psc instanceof ParameterDisposer) {
((ParameterDisposer) psc).cleanupParameters();
}
String sql = getSql(psc);
psc = null;
JdbcUtils.closeStatement(ps);
ps = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate("PreparedStatementCallback", sql, ex);
}
finally {
if (psc instanceof ParameterDisposer) {
((ParameterDisposer) psc).cleanupParameters();
}
JdbcUtils.closeStatement(ps);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}