好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

JDBC第二部分—statment和preparedStatement

Statement使用的注意事项

statement的作用是执行一段静态的sql语句,并返回处理的结果集对象。但是statement存在一定的弊端:

①sql语句需要传递参数时,我们需要对sql语句进行拼接,这样会很麻烦,影响我们的开发效率。

②使用statement执行sql语句时会存在sql注入问题,简单来说就是利用没有对用户输入的数据进行检查,利用非法的sql语句完成恶意行为的做法

下面写了一个简单的登录例子,用来测试statement存在的sql注入问题。

正常访问数据库时:

  @Test
      public   void   queryDataByStatement()  {
          /*  
        简单的登录模块测试statement的弊端
           */  
        Scanner scanner  =  new   Scanner(System.in);
        System.out.print( "请输入用户账号:" );
        String userNum  =  scanner.nextLine();
        System.out.print( "请输入用户密码:" );
        String password  =  scanner.nextLine();

        Connection connection  =  null  ;
        Statement statement  =  null  ;
        ResultSet resultSet  =  null  ;

          try   {
              //  1.获取数据库的连接:使用自定义工具类 
            connection =  MyJDBCUtils.getConnection();
              //  2.创建一个statement实例 
            statement =  connection.createStatement();
              //  3.创建sql语句:此处需要对sql语句进行拼串操作,略微麻烦 
            String sql="select user,password from user_table where user=‘"+userNum+"‘ and password=‘"+password+"‘" ;
              //  4.执行sql语句 
            resultSet =  statement.executeQuery(sql);
              //  5.对返回结果进行简单处理 
             if   (resultSet.next())
                System.out.println( "登录成功!!!" );
              else  
                System.out.println( "登录失败!!!" );
              //  6.关闭数据库的连接,此时statement和结果集也需要被关闭:使用自定义工具类 
        }  catch   (Exception e) {
            e.printStackTrace();
        }   finally   {
            MyJDBCUtils.closeConnection(connection,statement,resultSet);
        }

    } 

 

返回的结果是正常的:

 

 

当恶意访问数据库时:


 @Test
public void queryDataByStatement() {
/*
简单的登录模块测试 statement 的弊端
*/
Scanner scanner = new Scanner(System. in) ;
System. out.print( " 请输入用户账号: ") ;
String userNum = scanner.nextLine() ;
System. out.print( " 请输入用户密码: ") ;
String password = scanner.nextLine() ;

Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;

try {
//1. 获取数据库的连接 : 使用自定义工具类
connection = MyJDBCUtils. getConnection() ;
//2. 创建一个 statement 实例
statement = connection.createStatement() ;
//3. 创建 sql 语句:此处需要对 sql 语句进行拼串操作,略微麻烦

String sql= "select user ,password from user_table where user = ‘"+userNum+ " ‘and password = ‘"+password+ "‘" ;
//4. 执行 sql 语句
resultSet = statement.executeQuery(sql) ;
//5. 对返回结果进行简单处理
if (resultSet.next())
System. out.println( " 登录成功!!! ") ;
else
System. out.println( " 登录失败!!! ") ;
//6. 关闭数据库的连接 , 此时 statement 和结果集也需要被关闭:使用自定义工具类
} catch (Exception e) {
e.printStackTrace() ;
} finally {
MyJDBCUtils. closeConnection(connection ,statement ,resultSet) ;
}


执行结果为:此时账号和密码明显不对,但是却能登陆成功。

  之所以出现这种情况,是因为statement没有对sql语句进行事先的编译,我们传入什么,它就会向数据库发送什么数据,当账号和密码是上图中的情况时,sql语句实际为下图的情况,这个就叫做sql注入

 

 

 

P reparedStatement使用注意事项

为了解决statement中sql注入的问题,我们需要使用preparedStatement来替换原有的statement。

  preparedStatement是statement的一个子接口,它的好处是可以对sql语句进行预编译,在创建preparedStatement实例时已经知道了自己要执行的sql语句是什么

使用preparedStatement完成对数据库的增删改查操作

1.使用prepareStatement向user表中添加一条数据

 @Test
      public   void   addUserByPre()  {
        Connection connection  =  null  ;
        PreparedStatement preparedStatement  =  null  ;
          try   {
              //  1.获取数据库的连接 
            connection =  MyJDBCUtils.getConnection();
              //  2.创建sql语句:此时数据库中设计的主键id是自增的,我们可以不用主动添加
              //  ?此时代表占位符,表明你将要传递的参数,有几个?代表需要传递几个参数 
            String sql="insert into `user`(name,password,address,phone) values(?,?,?,?)" ;
              //  3.创建preparedStatement对象 
            preparedStatement =  connection.prepareStatement(sql);
              //  4.注入占位符(
              //   两个参数,第一个为需要注入的占位符的下标,第二个参数为具体注入的内容.这里需要注意的是下标是从1开始的而不是0)。 
            preparedStatement.setString(1,"王宝强" );
            preparedStatement.setString( 2,"123456" );
            preparedStatement.setString( 3,"河北省秦皇岛市" );
            preparedStatement.setString( 4,"12345678910" );
              //  5.执行相关操作 
              preparedStatement.execute();
        }   catch   (Exception e) {
            e.printStackTrace();
        }   finally   {
              //  6.关闭相应连接 
             MyJDBCUtils.closeConnection(connection,preparedStatement);
        }

    } 

2. 使用preparedStatement修改user表中的某条数据

  @Test
      public   void   updateUserByPre()  {
        Connection connection  =  null  ;
        PreparedStatement preparedStatement  =  null  ;
          try   {
              //  1.获取数据库连接 
            connection =  MyJDBCUtils.getConnection();
              //  2.创建sql语句 
            String sql="update user set name = ? where id = ?" ;
              //  3.创建preparedStatement对象 
            preparedStatement =  connection.prepareStatement(sql);
              //  4.填充占位符 
            preparedStatement.setString(1,"许三多" );
            preparedStatement.setInt( 2,8 );
              //  5.执行操作 
             int  i =  preparedStatement.executeUpdate();
              if  (i != 0 )
                System.out.println( "修改成功" );
              else  
                System.out.println( "修改失败" );
        }   catch   (Exception e) {
            e.printStackTrace();
        }   finally   {
              //  6.关闭资源 
             MyJDBCUtils.closeConnection(connection,preparedStatement);
        }

    } 

3.使用preparedStatement删除user表中的一条数据

   @Test
      public   void   deleteUserByPre(){
            Connection connection  =  null  ;
            PreparedStatement preparedStatement  =  null  ;
              try   {
                  //  1.获取数据库连接 
                connection =  MyJDBCUtils.getConnection();
                  //  2.创建sql语句 
                String sql="delete from user  where id = ?" ;
                  //  3.创建preparedStatement对象 
                preparedStatement =  connection.prepareStatement(sql);
                  //  4.填充占位符 
                preparedStatement.setInt(1,6 );
                  //  5.执行操作 
                 int  i =  preparedStatement.executeUpdate();
                  if  (i != 0 )
                    System.out.println( "删除成功" );
                  else  
                    System.out.println( "删除失败" );
            }   catch   (Exception e) {
                e.printStackTrace();
            }   finally   {
                  //  6.关闭资源 
                 MyJDBCUtils.closeConnection(connection,preparedStatement);
            }
    } 

4.观察代码可以看出来,增删改三种方法的代码是具有一定的重复性的,唯一的区别无非就是sql语句和占位符的不同,因此我们可以考虑将三种方法封装为同一个方法,调用的时候只需要传递sql语句和占位符即可。代码如下(可自行测试,这里就不再写测试代码了)

  public   static   void   updateDataBase(String sql,Object ...args) {
        Connection connection  =  null  ;
        PreparedStatement preparedStatement  =  null  ;
          try   {
              //  1.获取数据库的连接 
            connection =  getConnection();
              //  2.创建sql语句,此步骤可直接使用传递进来的sql语句
              //  3.创建preparedStatement对象 
            preparedStatement =  connection.prepareStatement(sql);
              //  4.填充占位符 
         /*  
            1.首先要获取占位符的个数,因为可变形参的个数就是占位符的个数,所以只需要获取args的长度即可
            2.填充占位符,使用for循环来做,需要注意的是下标的问题
           */ 
             for  ( int  i = 0; i < args.length; i++ ) {
                preparedStatement.setObject(i +1 ,args[i]);
            }
              //  5.执行操作 
             int  i =  preparedStatement.executeUpdate();
              if  (i != 0 )
                System.out.println( "此次操作成功!" );
              else  
                System.out.println( "此次操作失败!" );
        }   catch   (Exception e) {
            e.printStackTrace();
        }   finally   {
              //  6.关闭资源 
             closeConnection(connection,preparedStatement);
        }

    } 

5.使用preparedStatement查询user表中的一条记录(查询和增删改是不同的,因为查询需要有返回的结果集)

   public  User queryUser(String sql,Object ...args)  {
        Connection connection =  null ;
        PreparedStatement prepareStatement =  null ;
        ResultSet resultSet =  null ;
         try  {
             // 1.获取数据库连接
            connection =  MyJDBCUtils.getConnection();
             // 2.创建sql语句

             // 3.创建preparedStatement对象
            prepareStatement =  connection.prepareStatement(sql);
             // 4.填充占位符
             for ( int i = 0; i < args.length; i++ ) {
                prepareStatement.setObject(i+1 ,args[i]);
            }
             // 5.执行操作
            resultSet =  prepareStatement.executeQuery();
             // 6.将查询出来的数据封装成为一个对象
             // 1.获取一个元数据对象
            ResultSetMetaData metaData =  resultSet.getMetaData();
             // 2.通过元数据对象来获取该条数据中一共有多少列
             int columnCount =  metaData.getColumnCount();
             if  (resultSet.next()){
                 /* 
                resultSet.next()有些类似与迭代器中的hashNext()和next()的结合体
                在迭代器中,hasNext()的作用是判断下一个位置是否为空,next()如果下一个位置不为空,指针下移并且返回当前对象,如果为空,则结束操作
                而resultSet.next()的作用是判断下一个位置是否为空,并且指针下移,返回的是Boolean值
                  */
                  // 3.创建一个对象实体
                User user =  new  User();
                 // 如何将数据封装进一个JavaBean中呢?此时并不知道取出的元素具体是什么类型的!
                 // 在resultSet中提供了一个方法用来获取查询到的元数据(元数据:修饰查询出来数据的数据,可以参考元注解的概念),
                 //  4.使用元数据来获取当前这一条数据的每一列的列名和对应的列值
                 for ( int i = 0; i < columnCount; i++ ) {
                    String columnName = metaData.getColumnName(i + 1 );
                    Object columnValue = resultSet.getObject(i + 1 );

                     // 5.使用反射技术动态的为bean对象中的属性赋值
                    Field declaredField = User. class .getDeclaredField(columnName);
                    declaredField.setAccessible( true );
                    declaredField.set(user,columnValue);
                }
                 // System.out.println(user);
                 return  user;
            }
        }  catch  (Exception e) {
            e.printStackTrace();
        }  finally  {
             // 6.关闭资源
             MyJDBCUtils.closeConnection(connection,prepareStatement,resultSet);
        }
         return  null ;
    }

  这个时候需要提供一个对应的JavaBean实例

6.同样的,我们也可以封装一个函数用来获取不同的表中的单条数据

    public   static  <T> T getBeanByPre(Class<T>  clazz,String sql,Object ...args) {
        Connection connection  =  null  ;
        PreparedStatement preparedStatement  =  null  ;
        ResultSet resultSet  =  null  ;
          try   {
              //  1.获取到数据库的连接 
            connection =  getConnection();
              //  2.创建一个preparedStatement实例 
            preparedStatement =  connection.prepareStatement(sql);
              //  3.填充占位符 
             for  ( int  i = 0; i < args.length; i++ ) {
                preparedStatement.setObject(i +1 ,args[i]);
            }
              //  4.执行操作 
            resultSet =  preparedStatement.executeQuery();
              //  5.获取查询记录的元数据 
            ResultSetMetaData metaData =  resultSet.getMetaData();
              //  6.获取查询记录中的列数 
             int  columnCount =  metaData.getColumnCount();
              if   (resultSet.next()){
                  //  7.使用反射创建一个bean是咧 
                T t =  clazz.newInstance();
                  for  ( int  i = 0; i < columnCount; i++ ) {
                      //  8.获取到每一列的别名 
                    String columnLabel = metaData.getColumnLabel(i + 1 );
                      //  9.获取到每一列的值 
                    Object columnValue = resultSet.getObject(i + 1 );

                      //  10.使用反射为bean中的属性赋值 
                    Field field =  clazz.getDeclaredField(columnLabel);
                    field.setAccessible(  true  );
                    field.set(t,columnValue);
                }
                  return   t;
            }
        }   catch   (Exception e) {
            e.printStackTrace();
        }   finally   {
              //  11.关闭资源 
             closeConnection(connection,preparedStatement,resultSet);
        }
          return   null  ;
    } 

7. 我们也可以封装一个函数用来获取不同的表中的多条数据

 public   static  <T>List<T> getBeanListByPre(Class<T>  clazz, String sql, Object ...args) {
        Connection connection  =  null  ;
        PreparedStatement preparedStatement  =  null  ;
        ResultSet resultSet  =  null  ;
          try   {
              //  1.获取到数据库的连接 
            connection =  getConnection();
              //  2.创建一个preparedStatement实例 
            preparedStatement =  connection.prepareStatement(sql);
              //  3.填充占位符 
             for  ( int  i = 0; i < args.length; i++ ) {
                preparedStatement.setObject(i +1 ,args[i]);
            }
              //  4.执行操作 
            resultSet =  preparedStatement.executeQuery();
              //  5.获取查询记录的元数据 
            ResultSetMetaData metaData =  resultSet.getMetaData();
              //  6.获取查询记录中的列数 
             int  columnCount =  metaData.getColumnCount();
              //  7.创建list集合 
            ArrayList<T> list =  new  ArrayList<> ();
              while   (resultSet.next()){
                  //  7.使用反射创建一个bean实例 
                T t =  clazz.newInstance();
                  for  ( int  i = 0; i < columnCount; i++ ) {
                      //  8.获取到每一列的别名 
                    String columnLabel = metaData.getColumnLabel(i + 1 );
                      //  9.获取到每一列的值 
                    Object columnValue = resultSet.getObject(i + 1 );

                      //  10.使用反射为bean中的属性赋值 
                    Field field =  clazz.getDeclaredField(columnLabel);
                    field.setAccessible(  true  );
                    field.set(t,columnValue);
                }
                list.add(t);
            }
              return   list;
        }   catch   (Exception e) {
            e.printStackTrace();
        }   finally   {
              //  11.关闭资源 
             closeConnection(connection,preparedStatement,resultSet);
        }
          return   null  ;
    } 

  之所以preparedStatement可以解决sql注入问题,是因为它的预编译sql语句的功能,在我们生成preparedStatement对象时,已经使用过了sql语句。在占位符还没有填充之前,它就已经对sql语句进行了解析。对于刚开始的登录测试来说,创建 preparedStatement对象 时,sql语句表示的就是user = ?and password = ?,无论传入什么值,它都会认为是user和password的值。以此避免了sql注入问题

 preparedStatement同时还可以操作Blob类型的数据,可以更高效的实现批量操作

JDBC第二部分—statment和preparedStatement

标签:void   并且   png   内容   and   image   注意   hash   sele   

查看更多关于JDBC第二部分—statment和preparedStatement的详细内容...

  阅读:36次