您的位置:首页 > 数据库

JDBC连接数据库的原理及预编译详解

2017-09-06 15:44 330 查看
一、JDBC基础知识        

        JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC为数据库开发人员提供了一个标准的API,据此可以构建更高级的工具和接口,使数据库开发人员能够用纯 Java API 编写数据库应用程序,并且可跨平台运行,并且不受数据库供应商的限制。

1、跨平台运行:这是继承了Java语言的“一次编译,到处运行”的特点;

2、不受数据库供应商的限制:巧妙在于JDBC设有两种接口,一个是面向应用程序层,其作用是使得开发人员通过SQL调用数据库和处理结果,而不需要考虑数据库的提供商;另一个是驱动程序层,处理与具体驱动程序的交互,JDBC驱动程序可以利用JDBC API创建Java程序和数据源之间的桥梁。应用程序只需要编写一次,便可以移到各种驱动程序上运行。Sun提供了一个驱动管理器,数据库供应商——如MySQL、Oracle,提供的驱动程序满足驱动管理器的要求就可以被识别,就可以正常工作。所以JDBC不受数据库供应商的限制。

        JDBC API可以作为连接Java应用程序与各种关系数据库的纽带,在带来方便的同时也有负面影响,以下是JDBC的优、缺点。优点如下:

操作便捷:JDBC使得开发人员不需要再使用复杂的驱动器调用命令和函数;
可移植性强:JDBC支持不同的关系数据库,所以可以使同一个应用程序支持多个数据库的访问,只要加载相应的驱动程序即可;
通用性好:JDBC-ODBC桥接驱动器将JDBC函数换成ODBC;
面向对象:可以将常用的JDBC数据库连接封装成一个类,在使用的时候直接调用即可。

        缺点如下:

访问数据记录的速度受到一定程度的影响;
更改数据源困难:JDBC可支持多种数据库,各种数据库之间的操作必有不同,这就给更改数据源带来了很大的麻烦

二、JDBC连接数据库的流程及其原理

1、在开发环境中加载指定数据库的驱动程序。例如,接下来的实验中,使用的数据库是MySQL,所以需要去下载MySQL支持JDBC的驱动程序(最新的是:mysql-connector-java-5.1.18-bin.jar);而开发环境是MyEclipse,将下载得到的驱动程序加载进开发环境中(具体示例的时候会讲解如何加载)。

2、在Java程序中加载驱动程序。在Java程序中,可以通过 “Class.forName(“指定数据库的驱动程序”)” 方式来加载添加到开发环境中的驱动程序,例如加载MySQL的数据驱动程序的代码为:  Class.forName(“com.mysql.jdbc.Driver”)

3、创建数据连接对象:通过DriverManager类创建数据库连接对象Connection。DriverManager类作用于Java程序和JDBC驱动程序之间,用于检查所加载的驱动程序是否可以建立连接,然后通过它的getConnection方法,根据数据库的URL、用户名和密码,创建一个JDBC
Connection 对象。如:Connection connection =  DriverManager.geiConnection(“连接数据库的URL", "用户名", "密码”)。其中,URL=协议名+IP地址(域名)+端口+数据库名称;用户名和密码是指登录数据库时所使用的用户名和密码。具体示例创建MySQL的数据库连接代码如下:

              Connection connectMySQL  =  DriverManager.geiConnection(“jdbc:mysql://localhost:3306/myuser","root" ,"root" );

4、创建Statement对象:Statement 类的主要是用于执行静态 SQL 语句并返回它所生成结果的对象。通过Connection 对象的 createStatement()方法可以创建一个Statement对象。例如:Statement statament = connection.createStatement(); 具体示例创建Statement对象代码如下:

             Statement statamentMySQL =connectMySQL.createStatement(); 

5、调用Statement对象的相关方法执行相对应的 SQL 语句:通过execuUpdate()方法用来数据的更新,包括插入和删除等操作,例如向staff表中插入一条数据的代码:

       statement.excuteUpdate( "INSERT INTO staff(name, age, sex,address, depart, worklen,wage)" + " VALUES ('Tom1', 321, 'M', 'china','Personnel','3','3000' ) ") ; 

通过调用Statement对象的executeQuery()方法进行数据的查询,而查询结果会得到 ResulSet对象,ResulSet表示执行查询数据库后返回的数据的集合,ResulSet对象具有可以指向当前数据行的指针。通过该对象的next()方法,使得指针指向下一行,然后将数据以列号或者字段名取出。如果当next()方法返回null,则表示下一行中没有数据存在。使用示例代码如下:

       ResultSet resultSel = statement.executeQuery( "select * from staff" );

6、关闭数据库连接:使用完数据库或者不需要访问数据库时,通过Connection的close() 方法及时关闭数据连接。

什么是预编译语句
预编译语句PreparedStatement 是java.sql中的一个接口,它是Statement的子接口。通过Statement对象执行SQL语句时,需要将SQL语句发送给DBMS数据库管理系统(Database
Management System),由DBMS首先进行编译后再执行。预编译语句和Statement不同,在创建PreparedStatement 对象时就指定了SQL语句,该语句立即发送给DBMS进行编译。当该编译语句被执行时,DBMS直接运行编译后的SQL语句,而不需要像其他SQL语句那样首先将其编译。
 
什么时候使用预编译语句
一般是在需要反复使用一个SQL语句时才使用预编译语句,预编译语句常常放在一个for或者while循环里面使用,通过反复设置参数从而多次使用该SQL语句;为了防止SQL注入漏洞,在某些数据操作中也使用预编译语句。

JDBC使用预编译SQL的好处

1. 执行效率

PreparedStatement可以尽可能的提高访问数据库的性能,数据库在处理SQL语句时都有一个预编译的过程,而预编译对象就是把一些格式固定的SQL编译后,存放在内存池中即数据库缓冲池,当我们再次执行相同的SQL语句时就不需要预编译的过程了,只需DBMS运行SQL语句。所以当你需要执行Statement对象多次的时候,PreparedStatement对象将会大大降低运行时间,特别是的大型的数据库中,它可以有效的也加快了访问数据库的速度。

2. 代码可读性、可维护性
比如向记录用户信息的表中插入记录: user(id, password, name, email, address)。使用Statement的SQ语句如下:

[sql] view
plain copy

String sqlString ="insert into user values('"+  

user.id + "', '"+  

user.password + "', '" +  

user.name + "', '" +  

user.email + "', '" +  

user.address + "')";  

使用PrearedStatement的SQL语句如下:

[sql] view
plain copy

String sqlString ="insert into user(id, password, name, email, address) values(?, ?, ?, ?, ?)";  

PreparedStatement pstmt = connection.PreparedStatement(sqlString);  

pstmt.setString(1, user.id);  

pstmt.setString(2, user.password);  

pstmt.setString(3, user.name);  

pstmt.setString(4, user.email);  

pstmt.setString(5, user.address);  

使用占位符?代替参数,将参数与SQL语句分离出来,这样就可以方便对程序的更改和延续,同样,也可以减少不必要的错误。

3. SQL执行的安全性
SQL注入攻击:是从客户端输入一些非法的特殊字符,从而使服务器端在构造SQL语句时仍能正确构造,从而收集程序和服务器的信息或数据。比如在Web信息系统的登录入口处,要求用户输入用户名和密码,客户端输入后,服务器端根据用户输入的信息来构造SQL语句,在数据库中查询是否存在此用户名以及密码是否正确。假设使用上述例子中的表“user”构造SQL语句的Java程序可能是:

其中userID, userPassword是从用户输入的用户名及密码。如果用户和密码输的都是 '1' or '1'='1',则服务器端生成的SQL语句如下:

这个SQL语句中的where字句没有起到数据筛选的作用,因为只要数据库中有记录,就会返回一个结果不为空的记录集,查询就会通过。上面的例子说明:在Web环境中,有恶意的用户会利用那些设计不完善的、不能正确处理字符串的应用程序。特别是在公共Web站点上,在没有首先通过PreparedStatement对象处理的情况下,所有的用户输入都不应该传递给SQL语句。此外,在用户有机会修改SQL语句的地方,如HTML的隐藏区域或一个查询字符串上,SQL语句都不应该被显示出来。

3. 减少硬解析,节约大量的CPU资源

使用预编译可以利用数据库的软解析降低资源消耗,所谓软解析,就是因为相同文本的SQL语句存在于library cache中,所以本次SQL语句的解析就可以去掉硬解析中的一个或多个步骤(主要为选择执行计划步骤),从而节省大量的资源的耗费。

关于软解析和硬解析,可参见:http://blog.csdn.net/cyzero/article/details/8621171

附:

Statement,PreparedStatement 与 PreparedStatement + 批处理 的用法与比较

java方面

1.使用Statement对象

2.预编译PreparedStatement

3.使用PreparedStatement + 批处理

为了区分出这三者之间的效率,下面的事例执行过程都是在数据库表t1中插入1万条记录,并记录出所需的时间(此时间与电脑硬件有关)

1.使用Statement对象

使用范围:当执行相似SQL(结构相同,具体值不同)语句的次数比较少

优点:语法简单

缺点:采用硬编码效率低,安全性较差。

原理:硬编码,每次执行时相似SQL都会进行编译   

        

事例执行过程:

   public void exec(Connection conn){

           try {

                     Long beginTime = System.currentTimeMillis();

                           conn.setAutoCommit(false);//设置手动提交

                    Statement st = conn.createStatement();

                            for(int i=0;i<10000;i++){

                       String sql="insert into t1(id) values ("+i+")";

                       st.executeUpdate(sql); 

                    }

                           Long endTime = System.currentTimeMillis();

                   System.out.println("Statement用时:"+(endTime-beginTime)/1000+"秒");//计算时间

                   st.close();

                  conn.close();

                } catch (SQLException e) {               

                  e.printStackTrace();

          } 

   }

 

执行时间:Statement用时:31秒

 

2.预编译PreparedStatement

使用范围:当执行相似sql语句的次数比较多(例如用户登陆,对表频繁操作..)语句一样,只是具体的值不一样,被称为动态SQL

优点:语句只编译一次,减少编译次数。提高了安全性(阻止了SQL注入)

缺点: 执行非相似SQL语句时,速度较慢。

原理:相似SQL只编译一次,减少编译次数

名词解释:

SQL注入:select * from user where username="张三" and password="123" or 1=1;

前面这条语句红色部分就是利用sql注入,使得这条词句使终都会返回一条记录,从而降低了安全性。

事例执行过程:

      public void exec2(Connection conn){

         try {

                   Long beginTime = System.currentTimeMillis();

                    conn.setAutoCommit(false);//手动提交

                    PreparedStatement pst = conn.prepareStatement("insert into t1(id) values (?)");

                    for(int i=0;i<10000;i++){

                       pst.setInt(1, i);

                       pst.execute();   

                    }

                      conn.commit();

                      Long endTime = System.currentTimeMillis();

                      System.out.println("Pst用时:"+(endTime-beginTime)+"秒");//计算时间

                      pst.close();

                      conn.close();

          } catch (SQLException e) {                 

                     e.printStackTrace();

          }

    }

执行时间:Pst用时:14秒

 

3.使用PreparedStatement + 批处理

使用范围:一次需要更新数据库表多条记录

优点:减少和SQL引擎交互的次数,再次提高效率,相似语句只编译一次,减少编译次数。提高了安全性(阻止了SQL注入)

缺点:

原理:

批处理: 减少和SQL引擎交互的次数,一次传递给SQL引擎多条SQL。

名词解释:

PL/SQL引擎:在oracle中执行pl/sql代码的引擎,在执行中发现标准的sql会交给sql引擎进行处理。

SQL引擎:执行标准sql的引擎。

事例执行过程:

public void exec3(Connection conn){

                     try {

                      conn.setAutoCommit(false);

                      Long beginTime = System.currentTimeMillis();

                      PreparedStatement pst = conn.prepareStatement("insert into t1(id) values (?)");

                     

                      for(int i=1;i<=10000;i++){   

                       pst.setInt(1, i);

                       pst.addBatch();//加入批处理,进行打包

                       if(i%1000==0){//可以设置不同的大小;如50,100,500,1000等等

                        pst.executeBatch();

                        conn.commit();

                        pst.clearBatch();

                       }

                      }

                     pst.executeBatch();

                      Long endTime = System.currentTimeMillis();

                      System.out.println("pst+batch用时:"+(endTime-beginTime)+"毫秒");

                      pst.close();

                      conn.close();

                     } catch (SQLException e) {

                      // TODO Auto-generated catch block

                      e.printStackTrace();

                     }

 }

执行时间:pst+batch用时:485毫秒

 

数据库方面



1 静态SQL

使用范围:

优点:每次执行相似sql都会进行编译。

缺点:效率低,占用破费cpu资源,耗费SGA 资源。

原理:(检查SQL 语句在SGA 中的共享池中是否存在, 如果不存在,则编译、解析后执行:硬解析,

     如果已存在,则取出编译结果执行:软解析)

事例执行过程:

DECLARE

 time1 NUMBER;

 time2 NUMBER;

BEGIN

  time1 := dbms_utility.get_time;

  EXECUTE IMMEDIATE 'truncate table t_demo';

  FOR i IN 1..10000

  LOOP

     EXECUTE IMMEDIATE

       'insert into t_demo(u_id) values('||i||')'; 

  END LOOP;

  time2 := dbms_utility.get_time;

  dbms_output.put_line((time2-time1)/100||'秒');

END

执行时间:pst+batch用时:20.93秒

2 动态SQL

使用范围:

优点:语句只编译一次,减少编译次数,提高效率。

缺点:

原理:减少编译次数(检查SQL 语句在SGA 中的共享池中是否存在, 如果不存在,则编译、解析后执行:硬解析,

     如果已存在,则取出编译结果执行:软解析)

事例执行过程:

DECLARE

 time1 NUMBER;

 time2 NUMBER;

BEGIN

  time1 := dbms_utility.get_time;

  EXECUTE IMMEDIATE 'truncate table t_demo';

  FOR i IN 1..10000

  LOOP

     EXECUTE IMMEDIATE

       'insert into t1(u_id) values(:x)' USING i; 

  END LOOP;

  time2 := dbms_utility.get_time;

  dbms_output.put_line((time2-time1)/100||'秒');

END;        

执行时间:pst+batch用时:10.55秒

 

3 利用forall进行批处理<相似java中的批处理>

使用范围:当执行相似sql语句的次数比较多(例如用户登陆,对表频繁操作..)FORALL只能运行一条sql语句。

优点:减少与SQL引擎转替次数,提交效率。

缺点:循环时从开始到结束,一次性读完,不能取其中的某些循环进行操作。

原理:使用ForAll语句可以让PL/SQL引擎一次将多条SQL转发给SQL引擎,从而减少转发次数,提高系统性能。

事例执行过程:

CREATE  OR  REPLACE  PROCEDURE  p_test

AS

   --定义新的类型(不是定义变量!)

   TYPE id_table_type IS TABLE OF NUMBER(6) INDEX BY BINARY_INTEGER; 

   --用新的类型来定义变量

   id_table id_table_type;

   time1 NUMBER;

 time2 NUMBER;

BEGIN

    FOR i IN 1..10000

    LOOP

       --往两个数组中存放数据

       id_table(i) := i;  

    END LOOP;

   --★:一次性向SQL引擎传递了多条数据而不是一条

    time1 := dbms_utility.get_time;

    FORALL i IN 1..id_table.COUNT

        INSERT INTO t_demo(u_id) VALUES(id_table(i));

  time2 := dbms_utility.get_time;

  dbms_output.put_line((time2-time1)*10||'毫秒');

end p_test;

 

call p_test();

执行时间:pst+batch用时:170毫秒

 

4 利用BULK COLLECT

使用范围:处理数据量比较少时。

优点:一次读取多行数据,提高效率。

缺点: 需要较大的内存开销

原理:将多个行引入一个或多个集合中。

事例执行过程:

DECLARE

   TYPE books_aat

 

      IS TABLE OF book%ROWTYPE

      INDEX BY PLS_INTEGER;

   book books_aat;

BEGIN

   SELECT *

     BULK COLLECT INTO book

     FROM books

    WHERE title LIKE '%PL/SQL%';

   ...

END;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: