轉載:
https://blog.csdn.net/dyllove98/article/details/7772463
https://www.e-learn.cn/content/qita/833737
JdbcTemplate主要提供以下五類方法:
- execute方法:可以用於執行任何SQL語句,一般用於執行DDL語句;
- update方法及batchUpdate方法:update方法用於執行新增、修改、刪除等語句;batchUpdate方法用於執行批處理相關語句;
- query方法及queryForXXX方法:用於執行查詢相關語句;
- call方法:用於執行存儲過程、函數相關語句。
JdbcTemplate類支持的回調類:
- 預編譯語句及存儲過程創建回調:用於根據JdbcTemplate提供的連接創建相應的語句;
PreparedStatementCreator:通過回調獲取JdbcTemplate提供的Connection,由用戶使用該Conncetion創建相關的PreparedStatement;
CallableStatementCreator:通過回調獲取JdbcTemplate提供的Connection,由用戶使用該Conncetion創建相關的CallableStatement;
- 預編譯語句設值回調:用於給預編譯語句相應參數設值;
PreparedStatementSetter:通過回調獲取JdbcTemplate提供的PreparedStatement,由用戶來對相應的預編譯語句相應參數設值;
BatchPreparedStatementSetter:;類似於PreparedStatementSetter,但用於批處理,需要指定批處理大小;
- 自定義功能回調:提供給用戶一個擴展點,用戶可以在指定類型的擴展點執行任何數量需要的操作;
ConnectionCallback:通過回調獲取JdbcTemplate提供的Connection,用戶可在該Connection執行任何數量的操作;
StatementCallback:通過回調獲取JdbcTemplate提供的Statement,用戶可以在該Statement執行任何數量的操作;
PreparedStatementCallback:通過回調獲取JdbcTemplate提供的PreparedStatement,用戶可以在該PreparedStatement執行任何數量的操作;
CallableStatementCallback:通過回調獲取JdbcTemplate提供的CallableStatement,用戶可以在該CallableStatement執行任何數量的操作;
- 結果集處理回調:通過回調處理ResultSet或將ResultSet轉換為需要的形式;
RowMapper:用於將結果集每行數據轉換為需要的類型,用戶需實現方法mapRow(ResultSet rs, int rowNum)來完成將每行數據轉換為相應的類型。
RowCallbackHandler:用於處理ResultSet的每一行結果,用戶需實現方法processRow(ResultSet rs)來完成處理,在該回調方法中無需執行rs.next(),該操作由JdbcTemplate來執行,用戶只需按行獲取數據然后處理即可。
ResultSetExtractor:用於結果集數據提取,用戶需實現方法extractData(ResultSet rs)來處理結果集,用戶必須處理整個結果集;
PreparedStatement和CallableStatement區別 :
Statement接口提供了執行語句和獲取結果的基本方法, PreparedStatement繼承自Statement, 而CallableStatement繼承自PreparedStatement
PreparedStatement 接口添加了處理 IN 參數的方法
CallableStatement 接口添加了處理 OUT 參數的方法
PreparedStatement:
可變參數的SQL,編譯一次,執行多次,效率高;
安全性好,有效防止Sql注入等問題;
支持批量更新,批量刪除;
CallableStatement:
繼承自PreparedStatement,支持帶參數的SQL操作;
支持調用存儲過程,提供了對輸出和輸入/輸出參數(INOUT)的支持;
Statement每次執行sql語句,數據庫都要執行sql語句的編譯 ,最好用於僅執行一次查詢並返回結果的情形時,效率高於PreparedStatement。
PreparedStatement是預編譯的,使用PreparedStatement有幾個好處
1. 在執行可變參數的一條SQL時,PreparedStatement比Statement的效率高,因為DBMS預編譯一條SQL當然會比多次編譯一條SQL的效率要高。
2. 安全性好,有效防止Sql注入等問題。
3. 對於多次重復執行的語句,使用PreparedStament效率會更高一點,並且在這種情況下也比較適合使用batch;
4. 代碼的可讀性和可維護性。
注:
executeQuery:返回結果集(ResultSet)。
executeUpdate: 執行給定SQL語句,該語句可能為 INSERT、UPDATE 或 DELETE 語句, 或者不返回任何內容的SQL語句(如 SQL DDL 語句)。
execute: 可用於執行任何SQL語句,返回一個boolean值,
表明執行該SQL語句是否返回了ResultSet。如果執行后第一個結果是ResultSet,則返回true,否則返回false。
接下來讓我們看下具體示例吧,在示例中不可能介紹到JdbcTemplate全部方法及回調類的使用方法,我們只介紹代表性的,其余的使用都是類似的;
1)預編譯語句及存儲過程創建回調、自定義功能回調使用:
public void testPpreparedStatement1() { int count = jdbcTemplate.execute(new PreparedStatementCreator() { @Override public PreparedStatement createPreparedStatement(Connection conn) throws SQLException { return conn.prepareStatement("select count(*) from test"); }}, new PreparedStatementCallback<Integer>() { @Override public Integer doInPreparedStatement(PreparedStatement pstmt) throws SQLException, DataAccessException { pstmt.execute(); ResultSet rs = pstmt.getResultSet(); rs.next(); return rs.getInt(1); }});
Assert.assertEquals(0, count); }
首先使用PreparedStatementCreator創建一個預編譯語句,其次由JdbcTemplate通過PreparedStatementCallback回調傳回,由用戶決定如何執行該PreparedStatement。此處我們使用的是execute方法。
2)預編譯語句設值回調使用:
public void testPreparedStatement2() { String insertSql = "insert into test(name) values (?)"; int count = jdbcTemplate.update(insertSql, new PreparedStatementSetter() { @Override public void setValues(PreparedStatement pstmt) throws SQLException { pstmt.setObject(1, "name4"); }}); Assert.assertEquals(1, count); String deleteSql = "delete from test where name=?"; count = jdbcTemplate.update(deleteSql, new Object[] {"name4"}); Assert.assertEquals(1, count); }
通過JdbcTemplate的int update(String sql, PreparedStatementSetter pss)執行預編譯sql,其中sql參數為“insert into test(name) values (?) ”,該sql有一個占位符需要在執行前設值,PreparedStatementSetter實現就是為了設值,使用setValues(PreparedStatement pstmt)回調方法設值相應的占位符位置的值。JdbcTemplate也提供一種更簡單的方式“update(String sql, Object... args)”來實現設值,所以只要當使用該種方式不滿足需求時才應使用PreparedStatementSetter。
3)結果集處理回調:
public void testResultSet1() { jdbcTemplate.update("insert into test(name) values('name5')"); String listSql = "select * from test"; List result = jdbcTemplate.query(listSql, new RowMapper<Map>() { @Override public Map mapRow(ResultSet rs, int rowNum) throws SQLException { Map row = new HashMap(); row.put(rs.getInt("id"), rs.getString("name")); return row; }}); Assert.assertEquals(1, result.size()); jdbcTemplate.update("delete from test where name='name5'"); }
RowMapper接口提供mapRow(ResultSet rs, int rowNum)方法將結果集的每一行轉換為一個Map,當然可以轉換為其他類,如表的對象畫形式。
public void testResultSet2() { jdbcTemplate.update("insert into test(name) values('name5')"); String listSql = "select * from test"; final List result = new ArrayList(); jdbcTemplate.query(listSql, new RowCallbackHandler() { @Override public void processRow(ResultSet rs) throws SQLException { Map row = new HashMap(); row.put(rs.getInt("id"), rs.getString("name")); result.add(row); }}); Assert.assertEquals(1, result.size()); jdbcTemplate.update("delete from test where name='name5'"); }
RowCallbackHandler接口也提供方法processRow(ResultSet rs),能將結果集的行轉換為需要的形式。
public void testResultSet3() { jdbcTemplate.update("insert into test(name) values('name5')"); String listSql = "select * from test"; List result = jdbcTemplate.query(listSql, new ResultSetExtractor<List>() { @Override 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)查詢一行數據並將該行數據轉換為Map返回
jdbcTemplate.queryForMap("select * from test where name='name5'");
2)查詢一行任何類型的數據,最后一個參數指定返回結果類型
jdbcTemplate.queryForObject("select count(*) from test", Integer.class);
3)查詢一批數據,默認將每行數據轉換為Map
jdbcTemplate.queryForList("select * from test");
4)只查詢一列數據列表,列類型是String類型,列名字是name
jdbcTemplate.queryForList("select name from test where name=?", new Object[]{"name5"}, String.class);
5)查詢一批數據,返回為SqlRowSet,類似於ResultSet,但不再綁定到連接上
SqlRowSet rs = jdbcTemplate.queryForRowSet("select * from test");
存儲過程及函數回調
創建一個表,插入一條數據
創建三個存儲過程
CREATE DEFINER=`root`@`127.0.0.1` PROCEDURE `t_customer_insert`(IN `cust_name` varchar(16),IN `phone` varchar(16),IN `gender` tinyint,IN `create_time` bigint) BEGIN
INSERT INTO t_customer(cname,phone,gender,create_time) VALUES(cust_name,phone,gender,create_time); END
CREATE DEFINER=`root`@`127.0.0.1` PROCEDURE `t_customer_select`(IN cust_id BIGINT, OUT cust_name varchar(16), OUT out_phone varchar(16)) BEGIN
SELECT cname,phone INTO cust_name, out_phone FROM t_customer WHERE id = cust_id; END
CREATE DEFINER=`root`@`127.0.0.1` PROCEDURE `t_customer_querylist`(IN cust_id BIGINT) BEGIN
SELECT * FROM t_customer WHERE id = cust_id; END
執行第一個存儲過程
public void insertCustomer() { String sql = "call t_customer_insert('eerr1','13265326536',0,1557308503005)"; jdbcTemplate.execute(sql); }
執行第二個存儲過程
public void customerSelect() { Map<String, String> resultMap = getJdbcTemplate().execute(new CallableStatementCreator() { @Override public CallableStatement createCallableStatement(Connection con) throws SQLException { String storedProc = "{call t_customer_select (?,?,?)}"; CallableStatement cs = con.prepareCall(storedProc); cs.setInt(1, 1); // 設置輸入參數的值
cs.registerOutParameter(2, Types.VARCHAR); // 注冊輸出參數的類型
cs.registerOutParameter(3, Types.VARCHAR); return cs; } }, new CallableStatementCallback<Map<String, String>>() { @Override public Map<String, String> doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException { cs.execute(); Map<String, String> retMap = new HashMap<>(); retMap.put("cname", cs.getString(2)); retMap.put("phone", cs.getString(3)); return retMap; } }); }
執行第三個存儲過程
public <T> List<T> queryCustomer(long custid, Class<T> clazz) { return getJdbcTemplate().execute(new CallableStatementCreator() { @Override public CallableStatement createCallableStatement(Connection con) throws SQLException { String storedProc = "{call proc_cust_querylist(?)}"; CallableStatement cs = con.prepareCall(storedProc); cs.setLong(1, custid); return cs; } }, new CallableStatementCallback<List<T>>() { @Override public List<T> doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException { cs.execute(); ResultSet rs = cs.getResultSet(); ResultSet result = cs.getResultSet(); List<T> list = new ArrayList<T>(); if (result != null) { RowMapper<T> rowMapper = new BeanPropertyRowMapper<T>(clazz); int rowNum = 1; while (result.next()) { list.add(rowMapper.mapRow(result, rowNum++)); } } return list; } }); }
也可以用call方法來調用
public void testCallableStatementCreator3() { final String callProcedureSql = "{call PROCEDURE_TEST(?, ?)}"; List<SqlParameter> params = new ArrayList<SqlParameter>(); params.add(new SqlInOutParameter("inOutName", Types.VARCHAR)); params.add(new SqlOutParameter("outId", Types.INTEGER)); Map<String, Object> outValues = jdbcTemplate.call( new CallableStatementCreator() { @Override public CallableStatement createCallableStatement(Connection conn) throws SQLException { CallableStatement cstmt = conn.prepareCall(callProcedureSql); cstmt.registerOutParameter(1, Types.VARCHAR); cstmt.registerOutParameter(2, Types.INTEGER); cstmt.setString(1, "test"); return cstmt; }}, params); }
outValues:通過SqlInOutParameter及SqlOutParameter參數定義的name來獲取存儲過程結果。
調用自定義函數
public void testCallableStatementCreator1() { final String callFunctionSql = "{call FUNCTION_TEST(?)}"; List<SqlParameter> params = new ArrayList<SqlParameter>(); params.add(new SqlParameter(Types.VARCHAR)); params.add(new SqlReturnResultSet("result", new ResultSetExtractor<Integer>() { @Override public Integer extractData(ResultSet rs) throws SQLException, DataAccessException { while(rs.next()) { return rs.getInt(1); } return 0; })); Map<String, Object> outValues = jdbcTemplate.call( new CallableStatementCreator() { @Override public CallableStatement createCallableStatement(Connection conn) throws SQLException { CallableStatement cstmt = conn.prepareCall(callFunctionSql); cstmt.setString(1, "test"); return cstmt; }}, params); Assert.assertEquals(4, outValues.get("result")); }
- {call FUNCTION_TEST(?)}:定義自定義函數的sql語句,注意hsqldb {?= call …}和{call …}含義是一樣的,而比如mysql中兩種含義是不一樣的;
- params:用於描述自定義函數占位符參數或命名參數類型;SqlParameter用於描述IN類型參數、SqlOutParameter用於描述OUT類型參數、SqlInOutParameter用於描述INOUT類型參數、SqlReturnResultSet用於描述調用存儲過程或自定義函數返回的ResultSet類型數據,其中SqlReturnResultSet需要提供結果集處理回調用於將結果集轉換為相應的形式,hsqldb自定義函數返回值是ResultSet類型。
- CallableStatementCreator:提供Connection對象用於創建CallableStatement對象
- outValues:調用call方法將返回類型為Map<String, Object>對象;
- outValues.get("result"):獲取結果,即通過SqlReturnResultSet對象轉換過的數據;其中SqlOutParameter、SqlInOutParameter、SqlReturnResultSet指定的name用於從call執行后返回的Map中獲取相應的結果,即name是Map的鍵。
注:因為hsqldb {?= call …}和{call …}含義是一樣的,因此調用自定義函數將返回一個包含結果的ResultSet。
最后讓我們示例下mysql如何調用自定義函數:
public void testCallableStatementCreator2() { JdbcTemplate mysqlJdbcTemplate = new JdbcTemplate(getMysqlDataSource); //2.創建自定義函數 String createFunctionSql = "CREATE FUNCTION FUNCTION_TEST(str VARCHAR(100)) " + "returns INT return LENGTH(str)"; String dropFunctionSql = "DROP FUNCTION IF EXISTS FUNCTION_TEST"; mysqlJdbcTemplate.update(dropFunctionSql); mysqlJdbcTemplate.update(createFunctionSql); //3.准備sql,mysql支持{?= call …} final String callFunctionSql = "{?= call FUNCTION_TEST(?)}"; //4.定義參數 List<SqlParameter> params = new ArrayList<SqlParameter>(); params.add(new SqlOutParameter("result", Types.INTEGER)); params.add(new SqlParameter("str", Types.VARCHAR)); Map<String, Object> outValues = mysqlJdbcTemplate.call(new CallableStatementCreator() { @Override public CallableStatement createCallableStatement(Connection conn) throws SQLException { CallableStatement cstmt = conn.prepareCall(callFunctionSql); cstmt.registerOutParameter(1, Types.INTEGER); cstmt.setString(2, "test"); return cstmt; }}, params); Assert.assertEquals(4, outValues.get("result")); } public DataSource getMysqlDataSource() { String url = "jdbc:mysql://localhost:3306/test"; DriverManagerDataSource dataSource = new DriverManagerDataSource(url, "root", "");
dataSource.setDriverClassName("com.mysql.jdbc.Driver"); return dataSource; }
- getMysqlDataSource:首先啟動mysql(本書使用5.4.3版本),其次登錄mysql創建test數據庫(“create database test;”)
- {?= call FUNCTION_TEST(?)}:可以使用{?= call …}形式調用自定義函數;
- params:無需使用SqlReturnResultSet提取結果集數據,而是使用SqlOutParameter來描述自定義函數返回值;
- CallableStatementCreator:同上個例子含義一樣;
- cstmt.registerOutParameter(1, Types.INTEGER):將OUT類型參數注冊為JDBC類型Types.INTEGER,此處即返回值類型為Types.INTEGER。
- outValues.get("result"):獲取結果,直接返回Integer類型,比hsqldb簡單多了吧。
JdbcTemplate類還提供了很多便利方法,在此就不一一介紹了,但這些方法是由規律可循的,第一種就是提供回調接口讓用戶決定做什么,第二種可以認為是便利方法(如queryForXXX),用於那些比較簡單的操作
tatement的Batch使用: Statement stmt = conn.createStatement(); String sql = null; for(int i =0;i<20;i++){ sql = "insert into test(id,name)values("+i+","+i+"_name)"; stmt.addBatch(sql); } stmt.executeBatch(); PreparedStatement的Batch使用: PreparedStatement pstmt = con.prepareStatement("UPDATE EMPLOYEES SET SALARY = ? WHERE ID =?"); for(int i =0;i<length;i++){ pstmt.setBigDecimal(1, param1[i]); pstmt.setInt(2, param2[i]); pstmt.addBatch(); } pstmt.executeBatch();
【ReplicationDriver支持讀寫分離】
http://www.cnblogs.com/flywang/p/7777792.html
https://blog.csdn.net/zhu_tianwei/article/details/53454259