在spring中对原生的jdbc操作进行封装成模板类JdbcTemplate类,之所以封装,是因为原生jdbc操作不但麻烦而且啰嗦,使业务代码和数据库操作代码混在一起,相当杂乱。而且如果你获得数据源连接之后如果忘了关闭,就会有数据连接泄露的风险,久而久之,系统崩溃。而使用JdbcTemplate就不一样了,spring对于数据的操作采用模板模式进行,分为模板和回调两个部分,对于连接数据库,释放资源这种不变的操作封装在模板中,而对于数据库的数据访问封装在回调接口中进行,spring的这一方法,不可谓不经典!
今天的主题说的是JdbcTemplate对于查询数据的操作,有两种接口都可以进行查询,分别是RowCallbackHandler()和RowMapper<T>,下面先来看看例子:
package com.smart.pojo; //实体类,博客论坛 public class Forum { private int forumId; private String forumName; private String forumDesc; public int getForumId() { return forumId; } public void setForumId(int forumId) { this.forumId = forumId; } public String getForumName() { return forumName; } public void setForumName(String forumName) { this.forumName = forumName; } public String getForumDesc() { return forumDesc; } public void setForumDesc(String forumDesc) { this.forumDesc = forumDesc; } }
数据访问接口如下:
@Repository public class ForumDao { private JdbcTemplate jdbcTemplate; @Autowired public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } //查询数据,使用RowCallbackHandler处理结果集 public Forum getForum(final int forumId){ String sql = "select forum_name,forum_desc from t_forum where forum_id=?"; final Forum forum = new Forum(); //将结果集数据行中的数据抽取到forum对象中 jdbcTemplate.query(sql, new Object[]{forumId}, new RowCallbackHandler() { public void processRow(ResultSet rs) throws SQLException { forum.setForumId(forumId); forum.setForumName(rs.getString("forum_name")); forum.setForumDesc(rs.getString("forum_desc")); } }); return forum; } //查询数据,批量查询,调用RowCallbackHandler()接口 public List<Forum> getForums(final int fromId , final int toId){ String sql = "select * from t_forum where forum_id between ? and ?"; final List<Forum> forumList = new ArrayList<Forum>(); jdbcTemplate.query(sql, new Object[]{fromId, toId}, new RowCallbackHandler() {//将结果集中的数据映射到List中 public void processRow(ResultSet rs) throws SQLException { Forum forum = new Forum(); forum.setForumId(rs.getInt("forum_id")); forum.setForumName(rs.getString("forum_name")); forum.setForumDesc(rs.getString("forum_desc")); forumList.add(forum); } }); return forumList; } ////查询数据,批量查询,调用RowMapper()接口 public List<Forum> getForumsByRowMapper(final int fromId , final int toId){ String sql = "select * from t_form where forum_id between ? and ?"; return jdbcTemplate.query(sql, new Object[]{fromId, toId}, new RowMapper<Forum>() { public Forum mapRow(ResultSet rs, int rowNum) throws SQLException { Forum forum = new Forum(); forum.setForumId(rs.getInt("forum_id")); forum.setForumName(rs.getString("forum_name")); forum.setForumDesc(rs.getString("forum_desc")); return forum; } }); } }
可以看出,无论是单个对象查询还是集合查询,都可以使用RowCallbackHandler回调接口,通过该接口可以定义如何从结果集中获取数据。而RowMapper<T>仅需定义结果集行和对象的映射关系即可。
那么问题来了,二者有何区别?
【我们知道,通过JDBC查询返回一个ResultSet结果集时,JDBC并不会一次性将匹配的数据都加载到JVM中,而是只返回一批次的数据(由JDBC驱动程序决定,如ORACLE的JDBC驱动默认返回10行),当通过ResultSet#next()游标滚动结果集超过数据范围时,JDBC再获取一批数据。这样以一种“批量化+串行化”的处理方式避免大结果集处理时JVM内存的过大开销。】
当处理大结果集时,如果使用Row Mapper,那么采用的方式是将结果集中的所有数据都放到一个List<T>对象中,这样将会占用大量的JVM内存,甚至可能引发OutOfMemoryException,这时,可以采用RowCallbackHandler接口,在processRow()接口方法内部一边获取数据一边完成处理,这样数据就不会在内存中堆积,可大大减少对JVM内存的占用。
****************************************************************************************************************************
举例:如果程序要求给所有系统用户发送一封邮件,而系统用户数量为100万。一种方案是采用RowMapper,返回一个List<User>集合,再通过遍历这个List<User>,逐个发送邮件;而另一种方案是采用RowCallbackHandler接口,在processRow()接口方法内部逐行获取User数据后,立即调用邮件服务发送邮件。虽然这两种方案都达到了相同的目的,但第一种方案会在程序运行过程中,在JVM中产生一个系统用户数大小为100万条的List<User>,从而导致极低的系统性能和巨大的内存开销,甚至引起系统崩溃。
结论:采用RowMapper的操作方式是先获取数据,再处理数据;而RowCallbackHandler得操作方式是一边获取数据一边处理,处理完就丢弃。因此,可以将RowMapper看作采用批量化数据处理策略,而RowCallbackHandler则采用流化处理数据。