JDBC預(yù)編譯sql語句處理
時間:20180311
使用Statement與PreparedStatement執(zhí)行DDL與DML的區(qū)別惭墓?
/**
* jdbc通用方法
* @author mengjie
*
*/
public class JdbcUtil {
//url
private static String url = "jdbc:mysql://localhost:3306/day16";
//user
private static String user = "root";
//password
private static String password = "root";
/**
*z只注冊一次驅(qū)動菠赚,靜態(tài)代碼塊
*/
static {
//注冊驅(qū)動程序
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 獲取連接的方法
* @throws SQLException
*/
public static Connection getConnection() {
try {
Connection conn = DriverManager.getConnection(url, user, password);
return conn;
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 釋放資源的方法
*/
public static void close(Statement stmt, Connection conn) {
if(stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
/**
* 釋放資源的方法
*/
public static void close(ResultSet rs, Statement stmt, Connection conn) {
if(rs!=null) {
try {
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
}
/**
* 使用PreparedStatement執(zhí)行CRUD操作
* 執(zhí)行預(yù)編譯的sql語句
* @author mengjie
*
*/
public class Demo1 {
public static void main(String[] args) {
//insert();
//update();
query();
}
private static void query() {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet res = null;
try{
//1)建立連接
conn = JdbcUtil.getConnection();
//2) 創(chuàng)建sql
String sql = "select * from student where id = ?";
//3) 創(chuàng)建PreparedStatement,預(yù)編譯sql語句
stmt = conn.prepareStatement(sql);
//4) 給參數(shù)賦值
/**
* 注意參數(shù)的位置朽砰,一定要對應(yīng)字段的位置
*/
stmt.setInt(1, 5);
//5) 執(zhí)行sql
res = stmt.executeQuery();
while(res.next()) {
int id = res.getInt("id");
String name = res.getString("name");
int age = res.getInt("age");
System.out.println(id + "\t" +name+"\t"+age);
}
}catch(Exception e) {
e.printStackTrace();
}finally {
JdbcUtil.close(stmt, conn);
}
}
private static void update() {
/**
* 修改
*/
Connection conn = null;
PreparedStatement stmt = null;
try{
//1)建立連接
conn = JdbcUtil.getConnection();
//2) 創(chuàng)建sql
String sql = "update student set name = ?, age = ? where id = ?";
//3) 創(chuàng)建PreparedStatement,預(yù)編譯sql語句
stmt = conn.prepareStatement(sql);
//4) 給參數(shù)賦值
/**
* 注意參數(shù)的位置,一定要對應(yīng)字段的位置
*/
stmt.setInt(3, 4);
stmt.setString(1, "wanzhe");
stmt.setInt(2, 100);
//5) 執(zhí)行sql
int n = stmt.executeUpdate();
System.out.println("影響了"+ n + "行");
}catch(Exception e) {
e.printStackTrace();
}finally {
JdbcUtil.close(stmt, conn);
}
}
private static void insert() {
/**
* 插入
*/
Connection conn = null;
PreparedStatement stmt = null;
try {
//1)獲取連接
conn = JdbcUtil.getConnection();
//2)準(zhǔn)備sql//?號是參數(shù)的占位符农猬,一個問好代表是一個參數(shù)凶异。
String sql = "insert into student(id,name,age) values(?,?,?)";//
//3)創(chuàng)建PreparedStatement,預(yù)編譯sql語句
stmt = conn.prepareStatement(sql);//預(yù)編譯sql語句(sql語法和權(quán)限的檢查)棉胀,
//4)給參數(shù)賦值
/**
* 參數(shù)一: 參數(shù)的位置。從1開始
* 參數(shù)二:參數(shù)值
*/
stmt.setInt(1, 5);
stmt.setString(2, "里斯");
stmt.setInt(3, 40);
//5)發(fā)送參數(shù)到數(shù)據(jù)庫秽浇,執(zhí)行sql浮庐,
//executeUpdate方法中并沒有參數(shù),是將參數(shù)和sql組裝起來柬焕,并執(zhí)行
int n = stmt.executeUpdate();
System.out.println("影響了"+ n + "行");
}catch(Exception e){
e.printStackTrace( );
}finally {
//關(guān)閉資源审残,而且不用更改close方法(此時傳遞的stmt是preparedStatement對象),不用重載
//實現(xiàn)參數(shù)為preparedStatement對象的close方法
//原因是statement是preparedstatement類的父類
JdbcUtil.close(stmt, conn);
}
}
}
為什么要使用PreparedStatement?
1.PreparedStatement接口繼承了Statement斑举,PreparedStatement實例包含已編譯的SQL語句搅轿,所以其執(zhí)行速度要快于Statement對象。
2.作為Statement的子類富玷,PreparedStatment繼承了Statement的所有功能璧坟。三種方法execute,executeQuery和executeUpdate已被更改以使之不再需要參數(shù)赎懦。
3.在JDBC應(yīng)用中雀鹃,在任何時候都不要是使用Statement,原因如下:
??1)铲敛、代碼的可讀性和可維護(hù)性褐澎。Statement需要不斷地拼接,而PreparedStatement不會伐蒋。
??2)工三、PreparedStatement盡最大可能提高性能。DB有緩存機制先鱼,相同的預(yù)編譯語句再次被調(diào)用不會再次需要編譯俭正。
??3)、最重要的一點是極大的提高了安全性焙畔。Statement容易被SQL注入掸读,而PreparedStatement傳入的內(nèi)容和參數(shù)不會和sql語句發(fā)生任何匹配關(guān)系。
關(guān)于statement和PreparedStatement的安全性做具體分析如下:
例如用戶登陸的案例
在數(shù)據(jù)庫中操作:
--創(chuàng)建用戶表
CREATE TABLE user_list(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
PASSWORD VARCHAR(20)
)
--插入數(shù)據(jù)
INSERT INTO user_list(NAME,PASSWORD) VALUES('eric','123456');
INSERT INTO user_list(NAME,PASSWORD) VALUES('jacky','654321');
SELECT * FROM user_list;
如何判斷登陸是否成功呢?根據(jù)下列sql:
--登陸成功
SELECT * FROM user_list WHERE NAME='eric' AND PASSWORD='123456'
但是在數(shù)據(jù)庫sql中存在如下情況:
SELECT * FROM user_list WHERE 1=1; --1=1; 恒成立
登陸失敗的情況
SELECT * FROM user_list WHERE NAME='rose' AND PASSWORD='123456'
可將上述sql更改為恒成立情況儿惫,注意-- 前后的空格
SELECT * FROM user_list WHERE NAME ='rose' OR 1=1 -- ' AND PASSWORD='123456';
以上都是在客戶端中執(zhí)行的sql語句進(jìn)行的測試對比
分割線
以下都是在代碼中執(zhí)行的sql語句進(jìn)行的測試對比
分別在Statement和PreparedStatement中執(zhí)行帶有sql注入的sql澡罚,驗證Statement和PreparedStatement的安全性。
- Statement情況
public class Demo2 {
private static Connection conn = null;
private static Statement stmt = null;
private static ResultSet res = null;
public static void main(String[] args) throws Exception {
String username = "rose";
String password = "123456";
conn = JdbcUtil.getConnection();
stmt = conn.createStatement();
String sql = "SELECT * FROM user_list WHERE NAME =' "+username+" ' AND PASSWORD=' "+password+" '";
res = stmt.executeQuery(sql);
if(res.next()) {
System.out.println("登陸成功");
}else {
System.out.println("登陸失敗");
}
}
}
結(jié)果:
登陸失敗
- Statement SQL注入的情況
public class Demo2 {
private static Connection conn = null;
private static Statement stmt = null;
private static ResultSet res = null;
public static void main(String[] args) throws Exception {
String username = "rose' OR 1=1 -- ";
String password = "123456";
conn = JdbcUtil.getConnection();
stmt = conn.createStatement();
String sql = "SELECT * FROM user_list WHERE NAME =' "+username+" ' AND PASSWORD=' "+password+" '";
res = stmt.executeQuery(sql);
if(res.next()) {
System.out.println("登陸成功");
}else {
System.out.println("登陸失敗");
}
}
}
結(jié)果:
登陸成功
- PreparedStatement情況
public class Demo2 {
private static Connection conn = null;
private static PreparedStatement stmt = null;
private static ResultSet res = null;
public static void main(String[] args){
conn = JdbcUtil.getConnection();
String sql = "SELECT * FROM user_list WHERE NAME = ? AND PASSWORD= ?";
try {
stmt = conn.prepareStatement(sql);
stmt.setString(1, "rose' OR 1=1 -- ");
stmt.setString(2, "123456");
res = stmt.executeQuery();
if(res.next()) {
System.out.println("登陸成功");
}else {
System.out.println("登陸失敗");
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
JdbcUtil.close(res,stmt,conn);
}
}
}
結(jié)果:
登陸失敗
解析PreparedStatement是如何防止SQL注入的肾请?
- 執(zhí)行stmt = conn.prepareStatement(sql);(此句話叫預(yù)編譯sql)1.先將sql發(fā)送至數(shù)據(jù)庫留搔。2.發(fā)送之后驗證sql的語法。3.驗證sql中語法發(fā)現(xiàn)sql中有兩個參數(shù)NAME铛铁、PSASSWORD(該sql執(zhí)行時必須要有NAME隔显、PSASSWORD兩個個參數(shù))。
- 執(zhí)行stmt.setString(1, "rose' OR 1=1 -- ");
stmt.setString(2, "123456");參數(shù)賦值饵逐。 - 執(zhí)行stmt.executeQuery();即執(zhí)行sql括眠,就將賦值的兩個參數(shù)塞進(jìn)驗證sql語法驗證的兩個參數(shù)中去。此時"rose' OR 1=1 -- "是作為NAME的參數(shù)倍权,因此回去數(shù)據(jù)庫中查找NAME名為"rose' OR 1=1 -- "的數(shù)據(jù)掷豺,顯然是查詢不到的,說明PreparedStatement傳入的內(nèi)容不會和sql語句發(fā)生任何關(guān)系
- Statement中并不進(jìn)行sql語法驗證(參數(shù)驗證)
說明PreparedStatement傳入的內(nèi)容不會和sql語句發(fā)生任何關(guān)系账锹,這也是statement中的sql(靜態(tài)sql)能被注入sql的原因萌业。