spring之JdbcTemplate查詢資料的兩種方式
在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則採用流化處理資料。