JdbcTemplate(NamedParameterJdbcTemplate)詳解


Spring對數據庫的操作在jdbc上面做了深層次的封裝,提供了JdbcTemplate模板。

傳統Jdbc API與Spring jdbcTemplate比較:

//JDBC API
Statement statement = conn.createStatement();
ResultSet resultSet = statement.executeQuery("select count(*) from COUNT student")
if(resultSet.next()){
    Integer count = resultSet.getInt("COUNT");
}

//JDBC Template
Integer count = jdbcTemplate.queryForObject("select count(*) from student",Integer.class);

一、JdbcTemplate主要幾類方法

  • execute方法:可以用於執行任何SQL語句,一般用於執行DDL語句;
  • update方法及batchUpdate方法:update方法用於執行新增、修改、刪除等語句;batchUpdate方法用於執行批處理相關語句;
  • query方法及queryForXXX方法:用於執行查詢相關語句;
  • call方法:用於執行存儲過程、函數相關語句(了解即可)。

二、JdbcTemplate常用API

//update操作單個增刪改
int update(String sql,Object[] args)
int update(String sql,Objcet... args)

//batchUpdate批量增刪改
int[] batchUpdate(String[] sql)
int[] batchUpdate(String sql,List<Object[]>)

//單個簡單查詢
T queryForObjcet(String sql,Class<T> type)
T queryForObjcet(String sql,Object[] args,Class<T> type)
T queryForObjcet(String sql,Class<T> type,Object... arg)

//獲取多個
List<T> queryForList(String sql,Class<T> type)
List<T> queryForList(String sql,Object[] args,Class<T> type)
List<T> queryForList(String sql,Class<T> type,Object... arg)

 查詢復雜對象(封裝為Map):

//獲取單個
Map queryForMap(String sql)
Map queryForMap(String sql,Objcet[] args)
Map queryForMap(String sql,Object... arg)

//獲取多個
List<Map<String,Object>> queryForList(String sql)
List<Map<String,Object>> queryForList(String sql,Obgject[] args)
List<Map<String,Object>> queryForList(String sql,Obgject... arg)

查詢復雜對象(封裝為實體對象):

Spring JdbcTemplate是通過實現org.springframework.jdbc.core.RowMapper這個接口來完成對entity對象映射。

//獲取單個
T queryForObject(String sql,RowMapper<T> mapper)
T queryForObject(String sql,object[] args,RowMapper<T> mapper)
T queryForObject(String sql,RowMapper<T> mapper,Object... arg)

//獲取多個
List<T> query(String sql,RowMapper<T> mapper)
List<T> query(String sql,Object[] args,RowMapper<T> mapper)
List<T> query(String sql,RowMapper<T> mapper,Object... arg)

pring JDBC中目前有兩個主要的RowMapper實現,使用它們應該能解決大部分的場景了:SingleColumnRowMapper和BeanPropertyRowMapper。
SingleColumnRowMapper:返回單列數據
BeanPropertyRowMapper:當查詢數據庫返回的是多列數據,且需要將這些多列數據映射到某個具體的實體類上。

//示例:
String sql = "select name from test_student where id = ?";
jdbcTemplate.queryForObject(sql, new Object[]{id}, new SingleColumnRowMapper<>(String.class));

String sql = "select name, gender from test_student where name = ?";
jdbcTemplate.queryForObject(sql, new Object[]{name},new BeanPropertyRowMapper<>(Student.class));

定義自己的RowMapper

如果你SQL查詢出來的數據列名就是和實體類的屬性名不一樣,或者想按照自己的規則來裝配實體類,那么就可以定義並使用自己的Row Mapper。

//自定義
public class StudentRowMapper implements RowMapper<Student> {

    @Override
    public Student mapRow(ResultSet rs, int i) throws SQLException {
        Student student = new Student();
        student.setName(rs.getString("name"));
        student.setGender(rs.getString("gender"));
        student.setEmail(rs.getString("email"));
        return student;
    }
}

//使用
String sql = "select name, gender, email from test_student where name = ?";

三、JdbcTemplate支持的回調類

1. 預編譯語句及存儲過程創建回調:用於根據JdbcTemplate提供的連接創建相應的語句

PreparedStatementCreator:通過回調獲取JdbcTemplate提供的Connection,由用戶使用該Conncetion創建相關的PreparedStatement;
CallableStatementCreator:通過回調獲取JdbcTemplate提供的Connection,由用戶使用該Conncetion創建相關的CallableStatement;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCallback;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class) // 關聯Spring與Junit
@ContextConfiguration(locations = { "classpath:applicationContext.xml" }) // 加載配置spring配置文件
public class AppTest {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Test
    public void testPpreparedStatement1() {
        int count = jdbcTemplate.execute(new PreparedStatementCreator() {
            public java.sql.PreparedStatement createPreparedStatement(Connection conn) throws SQLException {
                return conn.prepareStatement("select count(*) from user");
            }
        }, new PreparedStatementCallback<Integer>() {
            public Integer doInPreparedStatement(java.sql.PreparedStatement pstmt)
                    throws SQLException, DataAccessException {
                pstmt.execute();
                ResultSet rs = pstmt.getResultSet();
                rs.next();
                return rs.getInt(1);
            }
        });
        System.out.println(count);
    }
}

首先使用PreparedStatementCreator創建一個預編譯語句,其次由JdbcTemplate通過PreparedStatementCallback回調傳回,由用戶決定如何執行該PreparedStatement。此處我們使用的是execute方法。以下是我們mysql中常用的插入時返回自增的主鍵:

public long queryBuildingInfo(Map<String, Object> map) {
    String sql = "INSERT INTO TS_DEVICE_CHANGE(DEVICE_ID,CHANGE_DEVICE_ID)VALUES(?,?) ";
    KeyHolder keyHolder = new GeneratedKeyHolder();
    jdbcTemplate.update(new PreparedStatementCreator() {
        @Override
        public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
            // 設置返回的主鍵字段名
            PreparedStatement ps = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
            ps.setString(1, "1");
            ps.setString(2, "2");
            return ps;
        }
    }, keyHolder);
    // 獲取到插入數據生成的ID
    return keyHolder.getKey().longValue();
}

2. 預編譯語句設值回調:用於給預編譯語句相應參數設值

PreparedStatementSetter:通過回調獲取JdbcTemplate提供的PreparedStatement,由用戶來對相應的預編譯語句相應參數設值;
BatchPreparedStatementSetter:;類似於PreparedStatementSetter,但用於批處理,需要指定批處理大小;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class) // 關聯Spring與Junit
@ContextConfiguration(locations = { "classpath:applicationContext.xml" }) // 加載配置spring配置文件
public class AppTest {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Test
    public void testPreparedStatement2() {
        String insertSql = "insert into user(user_name) values (?)";
        int count = jdbcTemplate.update(insertSql, new PreparedStatementSetter() {
            public void setValues(PreparedStatement pstmt) throws SQLException {
                pstmt.setObject(1, "mmNN");
            }
        });
        Assert.assertEquals(1, count);
        String deleteSql = "delete from user where user_name=?";
        count = jdbcTemplate.update(deleteSql, new Object[] { "mmNN" });
        Assert.assertEquals(1, count);
    }
}

通過JdbcTemplate的int update(String sql, PreparedStatementSetter pss)執行預編譯sql,其中sql參數為“insert into user(user_name) values (?) ”,該sql有一個占位符需要在執行前設值,PreparedStatementSetter實現就是為了設值,使用setValues(PreparedStatement pstmt)回調方法設值相應的占位符位置的值。JdbcTemplate也提供一種更簡單的方式“update(String sql, Object... args)”來實現設值,所以只要當使用該種方式不滿足需求時才應使用PreparedStatementSetter。

3. 自定義功能回調:提供給用戶一個擴展點,用戶可以在指定類型的擴展點執行任何數量需要的操作

ConnectionCallback:通過回調獲取JdbcTemplate提供的Connection,用戶可在該Connection執行任何數量的操作;
StatementCallback:通過回調獲取JdbcTemplate提供的Statement,用戶可以在該Statement執行任何數量的操作;
PreparedStatementCallback:通過回調獲取JdbcTemplate提供的PreparedStatement,用戶可以在該PreparedStatement執行任何數量的操作;
CallableStatementCallback:通過回調獲取JdbcTemplate提供的CallableStatement,用戶可以在該CallableStatement執行任何數量的操作;

4. 結果集處理回調:通過回調處理ResultSet或將ResultSet轉換為需要的形式

RowMapper:用於將結果集每行數據轉換為需要的類型,用戶需實現方法mapRow(ResultSet rs, int rowNum)來完成將每行數據轉換為相應的類型。
RowCallbackHandler:用於處理ResultSet的每一行結果,用戶需實現方法processRow(ResultSet rs)來完成處理,在該回調方法中無需執行rs.next(),該操作由JdbcTemplate來執行,用戶只需按行獲取數據然后處理即可。
ResultSetExtractor:用於結果集數據提取,用戶需實現方法extractData(ResultSet rs)來處理結果集,用戶必須處理整個結果集;

@Test
public void testResultSet1() {
    jdbcTemplate.update("insert into user(user_name) values('name7')");
    String listSql = "select * from user where user_name=?";
    List result = jdbcTemplate.query(listSql,new Object[]{"name7"}, new RowMapper<Map>() {
        public Map mapRow(ResultSet rs, int rowNum) throws SQLException {
            Map row = new HashMap();
            row.put(rs.getInt("user_id"), rs.getString("user_name"));
            return row;
        }
    });
    Assert.assertEquals(1, result.size());//查詢結果數量為1才向下執行
    jdbcTemplate.update("delete from user where user_name='name7'");
}

RowMapper接口提供mapRow(ResultSet rs, int rowNum)方法將結果集的每一行轉換為一個Map,當然可以轉換為其他類。

@Test
public void testResultSet2() {
    jdbcTemplate.update("insert into user(user_name) values('name5')");
    String listSql = "select * from user";
    final List result = new ArrayList();
    jdbcTemplate.query(listSql, new RowCallbackHandler() {
        public void processRow(ResultSet rs) throws SQLException {
            Map row = new HashMap();
            row.put(rs.getInt("user_id"), rs.getString("user_name"));
            result.add(row);
        }
    });
    Assert.assertEquals(1, result.size());
    jdbcTemplate.update("delete from user where user_name='name5'");
}

RowCallbackHandler接口也提供方法processRow(ResultSet rs),能將結果集的行轉換為需要的形式。

@Test
public void testResultSet3() {
    jdbcTemplate.update("insert into test(name) values('name5')");
    String listSql = "select * from test";
    List result = jdbcTemplate.query(listSql, new ResultSetExtractor<List>() {
        public List extractData(ResultSet rs) throws SQLException, DataAccessException {
            List result = new ArrayList();
            while (rs.next()) {
                Map row = new HashMap();
                row.put(rs.getInt("id"), rs.getString("name"));
                result.add(row);
            }
            return result;
        }
    });
    Assert.assertEquals(0, result.size());
    jdbcTemplate.update("delete from test where name='name5'");
}

ResultSetExtractor使用回調方法extractData(ResultSet rs)提供給用戶整個結果集,讓用戶決定如何處理該結果集。

當然JdbcTemplate提供更簡單的queryForXXX方法,來簡化開發:

//1.查詢一行數據並返回int型結果  
jdbcTemplate.queryForInt("select count(*) from test");  
//2. 查詢一行數據並將該行數據轉換為Map返回  
jdbcTemplate.queryForMap("select * from test where name='name5'");  
//3.查詢一行任何類型的數據,最后一個參數指定返回結果類型  
jdbcTemplate.queryForObject("select count(*) from test", Integer.class);  
//4.查詢一批數據,默認將每行數據轉換為Map       
jdbcTemplate.queryForList("select * from test");  
//5.只查詢一列數據列表,列類型是String類型,列名字是name  
jdbcTemplate.queryForList("  
select name from test where name=?", new Object[]{"name5"}, String.class);  
//6.查詢一批數據,返回為SqlRowSet,類似於ResultSet,但不再綁定到連接上  
SqlRowSet rs = jdbcTemplate.queryForRowSet("select * from test");  

四、Spring提供的JDBC模板

  • JdbcTemplate:Spring里最基本的JDBC模板,利用JDBC和簡單的索引參數查詢提供對數據庫的簡單訪問。
  • NamedParameterJdbcTemplate:能夠在執行查詢時把值綁定到SQL里的命名參數,而不是使用索引參數。
  • SimpleJdbcTemplate:利用Java 5的特性,比如自動裝箱、通用(generic)和可變參數列表來簡化JDBC模板的使用。

五、分頁查詢

使用JdbcTemplate都是使用SQL進行查詢的,如果需要分頁,則需要對SQL進行修改。下面提供了幾種常用數據庫的分頁SQL拼接的工具類及分頁對象:

/**
 * 分頁對象
 */
public final class PageBean<T> implements Serializable {
    //當前頁碼
    private Integer currentPage = 1;
    //每頁顯示的記錄數
    private Integer pageSize = 10;
    //總記錄數
    private Integer totalSize = 0;
    //本頁的數據列表
    private List<T> recordList;
    //總頁數
    private Integer totalPageCount = 0;
    //開始行號
    private Integer startIndex = 0;
    //頁碼列表的開始索引
    private Integer beginPageIndex;
    //頁碼列表的結束索引
    private Integer endPageIndex;

    public PageBean(int currentPage, int pageSize, int totalSize, List<T> recordList) {
        this.currentPage = currentPage;
        this.pageSize = pageSize;
        this.totalSize = totalSize;
        this.recordList = recordList;
        //計算beginPageIndex 和 endPageIndex
        //>>總頁數不多於10頁,則全部顯示
        if (totalPageCount <= 10) {
            beginPageIndex = 1;
            endPageIndex = totalPageCount;
        }
        //總頁數多於10頁,則顯示當前頁附近的共10個頁碼
        else {
            //當前頁附近的共10個頁碼(前4個+當前頁+后5個)
            beginPageIndex = currentPage - 4;
            endPageIndex = currentPage + 5;

            //當前面的頁碼不足4個時,則顯示前10個頁碼
            if (beginPageIndex < 1) {
                beginPageIndex = 1;
                endPageIndex = 10;
            }
            //當后面的頁碼不足5個時,則顯示后10個頁碼
            if (endPageIndex > totalPageCount) {
                endPageIndex = totalPageCount;
                beginPageIndex = totalPageCount - 10 + 1;
            }
        }
    }

    public Integer getCurrentPage() {
        return currentPage;
    }

    public void setCurrentPage(Integer currentPage) {
        this.currentPage = currentPage;
    }

    public Integer getPageSize() {
        return pageSize;
    }

    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }

    public Integer getTotalSize() {
        return totalSize;
    }

    public void setTotalSize(Integer totalSize) {
        this.totalSize = totalSize;
    }

    public List<T> getRecordList() {
        return recordList;
    }

    public void setRecordList(List<T> recordList) {
        this.recordList = recordList;
    }

    public Integer getTotalPageCount() {
        //總頁數
        totalPageCount = totalSize / pageSize;
        if (totalSize == 0 || totalSize % pageSize != 0) {
            totalPageCount++;
        }
        return totalPageCount;
    }

    public void setTotalPageCount(Integer totalPageCount) {
        this.totalPageCount = totalPageCount;
    }

    public void setStartIndex(Integer startIndex) {
        this.startIndex = startIndex;
    }

    public Integer getStartIndex() {
        //開始行號=(當前頁數-1)x每頁顯示的條數
        return (currentPage - 1) * pageSize;
    }

    public Integer getBeginPageIndex() {
        return beginPageIndex;
    }

    public void setBeginPageIndex(Integer beginPageIndex) {
        this.beginPageIndex = beginPageIndex;
    }

    public Integer getEndPageIndex() {
        return endPageIndex;
    }

    public void setEndPageIndex(Integer endPageIndex) {
        this.endPageIndex = endPageIndex;
    }
}
View Code
/**
 * SQL的分頁工具類
 */
public final class SqlPageUtil {

    private SqlPageUtil(){}

    /**
     * @MethodName populateMySQLPageQuerySQL
     * @Description mysql的分頁拼接
     * @param sqlStr
     * @param startIndex 起始索引
     * @param currentSize 每天顯示的記錄數
     * @Return java.lang.String 拼接后的分頁語句
     */
    public static String populateMySQLPageQuerySQL(String sqlStr, int startIndex, int currentSize){
        StringBuffer pageBuffer = new StringBuffer();
        if (startIndex == 0 && currentSize <= 0) {
            pageBuffer.append(sqlStr);
        } else {
            pageBuffer.append(sqlStr);
            pageBuffer.append(" limit " + startIndex + "," + currentSize);
        }
        return pageBuffer.toString();
    }

    /**
     * @MethodName populateOraclePageQuerySQL
     * @Description oracle的分頁拼接
     * @param sqlStr
     * @param startIndex 起始索引
     * @param currentSize 每天顯示的記錄數
     * @Return java.lang.String 拼接后的分頁語句
     */
    public static String populateOraclePageQuerySQL(String sqlStr, int startIndex, int currentSize) {
        StringBuffer pageBuffer = new StringBuffer();
        if (startIndex == 0 && currentSize <= 0) {
            pageBuffer.append(sqlStr);
        } else {
            pageBuffer.append(" SELECT ROW_.*, ROWNUM ROWNUM_ FROM ( ");
            pageBuffer.append(sqlStr);
            pageBuffer.append(" ) ROW_ ");
            if (startIndex == 0) {
                pageBuffer.append(" WHERE ROWNUM <= ");
                pageBuffer.append(startIndex + currentSize);
            } else if (currentSize <= 0) {
                pageBuffer.append(" WHERE ROWNUM > ");
                pageBuffer.append(startIndex);
            } else {
                pageBuffer.insert(0, "SELECT * FROM ( ");
                pageBuffer.append(" WHERE ROWNUM <= ");
                pageBuffer.append(startIndex + currentSize);
                pageBuffer.append(") WHERE ROWNUM_ > ");
                pageBuffer.append(startIndex);
            }
        }
        return pageBuffer.toString();
    }

    /**
     * @MethodName populateDB2PageQuerySQL
     * @Description DB2的分頁拼接
     * @param sqlStr
     * @param startIndex 起始索引
     * @param currentSize 每天顯示的記錄數
     * @Return java.lang.String 拼接后的分頁語句
     */
    public static String populateDB2PageQuerySQL(String sqlStr, int startIndex, int currentSize) {
        if (startIndex == 0 ) {
            return currentSize>1?sqlStr + " fetch first " + currentSize + " rows only":
                    " fetch first 1 row only";
        }
        return "select * from ( select inner2_.*, rownumber() over(order by order of inner2_) as rownumber_ from ( "
                + sqlStr + " fetch first " + String.valueOf(startIndex+currentSize)
                + " rows only ) as inner2_ ) as inner1_ where rownumber_ > "
                + startIndex + " order by rownumber_";
    }
}
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM