一,、使用PreparedStatement預(yù)編譯語句防止SQL注入
什么是SQL注入?
所謂SQL注入,,就是通過把SQL命令插入到Web表單提交或輸入域名或頁(yè)面請(qǐng)求的查詢字符串,,最終達(dá)到欺騙服務(wù)器執(zhí)行惡意的SQL命令,。
舉個(gè)例子:假如我們登錄時(shí)執(zhí)行的SQL語句為:select *from user where username='USERNAME' and password='PASSWORD';
但是我們可以把USERNAME填寫為"tom';-- ",(注意--后有空格),PASSWORD隨便寫(假設(shè)這里寫123),,這樣SQL語句就成了select *from user where username='tom';-- ' and password='123';
"-- "后的內(nèi)容就被注釋掉,,現(xiàn)在就算密碼不正確也能查詢到相應(yīng)的結(jié)果。利用SQL注入可以實(shí)現(xiàn)數(shù)據(jù)的盜取
在Java中使用PreparedStatement類防止SQL注入
防SQL注入的方法有很多,,使用預(yù)編譯語句是一種簡(jiǎn)單有效的方式
下面介紹如何使用這種方式來操作數(shù)據(jù)庫(kù)
我已經(jīng)在本地?cái)?shù)據(jù)庫(kù)mydb中創(chuàng)建了一個(gè)user表,,并在表中插入數(shù)據(jù)
現(xiàn)在我們使用字符串拼接的方式來構(gòu)造一個(gè)SQL語句,并在mysql客戶端執(zhí)行此SQL語句,,結(jié)果如下:
通過圖中我們發(fā)現(xiàn)這種方式SQL注入成功
下面使用預(yù)編譯SQL語句的方式來執(zhí)行此SQL語句
String url = "jdbc:mysql://localhost:3306/mydb";
String user = "root";
String password = "root";
String username = "杜若' or 1=1; -- "; //'-- ' 是sql中的注釋符號(hào)(注意后面的空格)
String pwd = "000";
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(url,user,password);
//構(gòu)造sql語句
String sql = "select *from user where username=? and pwd =?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, username);
pstmt.setString(2, pwd);
ResultSet rs = pstmt.executeQuery();
if(rs.next()){
do{
String username = rs.getString(2);
String pwd = rs.getString(3);
System.out.println(username ":" pwd);
}while(rs.next());
}else{
System.out.println("未查到相應(yīng)的結(jié)果");
}
if(rs!=null){
rs.close();
}
if(pstmt!=null){
pstmt.close();
}
if(connection!=null){
connection.close();
}
測(cè)試運(yùn)行之后的結(jié)果
這樣已經(jīng)達(dá)到了防SQL注入的目的
那為什么它這樣處理就能預(yù)防SQL注入提高安全性呢,?其實(shí)是因?yàn)镾QL語句在程序運(yùn)行前已經(jīng)進(jìn)行了預(yù)編譯,在程序運(yùn)行時(shí)第一次操作數(shù)據(jù)庫(kù)之前,,SQL語句已經(jīng)被數(shù)據(jù)庫(kù)分析,編譯和優(yōu)化,,對(duì)應(yīng)的執(zhí)行計(jì)劃也會(huì)緩存下來并允許數(shù)據(jù)庫(kù)已參數(shù)化的形式進(jìn)行查詢,,當(dāng)運(yùn)行時(shí)動(dòng)態(tài)地把參數(shù)傳給PreprareStatement時(shí),即使參數(shù)里有敏感字符如 or '1=1'也數(shù)據(jù)庫(kù)會(huì)作為一個(gè)參數(shù)一個(gè)字段的屬性值來處理而不會(huì)作為一個(gè)SQL指令
二,,獲取插入自增長(zhǎng)值
有時(shí)候在插入一條記錄的時(shí)候,,我們需要獲取插入這條數(shù)據(jù)的自增長(zhǎng)值,以便在其他地方使用
直接上代碼
//獲取插入自增長(zhǎng)
public class Demo1 {
private String user="root";
private String password="root";
private String url="jdbc:mysql://localhost:3306/mydb";
@Test
public void test1() throws Exception{
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(url, user, password);
String sql = "insert into user(username,pwd) values(?,?)";
//第二個(gè)參數(shù)是Statement類中的一個(gè)常量,,指示是否應(yīng)該返回自動(dòng)生成的鍵的標(biāo)志
PreparedStatement pstmt = connection.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
pstmt.setString(1, "mary");
pstmt.setString(2, "123456");
pstmt.executeUpdate();
ResultSet rs = pstmt.getGeneratedKeys();
if(rs.next()){
int key = rs.getInt(1);
System.out.println(key);
}
if(rs!=null){
rs.close();
}
if(pstmt!=null){
pstmt.close();
}
if(connection!=null){
connection.close();
}
}
}
三,,批處理執(zhí)行
有時(shí)候需要向數(shù)據(jù)庫(kù)發(fā)送一批SQL語句執(zhí)行,這時(shí)應(yīng)避免向數(shù)據(jù)庫(kù)一條條的發(fā)送執(zhí)行,,而應(yīng)采用JDBC的批處理機(jī)制,,以提升執(zhí)行效率。
無論使用Statement對(duì)象還是使用PreparedStatement對(duì)象都可以實(shí)現(xiàn)
這里介紹使用PreparedStatement來實(shí)現(xiàn)的方式
- 獲取PreparedStatement對(duì)象
- 設(shè)置每條批處理的參數(shù)
- 將一組參數(shù)添加到批處理命令中
下面上實(shí)例代碼
public class Demo1 {
private String user="root";
private String password="root";
private String url="jdbc:mysql://localhost:3306/mydb";
@Test
public void test1() throws Exception{
List<User> list = new ArrayList<User>();
for(int i = 0;i < 10;i ){
User user = new User();
user.setUsername("albee" i);
user.setPwd("123456");
list.add(user);
}
sava(list);
}
public void sava(List<User> l) throws Exception{
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(url, user, password);
String sql = "insert into user(username,pwd) values(?,?)";
PreparedStatement pstmt = connection.prepareStatement(sql);
for(int i = 0;i<l.size();i ){
pstmt.setString(1, l.get(i).getUsername());
pstmt.setString(2, l.get(i).getPwd());
pstmt.addBatch();
// 每到五條就執(zhí)行一次對(duì)象集合中的批處理命令,,
if(i%5==0){
pstmt.executeBatch();
pstmt.clearBatch();
}
}
// 不足五條也要執(zhí)行一次
pstmt.executeBatch();
pstmt.clearBatch();
if(pstmt!=null){
pstmt.close();
}
if(connection!=null){
connection.close();
}
}
}
四,,事務(wù)的操作
有時(shí)候我們需要完成一組數(shù)據(jù)庫(kù)操作,這其中的操作有一個(gè)失敗則整個(gè)操作就回滾,,即組成事務(wù)的每一個(gè)操作都執(zhí)行成功整個(gè)事務(wù)才算成功執(zhí)行
常見的例子就是銀行轉(zhuǎn)帳業(yè)務(wù)
update account set money=money-1000 where accountName='張三';
update account set money=money 1000 where accountName='李四';
已經(jīng)在mydb中創(chuàng)建了account表,,包含accountName和money字段,并插入了兩條記錄
實(shí)例代碼:
// 使用事務(wù)
@Test
public void test2(){
String url = "jdbc:mysql://localhost:3306/mydb";
String user = "root";
String password = "root";
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection(url,user,password);
connection.setAutoCommit(false); //設(shè)置事務(wù)為手動(dòng)提交
String sql_zs = "update account set money=money-1000 where accountName='張三'";
String sql_ls = "update account set money=money 1000 where accountName='李四'";
pstmt = connection.prepareStatement(sql_ls);
int count_ls = pstmt.executeUpdate();
pstmt = connection.prepareStatement(sql_zs);
int count_zs = pstmt.executeUpdate();
}catch (Exception e) {
e.printStackTrace();
try {
connection.rollback();
} catch (Exception e2) {
e2.printStackTrace();
}
} finally {
try {
connection.commit();
} catch (SQLException e1) {
e1.printStackTrace();
}
if(pstmt!=null){
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
如果事務(wù)執(zhí)行成功
執(zhí)行前和執(zhí)行后表的變動(dòng)
來源:https://www./content-4-542801.html
|