demo
@Repository("jdbcDao") public class JdbcTemplateDao { @Autowired private JdbcTemplate jdbcTemplate; @Autowired private NamedParameterJdbcTemplate namedTemplate; private final static List<String> names = new ArrayList<String>(); private final String childAge = "5"; private final String parentId = "2"; static { names.add("吳三"); names.add("吳二"); } }
<bean id="dataSource" ...> </bean >
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" abstract="false" lazy-init="false" autowire="default" > <property name="dataSource" ref="dataSource"/> </bean> <!-- NamedParameterJdbcTemplate的構造函數有2種。1.DataSource;2.JdbcOperations --> <bean id="namedTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" abstract="false" lazy-init="false" autowire="default" > <constructor-arg type="javax.sql.DataSource" ref="dataSource" /> <!--constructor-arg type="org.springframework.jdbc.core.JdbcOperations" ref="jdbcTemplate" / --> </bean>
一、數組形式的參數
優點: 針對簡單sql,可以節約部分內存空間。減少代碼量、易讀。
缺點:
1、相同的參數不可以復用,讓array占用更多的空間。(當然,以現在的硬件來說,這點空間/內存、多余添加數組值花費的時間 完全微不足道)
2、如果是in的參數,要動態拼接(?)占位符。(個人認為最麻煩、繁瑣的,擴展:oracle對in最大支持1000)
3、如果sql中參數過多,其實不好閱讀修改。
/** * 常規?占位參數;<br> * 問題:<br> * 1、如果參數是in,那么需要自己動態的添加占位符?。明顯這很麻煩。<br> * 2、如果參數多次出現,那么數組中也要重復出現。明顯這很浪費空間。<br> */ public List<Child> arrayParam() { List<Object> params = new ArrayList<Object>(); String sql = "select c.child_id childId,c.child_name,c.child_age childAge,c.parent_id parentId from child c"; sql += " where c.child_age=? and c.parent_id = ?"; params.add(childAge); params.add(parentId); //如果是in參數,拼接?占位符很麻煩。 sql += " and c.child_name in("; for (Iterator<String> iterator = names.iterator(); iterator.hasNext(); ) { iterator.next(); sql += "?"; if(iterator.hasNext()) sql += ","; } sql += ")"; params.addAll(names); return this.jdbcTemplate.query(sql,params.toArray(),new BeanPropertyRowMapper<Child>(Child.class)); }
個人習慣用List添加參數,然后再把List轉換成Array。
好處是:如果用數組,當sql存在動態條件,那么無法確定數組長度。而用List就不需要自己去維護。
二、map形式的參數
優點:
1、解決了in參數的問題。
2、參數值可以復用。
/** * map實現別名參數。<br/> * 解決:<br/> * 1、相對array參數,解決了參數in復雜、變量重用的問題。<br/> * 問題:<br/> * 1、如果是in貌似是不可以用數組的,用list可以。<br/> * 2、比較麻煩的是NamedParameterJdbcTemplate和JdbcTemplate沒繼承/接口關系。並且Named依賴Jdbc,所以在寫公共dao的時候要注意。 * @return */ public List<Child> mapParam(){ Map<String,Object> params = new HashMap<String,Object>(); String sql = "select c.child_id childId,c.child_name,c.child_age childAge,c.parent_id parentId from child c"; sql += " where c.child_age=:age and c.parent_id =:id and c.child_name in(:names)"; params.put("age",childAge); params.put("id",parentId); params.put("names",names); return namedTemplate.query(sql,params,new BeanPropertyRowMapper<Child>(Child.class)); }
可以看出對in的參數形式支持很友好,查詢條件也可以復用。但我遇到一個問題是:參數是in,在map不能用數組形式,用List是可以的。
特別:NamedParameterJdbcTemplate與jdbcTemplate沒有任何的繼承/實現關系(Named可以通過DataSource、JdbcTemplate來生成)。所以再寫公共的父類dao時,要想一下怎么寫。
三、javaBean形式的參數
/** * javaBean參數。<br></> * 要引入輔助的javaBean。 * @return */ public List<Child> beanParam(){ String sql = "select c.child_id childId,c.child_name,c.child_age childAge,c.parent_id parentId from child c"; sql += " where c.child_age=:childAge and c.parent_id =:parentId "; sql += " and c.child_name in(:names)"; ParamBean bean = new ParamBean(); bean.setChildAge(childAge); bean.setParentId(parentId); bean.setNames(names); SqlParameterSource param = new BeanPropertySqlParameterSource(bean); return namedTemplate.query(sql,param,new BeanPropertyRowMapper<Child>(Child.class)); }
簡單瀏覽了下源碼,感覺就是利用反射找到屬性名。然后處理和map形式的一樣。
此形式相對map來說,只在於是用Map還是JavaBean。
表面上的區別就是:如果參數是通過JavaBean傳到dao層,那么不用把bean轉換成map。相對的如果是通過map傳到dao層的,也用map形式也無需轉換成javaBean。
四、什么是防止sql注入?(個人很簡單的理解)
假設sql: select * from child c where c.id = ? ;
如果在dao層為了貪圖方便,或者沒有防止sql注入的概念(就是安全性問題)。在dao層的代碼:
String sql = "select * from child c where c.id='"+id+"'";
假設期望的id都是1,2,3等自增的數字字符串。
但是,此sql最后可能是任何形式。比如惡意攻擊時,最終sql: select * from child c where c.id='100';delete from child; --'
紅色下划線部分就是id的值(--在sql中是注釋,把最后一個引號注釋掉)。
那么會刪除整個表child的數據,這顯然是不安全的。
我簡單測試了下,數據庫是oracle。不管是jdbcTemplate、純jdbc、hibernate都不存在上訴問題(猜想是oracle驅動jar中處理了)。會拋一個異常出來:
java.sql.SQLException: ORA-00911: 無效字符 at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:439) at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:395) at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:802) at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:436) at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:186) at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:521) at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:205) at oracle.jdbc.driver.T4CPreparedStatement.executeForDescribe(T4CPreparedStatement.java:861) at oracle.jdbc.driver.OracleStatement.executeMaybeDescribe(OracleStatement.java:1145) at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1267) at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3449) at oracle.jdbc.driver.OraclePreparedStatement.executeQuery(OraclePreparedStatement.java:3493) at oracle.jdbc.driver.OraclePreparedStatementWrapper.executeQuery(OraclePreparedStatementWrapper.java:1203) at com.vergilyn.test.sh.dao.JdbcTemplateDao.injectionAttack(JdbcTemplateDao.java:49) at com.lyn.Junit.TestJdbc.testInjectionAttack(TestJdbc.java:35) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
測試代碼:
@Test @Rollback(true) public void testInjectionAttack(){ //結論 String id = "1"; id = "1';delete from child where child_id='1';--"; jdbcDao.injectionAttack(id); } @Test @Rollback(true) public void testSqlInjectionAttack(){ //結論 jdbcTemplate不會存在此問題 String id = "1"; id = "1';delete from child where child_id='1';--"; Child rs = jdbcDao.sqlInjectionAttack(id); System.out.println(JSON.toJSONString(rs)); }
public void injectionAttack(String id) { Connection con = null;// 創建一個數據庫連接 PreparedStatement pre = null;// 創建預編譯語句對象,一般都是用這個而不用Statement ResultSet result = null;// 創建一個結果集對象 try { Class.forName("oracle.jdbc.driver.OracleDriver");// 加載Oracle驅動程序 String url = "jdbc:oracle:thin:@127.0.0.1:1521:orcl";
String user = "vergilyn";
String password = "409839163";
con = DriverManager.getConnection(url, user, password);// 獲取連接 String sql = "select c.child_id childId,c.child_name,c.child_age childAge,c.parent_id parentId from child c"; sql += " where c.child_id= '"+id+"'"; pre = con.prepareStatement(sql);// 實例化預編譯語句 result = pre.executeQuery();// 執行查詢,注意括號中不需要再加參數 while (result.next()){ // 當結果集不為空時 System.out.println("姓名:" + result.getString("child_Name") ); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); }finally { try { // 逐一將上面的幾個對象關閉,因為不關閉的話會影響性能、並且占用資源 // 注意關閉的順序,最后使用的最先關閉 if (result != null) result.close(); if (pre != null) pre.close(); if (con != null) con.close(); System.out.println("數據庫連接已關閉!"); } catch (Exception e) { e.printStackTrace(); } } } /** * 測試sql注入攻擊。jdbcTemplate不會存在此安全問題。 * @param id * @return */ public Child sqlInjectionAttack(String id){ String sql = "select c.child_id childId,c.child_name,c.child_age childAge,c.parent_id parentId from child c"; sql += " where c.child_id= '"+id+"'"; return this.jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<Child>(Child.class)); }
正確sql:
select c.child_id childId,c.child_name,c.child_age childAge,c.parent_id parentId from child c where c.child_id= '1'
攻擊sql:
select c.child_id childId,c.child_name,c.child_age childAge,c.parent_id parentId from child c where c.child_id= '1';delete from child where child_id='1';--'
針對攻擊sql會拋出上面的異常,我把此sql在PL/SQL中是可以運行的。那么,個人猜想是oracle的驅動jar中進行處理了。
(可以看下如何防止sql注入攻擊,網上很多。雖然經過上面的測試,舉例的情況在測試時不會出現。但可以詳細了解下,還可能在什么情況下出現SQL注入攻擊)
百度baike: SQL注入攻擊
2016-12-22 以上說的: oracle的驅動jar中處理了sql注入攻擊是錯誤的。
【spring】(填坑)sql注入攻擊 - 持久層參數化 (驗證了錯誤,並沒看懂源代碼)