一、PrepareStatement
PrepareStatement是 Statement的子接口,可以传入带占位符的 SQL语句,提供了补充占位符变量的方法。
使用 prepareStatement:
1. 创建 PrepareStatement对象
- String sql = "INSERT INTO Student VALUES(?,?,?,?,?,?)";
- PrepareStatement ps = conn.prepareStatement(sql);
2. 调用 PrepareStatement的 setXxx(int index, Object val) 设置占位符的值,索引值从1开始。
3. 执行 SQL语句: executeQuery() 或 executeUpdate().。执行时不需要再传入 SQL 语句。
二、使用带占位符的SQL语句
/**
* PrepareStatement是 Statement的子接口,可以传入带占位符的 SQL语句
* 提供了补充占位符变量的方法。
*
* 使用 prepareStatement:
* 1. 创建 PrepareStatement对象
* String sql = "INSERT INTO Student VALUES(?,?,?,?,?,?)";
* PrepareStatement ps = conn.prepareStatement(sql);
* 2. 调用 PrepareStatement的 setXxx(int index, Object val) 设置占位符的值
* 索引值从1开始。
* 3. 执行 SQL语句: executeQuery() 或 executeUpdate().
* 执行时不需要再传入 SQL 语句。
*/
@Test
public void test8() {
Connection conn = null;
PreparedStatement preparedStatement = null;
try {
//获取数据库连接
conn = getConnection2();
//设置 SQL语句
String sql = "INSERT INTO Student(Sno, Sname, Ssex, Sage, Sdept, S_entrance) " +
"VALUES(?,?,?,?,?,?)";
//为占位符赋值
preparedStatement = conn.prepareStatement(sql);
preparedStatement.setString(1, "201415411");
preparedStatement.setString(2, "李明");
preparedStatement.setString(3, "男");
preparedStatement.setInt(4, 20);
preparedStatement.setString(5, "CS");
preparedStatement.setDate(6, Date.valueOf("2014-09-09"));
//执行 SQL语句
preparedStatement.executeUpdate();
} catch(Exception e) {
e.printStackTrace();
} finally {
if(preparedStatement != null) {
try {
preparedStatement.close();
} catch(Exception e) {
e.printStackTrace();
}
}
if(conn != null) {
try {
conn.close();
} catch(Exception e) {
e.printStackTrace();
}
}
}
}
//getConnection2()方法
public Connection getConnection2() throws Exception {
//1.准备连接数据库的4个字符串
//1.1 创建Properties对象
Properties properties = new Properties();
//1.2 获取jdbc.properties对应的输入流
java.io.InputStream in = this.getClass().getClassLoader().getResourceAsStream("jdbc.properties");
//1.3 加载文件
properties.load(in);
//1.4 给字符串赋值
String driver = properties.getProperty("driver");
String jdbcUrl = properties.getProperty("jdbcUrl");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
//2.加载数据库驱动程序(对应的Driver实现类中有注册驱动的静态代码块)
Class.forName(driver);
//3.通过DriverManager的getConnection方法获取数据库连接
return DriverManager.getConnection(jdbcUrl, user, password);
}
三、SQL 注入攻击
SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令,从而利用系统的 SQL 引擎完成恶意行为的做法。
对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement 取代 Statement 就可以了。
如下程序,使用Statement,有SQL注入风险
/**
* 用户表中有两列:用户username和密码password
* 只有当用户和密码都正确时,登陆成功,否则不成功
*
*/
@Test
public void test9() {
//当用户名和密码设置如下,表中不存在这个用户名和密码,但是任然可以登录成功
String username = "a' OR password = ";
String userpassword = " OR '1'='1";
String sql = "SELECT * FROM Users WHERE username = '" +
username +"' AND " +
"password= '" + userpassword + "'";
Connection conn = null;
Statement statement = null;
ResultSet resultSet = null;
try {
conn = getConnection2();
statement = conn.createStatement();
resultSet = statement.executeQuery(sql);
if(resultSet.next()) {
System.out.println("登陆成功!");
} else {
System.out.println("用户名和密码不匹配或用户不存在");
}
} catch(Exception e) {
e.printStackTrace();
} finally {
if(resultSet != null) {
try {
resultSet.close();
} catch(Exception e) {
e.printStackTrace();
}
}
if(statement != null) {
try {
statement.close();
} catch(Exception e) {
e.printStackTrace();
}
}
if(conn != null) {
try {
conn.close();
} catch(Exception e) {
e.printStackTrace();
}
}
}
}
当用户名和密码这样设置时:
String username = "a' OR password = ";
String userpassword = " OR '1'='1";
尽管表中没有这个用户名和密码,但依然可以登录,此时SQL语句为:
SELECT * FROM Student WHERE username = 'a' OR password = ' AND Sno= ' OR '1'='1'
如上,username的值为 a,password的值为 AND Sno=。整个WHERE条件是或条件判断,而 '1'='1' 是恒成立的,所以这条语句会执行。
使用PreparedStatement,将有效的解决注入问题
/**
* 使用 PreparedStatement将有效的解决注入问题
*
*/
@Test
public void test10() {
String username = "a' OR Sno = ";
String userpassword = " OR '1'='1";
String sql = "SELECT * FROM Users WHERE username = ?" +
" AND password= ?";
Connection conn = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
conn = getConnection2();
preparedStatement = conn.prepareStatement(sql);
preparedStatement.setString(1, username);
preparedStatement.setString(2, userpassword);
resultSet = preparedStatement.executeQuery();
if(resultSet.next()) {
System.out.println("登陆成功!");
} else {
System.out.println("用户名和密码不匹配或用户不存在");
}
} catch(Exception e) {
e.printStackTrace();
} finally {
if(resultSet != null) {
try {
resultSet.close();
} catch(Exception e) {
e.printStackTrace();
}
}
if(preparedStatement != null) {
try {
preparedStatement.close();
} catch(Exception e) {
e.printStackTrace();
}
}
if(conn != null) {
try {
conn.close();
} catch(Exception e) {
e.printStackTrace();
}
}
}
}
四、PreparedStatement 能最大可能提高性能
- DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。
- 在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存.这样每执行一次都要对传入的语句编译一次.
- (语法检查,语义检查,翻译成二进制命令,缓存)
JDBC学习笔记:
1. 获取数据库连接 http://my.oschina.net/daowuming/blog/704243
2. 通过Statement执行更新、查询操作 http://my.oschina.net/daowuming/blog/704384
3. 使用PrepareStatement ----当前----
4. 使用ResultSetMetaData 对象处理结果集元数据 http://my.oschina.net/daowuming/blog/704487
5. 使用DatabaseMetaData获取数据库信息 http://my.oschina.net/daowuming/blog/704553
6. BLOB http://my.oschina.net/daowuming/blog/704593
7. 处理事务与隔离级别 http://my.oschina.net/daowuming/blog/704611
8. 批量处理 http://my.oschina.net/daowuming/blog/704641
9. 数据库连接池 http://my.oschina.net/daowuming/blog/704700
10. 调用函数与存储过程 http://my.oschina.net/daowuming/blog/704813