简介
访问数据库的方式有很多种,最简单原始的方式无非是通过java jdbc库去直接访问。这种方式实现的代码很冗长,而且要考虑的东西比较多,比如建立数据库连接、执行sql语句、获取结果并解析、关闭连接。这种传统方式带来的一个问题就是,我们也许仅仅为了执行一个简单的sql语句却需要一大堆冗长的代码,而且每执行一条语句都要重复这些代码。于是一种当然的想法就是如何将这些冗余的部分给提取出来使得我们的代码里只关注具体业务的方面。正是基于这个想法,spring提供了jdbc数据访问的模板。在本文中针对这两种数据访问的方式进行比较和总结。
数据库访问的传统方式
先来看看一个典型的传统jdbc数据库访问示例:
private static final String SQL_INSERT_SPITTER = "insert into spitter (username, password, fullname) values (?, ?, ?)"; private DataSource dataSource; public void addSpitter(Spitter spitter) { Connection conn = null; PreparedStatement stmt = null; try { //获取connection conn = dataSource.getConnection(); //创建执行语句 stmt = conn.prepareStatement(SQL_INSERT_SPITTER); //绑定执行参数 stmt.setString(1, spitter.getUsername()); stmt.setString(2, spitter.getPassword()); stmt.setString(3, spitter.getFullName()); //执行sql语句 stmt.execute(); } catch (SQLException e) { //处理异常情况 } finally { try { //关闭连接资源 if (stmt != null) { stmt.close(); } if (conn != null) { conn.close(); } } catch (SQLException e) { } } }
上面代码是一个执行插入数据到数据库表中的操作,其实其中最核心的部分仅仅是这么一个insert语句。而要实现真正执行这个语句的效果,这里却需要做一大堆的事情,比如建立连接、创建执行语句、执行sql语句、处理异常情况以及关闭连接资源等。在每个需要执行的语句里都要穿插这么多冗余代码的话,肯定不合理,需要一种方法将它们精简。
除了这些,在上述的代码里,其实还存在着一些其他的问题。比如说出现异常的地方我们该怎么处理呢?因为可能出现异常的情况比较多,是连接不上数据库了,还是sql语句执行错误呢?这些在传统的这种做法里没法区分。
改进
上面给出的那个示例并没有完整的代码。仅仅是这一部分我们就已经看到传统方式的不足了。 既然要改进的话,看到那些代码就已经有一个初步的思路了。上面的思路用一种伪码来描述的话如下:
Connection connection = createConnection(); Statement statement = createStatement(); statement = bindParameters(); execute(statement); closeconnection();
其中最核心的部分其实就是绑定参数和执行sql语句,其他部分对于其他的语句来说也是通用的。我们完全可以将它们作为一个公共父类的一部分提取出来。这种方式有点像下图的思路:
这样,在父类里我们定义好了createConnection, closeConnection以及handleException的大致框架。而在具体实现类比如SplitterDAO里实现具体的createStatement, bindParameters就可以了。如果我们对设计模式的一些套路比较熟悉的话已经知道这种思路无非就是template method。
正式因为有这么一个思路,spring里面已经提供了典型的实现给我们使用。最常见的就是JdbcTemplate。
spring JDBC template
如果使用spring JdbcTemplate实现上面示例的代码,一个简单对应的实现则如下:
public void insertSpitter(Spitter spitter) { jdbcTemplate.update(INSERT_SPITTER, spitter.getUsername(), spitter.getPassword(), spitter.getFullName(), spitter.getEmail(), spitter.isUpdateByEmail()); } private static final String INSERT_SPITTER = "insert into Spitter (username, password, fullname, email, updateByEmail) values (?, ?, ?, ?, ?)";
从上述的代码中可以看到,上述代码中我们只需要首先创建必须的sql语句,然后在代码里将必须的参数绑定,剩下的就交给JdbcTemplate来处理。
既然JdbcTemplate可以解决这么多的问题,我们接着对这部分再深入了解一下。在spring中最常用的两个JdbcTemplate分别是JdbcTemplate和NamedParameterJdbcTemplate。其中JdbcTemplate提供最基础的数据访问和基于参数索引方式的查询。而NamedParameterJdbcTemplate可以将参数名绑定到sql语句中。在典型代码示例里如下:
private static final String CREATE_SQL = "insert into contact (last_name, first_name, mi, email) values (:lastName, :firstName, :mi, :email)"; public void createContact(Contact contact) { SqlParameterSource params = new MapSqlParameterSource() .addValue("lastName", contact.getLastName()) .addValue("firstName", contact.getFirstName()) .addValue("mi", contact.getMiddleInitial()) .addValue("email", contact.getEmail()); KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTemplate.update(CREATE_SQL, params, keyHolder); contact.setId(keyHolder.getKey().longValue()); }
在上述的示例代码里定义的sql语句有点不一样,前面的对应参数位置是用?来代替,这样如果我们需要映射到对应参数实现的时候还需要去查表格。而这边的语句里直接映射了一个名字,并在后面用MapSqlParameterSource()来将名字和对应的实体成员关联起来。这种方式显得更加直观一些。该示例的详细实现可以参看后面的附件。
总结
使用传统jdbc的方式过于冗长而且容易出错,而spring jdbcTemplate提供的这种模板方法的方式其实就是基于template method的模式将那些恼人的细节隐藏了起来,使得使用者只需要关心具体业务逻辑。关于它们具体实现的细节会在后续的文章中深入讨论。
参考材料