JDBC基础知识点梳理

  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 ?");

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

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

      3. 执行查询

        resultSet = JdbcUtil.executeQueryPrepared();

      4. 关闭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自己的问题吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值