基础知识整理之JDBC基础知识点梳理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Frank_Adam/article/details/79520016
  1. JDBC(Java Database Connectivity)

  2. JDBC API:JDBC API是Java语言中用于连接各种数据库的API。

  3. 主要的包和相应的接口、类:

    java.sql.*: class DriverManager; interface Connection, Statement, ResultSet, PreparedStatement, CallableStatement

    javax.sql.*: interface DataSource

  4. 使用JDBC的6个关键步骤(面试可能让手写!!)

    1. 加载驱动

      Class.forName("com.mysql.jdbc.Driver");

    2. 建立连接

      connection = DriverManager.getConnection(url, user, passwd);

    3. 创建Statement

      statement = connection.createStatement();

    4. 执行查询

      String sql = "show databases";
      resultSet = statement.executeQuery(sql);
    5. 处理结果

      while (resultSet.next()) {
          String database = resultSet.getString("Database");
          System.out.println("Query: " + database);
      }
    6. 关闭连接(放到finally子句)

      finally {
          //6.关闭连接
          try {
              resultSet.close();
              statement.close();
              connection.close();
          } catch (NullPointerException e) {
              System.out.println("数据库连接未建立或查询操作有误!");
          } catch (SQLException e) {
              e.printStackTrace();
          }
      }

      (完整代码请转至我的Github主页,X-ration/Practice_JavaJDBC,第一次Commit下的Main.java文件,或者点这里下载源代码)

      • 数据库操作也是I/O过程。

      • PreparedStatement

      • 优点:防止SQL注入,有相应的setInt()等方法。

      • 基本使用:

        1. 创建PreparedStatement:

        preparedStatement = connection.prepareStatement("select ename from emp where ename like ?");

        1. 设置参数值(注意,这里的columnIndex是从1开始的

        preparedStatement.setString(1,"%A%");

        1. 执行查询

        resultSet = JdbcUtil.executeQueryPrepared();

        1. 关闭PreparedStatement:调用close方法。
      • 事务控制

      • 设置是否自动提交:Connection.setAutoCommit(Boolean)

      • 手动提交:Connection.commit()
      • 手动回滚:Connection.rollback()

      • ResultSet滚动结果集

      • 常用API():

      • next()

      • previous()
      • absolute(int row)

      • 创建Statement时可以使用带参的方法createStatement(int resultSetType, int resultSetConcurrency),例如:

      Statement st = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATE);

      第一个参数可选值:

      • ResultSet.TYPE_FORWARD_ONLY, 指示光标只能往前移动;
      • ResultSet.TYPE_SCROLL_INSENSITIVE, 指示光标可滚动但通常不受底层数据变动的影响;
      • ResultSet.TYPE_SCROLL_SENSITIVE,指示光标可滚动并且可实时得到底层数据变化后的最新数据。

      第二个参数可选值:

      • ResultSet.CONCUR_READ_ONLY,指示不可以更新的ResultSet并发模式
      • ResultSet.CONCUR_UPDATABLE, 指示可以更新的ResultSet并发模式

      • 获取元数据

      • ParameterMetaData:参数的元数据

      ParameterMetaData parameterMetaData = preparedStatement.getParameterMetaData();
      System.out.println("ParameterMetaData测试,参数个数:" + parameterMetaData.getParameterCount());
      • ResultSetMetaData:结果集的元数据
      ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
      System.out.println("ResultSetMetaData测试,结果集列数:" + resultSetMetaData.getColumnCount());
      • DatabaseMetaData:数据库的元数据
      DatabaseMetaData databaseMetaData = connection.getMetaData();
      System.out.println("DatabaseMetaData测试,用户名:" + databaseMetaData.getUserName());
      • Statement批处理操作

      • 批处理操作的好处显而易见。实例测试:使用JDBC向Oracle数据表中插入600条数据,分别使用普通的Statement逐条插入和使用批处理插入。实测结果:逐条插入耗时4563ms,而批处理插入耗时20ms。

      And:批处理插入8400条记录,共耗时116ms;批处理插入90000条数据,共耗时1847ms。(太快了吧也!!简直不在一个量级上!!)

      • 添加批处理语句:

      Statement.addBatch(String sql);

      PreparedStatement.addBatch();(每次将参数值改变后执行,相当于批量执行PreparedStatement)

      PreparedStatement.addBatch(String sql);(没有测试过)

      • 执行批处理语句:

      Statement.executeBatch();

      PreparedStatement.executeBatch();

      这两个方法的返回值是int[]类型,其中数组中的每个值代表对应语句影响到的行数。

      正好在这里记录一个小问题。看到有博客上说调用executeBatch()方法执行成功后会自动调用clearBatch()方法,我感觉这种说法是不合适的,要想自己验证当然可以去找相应的源代码实现。查看相关源代码发现,java.sql.Statement是一个接口,只是规定了Statement有executeBatch()方法,所以我猜想,真正的实现要依赖于具体的Driver,那就看你用的是Mysql的Driver还是Oracle的Driver或者是其他的Driver了。我的使用情境中Driver用的是OracleDriver,查看OracleStatement的源代码发现有这么一句比较关键的:

      } finally {
         this.clearBatchItems();  
         ...
      }

      其中clearBatchItems()方法的实现为this.m_batchItems.removeAllElements();,而这里的m_batchItems(注:Vector类型)恰好是OracleStatement内部对批处理指令存储的实现。那么executeBatch()方法会不会执行到这一句呢?看上面的关键代码,这一句被写在finally里面,finally是try-catch-finally的最后一步,即不管发生什么异常都会执行的意思,所以这一句八成是要调的,但也不能这么绝对,还得先分析下代码结构。经过我的分析发现executeBatch()的代码结构如下:

      //some useless code(这里的没用指的是对分析过程没用,即不起决定性作用)
      synchronized(this.connnection){
         //some useless code
         if(this.getBatchSize()<=0){
             return new int[0];
         } else {
             //some useless code
             try {
                 //some useless code
             } catch (SomeException e){
                 //some useless code
             } finally {
                 this.clearBatchItems();
                 //some useless code
             }
         }
      }

      这里我唯一感到不明白的是synchronized关键字的含义,所以我认为在Oracle Driver实现中executeBatch()方法最后调用clearBatch()方法是正确的,判断正确的把握是95%。(所以如果有大佬研究的比较深的,还是请再指点一下啊~)

      • 清理批处理语句:

      Statement.clearBatch();

      PreparedStatement.clearBatch();

      贴一段Statement.clearBatch()方法的官方说明:(大意是:如果数据库连接过程中出现错误(error),这个方法(clearBatch)会在关闭Statement时调用,或者当前驱动根本不支持批处理操作。)

      /**
      * ... if a database access error occurs,
      *  this method is called on a closed Statement or the
      *  driver does not support batch updates
      */
      • 使用JDBC时的几点注意

      • 地址:不同的Driver要求的URL地址不同,通常我们用的mysql地址示例:jdbc:mysql://localhost:3306/some_database?useUnicode=true&characterEncoding=UTF8

      Oracle地址示例:jdbc:oracle:thin:@localhost:1521:orcl

      • 可以通过读取Resource Bundle的方式让程序从配置文件中读取数据库url用户名等信息。

      Idea中的步骤是:

      1. 创建一个resources文件夹,并在Project Structure中设置该文件夹为resource dir

      2. 在resources文件夹中创建Resource Bundle(后缀.properties),例如jdbc.properties

      3. 在jdbc.properties中以每行一个key=value的形式设定数据库相关配置,例如

        driver=oracle.jdbc.driver.OracleDriver

      4. 在程序中使用如下语句获取相应key的值,如获取driver:

        String driver = ResourceBundle.getBundle("jdbc").getString("driver");

        • 使用PreparedStatement时,通常设定的是”value”。例如下面这个例子执行是有问题的:
      PreparedStatement preparedStatement = JdbcUtil.createPreparedStatement("select * from ?");
      preparedStatement.setString(1,"user_tables");
      resultSet = JdbcUtil.executeQueryPrepared();

      这里的代码是在对Oracle数据库进行操作的,运行时报错java.sql.SQLSyntaxErrorException: ORA-00903: invalid table name。贴一段来自StackOverflow上大神Jon Skeet的解答:

      I believe that PreparedStatement parameters are only for values - not for parts of the SQL query such as tables. There may be some databases which support what you’re trying to achieve, but I don’t believe Oracle is one of them. You’ll need to include the table name directly - and cautiously, of course.

      这启示我们,至少在Oracle数据库中,不能把表名当做查询参数传给PreparedStatement

      • 对Oracle数据库进行executeBatch()操作返回-2

      执行executeBatch()操作,返回值是一个整型数组,数组中的每个数字对应一条命令影响到的行数。但在Oracle的驱动中没有实现该功能,即提交成功后不能返回影响行数。

      在JDBC的规范中Statement.SUCCESS_NO_INFO(-2)代表:执行成功,受影响行数不确定

      • 再记录一个关于使用Oracle Driver的weird problem!!

      获取PreparedStatement的元数据ParameterMetaData时,不能调用getParameterClassName(int num)这个方法,在我调用时报错”Unsupported feature”,当时就在心里骂着这破Oracle咋这么多限制(虽然我知道还是要用它,哎)。我的测试场景是PreparedStatement中含有一个待定参数,调用这个方法试了传0或1结果都是报这个错,应该就是Oracle Driver自己的问题吧。

猜你喜欢

转载自blog.csdn.net/Frank_Adam/article/details/79520016