JDBC--使用PrepareStatement

原创
2016/07/01 23:59
阅读数 1.1W

一、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

展开阅读全文
打赏
0
3 收藏
分享
加载中
更多评论
打赏
0 评论
3 收藏
0
分享
返回顶部
顶部