JDBC彻底总结

JDBC基础知识

1、采用JDBC访问数据库的基本步骤:
    A.载入JDBC驱动程序
    B.定义链接URL
    C.创建链接
    D.建立Statement对象
    E.执行查询或更新
    F.结果处理
    G.关闭链接

2、载入JDBC驱动程序:
1.为了使代码尽量地灵活,咱们要避免对类名的引用进行硬编码(hard-coding),所以咱们能够采用从Properties文件中载入驱动程序的方法,也可使用在服务器中配置数据源(DataSource)的方法来避免在代码中硬编码
    
2.在开发过程当中要保证CLASSPATH设定中包括驱动程序JAR文件所在的路径。在WEB服务
器上部署时要将JAR文件放在Web应用的WEB-INF/lib目录下。若是多个Web应用使用相同的数据库驱动程序能够将JAR文件放置在服务器使用的公共目录<%CATALINA_HOME%>/common/lib中

3、定义链接URL:
       载入JDBC驱动程序以后,必须指定数据库服务器位置。指向数据库的URL所使用的协议是:
    jdbc:子协议,而且载入服务器的主机名、端口、数据库名(或引用)。如:Oracle 的链接URL: 
    jdbc:oracle:thin:@192.168.0.71:1521:UMV2
    jdbc:oracle:采用Oracle驱动程序
    thin:指链接服务器所采用的模式
    @192.168.0.71:服务器的地址
    1521:服务器的监听端口
    UMV2:数据库名

4、创建链接:
    1.一个数据库链接(Connection)能够经过其自身的getMetaData()来获取它的自身信息
    2.默认状况下一个数据库的链接是自动提交模式的(auto-commit),也就是说每当一个SQL语句
被执行后其改变结果都会被自动提交,若是auto-commit模式被关闭,那么方法commit()必须被显式调用以提交改变结果,不然的话全部对数据库操做的结果都不会被保存

5、建立Statement对象:
在 同一时间下,每一个Statement对象只能打开一个ResultSet对象。因此,假若有两个一样结果的结果集在交叉访问,那么这两个结果集一定为两个 不一样的Statement对象所建立。若是在打开一个新的结果集的时候存在一个已经打开的结果集,则这个已经存在的结果集会被隐式的关闭    

6、执行查询或更新:
    在Statement对象中能够执行以下的操做:
    A.查询操做:executeQuery(SQL语句)  B.维护操做:executeUpdate(SQL语句)
    C.批处理操做:executeBath()

7、结果处理:
    1.ResultSet中行的第一列索引为1,而非0,访问ResultSet中的数据时要使用列名,而非索引
     但要注意使用列名做为查询条件是大小写敏感的。

    2.JDBC1.0中,咱们只能在ResultSet中向前移动;在JDBC2.0中,咱们能够在ResultSet中向
     下(next)或向上(previous)移动,一样也能够移到特定的行(relative,absolute)

3.默认状况下ResultSet是不可更新的,且只能向前移动。下面的代码显示了如何建立一个可滚动的、对更新敏感的ResultSet

       Statement stmt = con.createStatement(
                                      ResultSet.TYPE_SCROLL_INSENSITIVE,
                                      ResultSet.CONCUR_UPDATABLE);
       ResultSet rs = stmt.executeQuery("SELECT a, b FROM TABLE2");
       // rs will be scrollable, will not show changes made by others,
       // and will be updatable
      
    4.ResultSet和ResultSetMetaData没有直接提供方法返回查询所返回的行数。然而,在JDBC
     2.0中,能够经过调用last()方法将游标定位到ResultSet的最后一行,而后调用getRow()方
     法获取当前的行号。在JDBC1.0中,肯定行数的唯一方式是重复调用ResultSet的next()方法,
     直到它返回false为至

8、关闭链接:
    在关闭数据库链接时应该以ResultSet、Statement、Connection的顺序进行

JDBC-PreparedStatement(预备语句)

1、PreparedStatement(预备语句)的建立:
首 先按照标准的格式建立参数化语句,在实际使用以前发送参数到数据库进行编译。用问号表示语句中应该为具体的值所替换的位置。每次使用预备语句时,只须要使 用相应的setXxx调用,替换语句中标记出来的参数。而后就能够和常规的语句同样,使用executeQuery或 execute/executeUpdate修改表中的数据。例如:

Connection connection = DriverManager.getConnection (url,username,password);
// 建立带问号的参数化语句
String template = " UPDATE music SET price=? WHERE id=? ";
PreparedStatement statement = connection.prepareStatement (template);

float newPrices[] = getNewPrices();
int recordingIDs = getIDs();
for(int i=0; i<recordingIDs.length;i++){
   // 用setXxx代替?
   statement.setFloat(1,newPrices[i]);
   statement.setInt(2,recordingIDs[i]);
   // 执行预备语句
statement.execute();}

2、使用PreparedStatement的好处:
1.依赖于服务器对预编译查询的支持,以及驱动程序处理原始查询的效率,预备语句在性能上的优点可能有很大的不一样。
2.安全是预备语句的另一个特色,咱们推荐在经过HTML表单接受用户输入,而后对数据库进行更新时,必定要使用预备语句或存储过程。
3.预备语句还可以正确地处理嵌入在字符串中的引号以及处理非字符数据(好比向数据库发送序列化后的对象)    

JDBC-CallableStatement(可调用语句)

1、使用CallableStatement(可调用语句)的优缺点:
1.优势:语法错误能够在编译时找出来,而非在运行期间;数据库存储过程的运行可能比常规的
SQL查询快得多;程序员只需知道输入和输出参数,不需了解表的结构。另外,因为数据库语言可以访问数据库本地的一下儿功能(序列,触发器,多重游标),所以用它来编写存储过程可能要比使用Java编程语言要简易一些。
2. 缺点:存储过程的商业逻辑在数据库服务器上运行,而非客户机或Web服务器。而行业的发展趋势是尽量多地将商业逻辑移出数据库,将它们放在 JavaBean组件(或者在大型的系统中,EnterPrise JavaBean组件)中,在Web构架上采用这种方式的主要动机是:数据库访问和网 络I/O经常是性能的瓶颈。

2、使用CallableStatement在JAVA中调用数据库存储过程:

1.定义对数据库过程的调用
    A.无参数过程:{ call procedure_name}
     B. 仅有输入参数的过程:{call procedure_name(?,?...)}
    C.有一个输出参数的过程:{? Call procedure_name}
    D.既有输入参数又有输出参数的过程{?=call procedure_name(?,?...)}
  在过程的4种形式中要注意过程可能返回多个输出参数,而且参数的索引值从输出参数开始。所以前面最后例子中,第一个输入参数的索引值是2而不是1。
2.为过程准备CallableStatement
  String procedure = “{ ? = call procedure_name(?,?) }”;
  CallableStatement statement = connection.prepareCall(procedure);
3.提供输入参数的值
  在执行存储过程以前,咱们须要调用与所要设置的项以及参数的类型相对应的setXxx,替换标记出来的输入参数
  Statement.setString(2,”name”);
4.注册输出参数的类型
    咱们必须使用registerOutParameter注册每一个输出参数的JDBC类型
  Statement.registerOutParameter(n,type);
5.执行这个存储过程
  Statement.execute();
6.访问返回的输出参数
  能够经过调用getXxx访问每一个对应的输出参数



例如:
Connection connection = DriverManager.getConnection(url,username,password);
String procedure = “{ ? = call myProc(?,?)}”;
CallableStatement statement = connection.prepareCall(procedure);
statement.setString(2,×××);
statement.setFloat(3,×××);
statement.registerOutParameter(1,Types.INTEGER);
statement.execute();
int row = statement.getInt(1);

CallableStatement 中定义的全部方法都用于处理 OUT 参数或 INOUT 参数的输出部分:注册 OUT 参数的 JDBC 类型(通常 SQL 类型)、从这些参数中检索结果,或者检查所返回的值是否为 JDBC NULL。

  一、建立 CallableStatement 对象

  CallableStatement 对象是用 Connection 方法 prepareCall 建立的。下例建立 CallableStatement 的实例,其中含有对已储存过程 getTestData 调用。该过程有两个变量,但不含结果参数:

java

CallableStatement cstmt = con.prepareCall("{call getTestData(?, ?)}");


  其中?占位符为IN、OUT仍是INOUT参数,取决于已储存过程getTestData。

  二、IN和OUT参数

  将IN参数传给 CallableStatement 对象是经过 setXXX 方法完成的。该方法继承自 PreparedStatement。所传入参数的类型决定了所用的setXXX方法(例如,用 setFloat 来传入 float 值等)。

   若是已储存过程返回 OUT 参数,则在执行 CallableStatement 对象之前必须先注册每一个 OUT 参数的 JDBC 类型(这是必需的,由于某些 DBMS 要求 JDBC 类型)。注册 JDBC 类型是用 registerOutParameter 方法来完成的。语句执行完后,CallableStatement 的 getXXX 方法将取回参数值。正确的 getXXX 方法是为各参数所注册的 JDBC 类型所对应的 Java 类型。换言之, registerOutParameter 使用的是 JDBC 类型(所以它与数据库返回的 JDBC 类型匹配),而 getXXX 将之转换为 Java 类型。

  做为示例,下述代码先注册 OUT 参数,执行由 cstmt 所调用的已储存过程,而后检索在 OUT 参数中返回的值。方法 getByte 从第一个 OUT 参数中取出一个 Java 字节,而 getBigDecimal 从第二个 OUT 参数中取出一个 BigDecimal 对象(小数点后面带三位数):

mysql

CallableStatement cstmt = con.prepareCall("{call getTestData(?, ?)}");
cstmt.registerOutParameter(1, java.sql.Types.TINYINT);
cstmt.registerOutParameter(2, java.sql.Types.DECIMAL, 3);
cstmt.executeQuery();
byte x = cstmt.getByte(1);
java.math.BigDecimal n = cstmt.getBigDecimal(2, 3);


  CallableStatement 与 ResultSet 不一样,它不提供用增量方式检索大 OUT 值的特殊机制。
  三、INOUT参数

   既支持输入又接受输出的参数(INOUT 参数)除了调用 registerOutParameter 方法外,还要求调用适当的 setXXX 方法(该方法是从 PreparedStatement 继承来的)。setXXX 方法将参数值设置为输入参数,而 registerOutParameter 方法将它的 JDBC 类型注册为输出参数。setXXX 方法提供一个 Java 值,而驱动程序先把这个值转换为 JDBC 值,而后将它送到数据库中。这种 IN 值的 JDBC 类型和提供给 registerOutParameter 方法的 JDBC 类型应该相同。而后,要检索输出值,就要用对应的 getXXX 方法。例如,Java 类型为byte 的参数应该使用方法 setByte 来赋输入值。应该给registerOutParameter 提供类型为 TINYINT 的 JDBC 类型,同时应使用 getByte 来检索输出值。

  下例假设有一个已储存过程 reviseTotal,其惟一参数是 INOUT 参数。方法setByte 把此参数设为 25,驱动程序将把它做为 JDBC TINYINT 类型送到数据库中。接着,registerOutParameter 将该参数注册为 JDBC TINYINT。执行完该已储存过程后,将返回一个新的 JDBC TINYINT 值。方法 getByte 将把这个新值做为 Java byte 类型检索。

程序员

CallableStatement cstmt = con.prepareCall("{call reviseTotal(?)}");
cstmt.setByte(1, 25);
cstmt.registerOutParameter(1, java.sql.Types.TINYINT);
cstmt.executeUpdate();
byte x = cstmt.getByte(1);


  四、先检索结果,再检索 OUT 参数

  由 于某些 DBMS 的限制,为了实现最大的可移植性,建议先检索由执行CallableStatement 对象所产生的结果,而后再用 CallableStatement.getXXX 方法来检索 OUT 参数。若是 CallableStatement 对象返回多个 ResultSet 对象(经过调用 execute 方法),在检索 OUT 参数前应先检索全部的结果。这种状况下,为确保对全部的结果都进行了访问,必须对 Statement 方法 getResultSet、getUpdateCount 和getMoreResults 进行调用,直到再也不有结果为止。

  检索完全部的结果后,就可用 CallableStatement.getXXX 方法来检索 OUT 参数中的值。

  五、检索做为OUT参数的NULL值

   返回到 OUT 参数中的值可能会是JDBC NULL。当出现这种情形时,将对 JDBC NULL 值进行转换以使 getXXX 方法所返回的值为 null、0 或 false,这取决于getXXX 方法类型。对于 ResultSet 对象,要知道0或false是否源于JDBCNULL的惟一方法,是用方法wasNull进行检测。若是 getXXX 方法读取的最后一个值是 JDBC NULL,则该方法返回 true,不然返回 flase。
sql

 


JDBC-Transation(事务处理)

1、Transation(事务处理)的概念:
在 更新数据库时,默认状况下,更改是永久性写入到数据库。然而这种默认行为能够经过编写程序来关闭。在自动交付关闭的状况下,若是在更新时发生问题,则对数 据库的每一个更改都可以取消(或者说回退到最初的值)。若是更新成功,那么以后能够将这些更改永久性提交给数据库。这种方式也称为事务管理。
咱们须要确保,要么全部的操做都发生,要么全部的操做都不发生。这就是事务管理的原则。

2、在JAVA中使用Transation(事务管理)保证数据库的完整性:
我 们使用try-catch-finally块来正确地应对事务管理,首先,记录自动提交的当前状态。而后,在try块中,调用 setAutoCommit(false)并执行一系列的查询或更新。若是发生故障,则在catch块中调用rollback;若是事务成功,则在try 块的结尾调用commit。无论哪一种方式,都在finally块中重置自动提交的状态。例如:

Connection connection = DriverManager.getConnection(url,username,password);
boolean autoCommit = connection.getAutoCommit();
Statement statement;
try{
    connection.setAutoCommit(false);    // 关闭数据库的自动提交
    statement = connection.createStatement();
statement.execute(…);
statement.execute(..);

connection.commit();                // 若是全部语句执行成功则提交事务
}
catch(SQLException sqle){
    connection.rollback();                // 若是有异常发生则回滚全部的事务
}
finally{
    if(statement!=null){statement.close();}
connection.setAutoCommit(autoCommit);    // 重置自动提交的状态
}
上 面的代码中,从DriverManager获取链接的语句在try/catch块以外。这样除非成功获取链接,不然不会调用rollback。若是把获取 链接的语句放在try/catch快以内,一旦在链接成功后发生异常,因为rollback的做用会把已经创建的链接断开。可是 getConnection方法也会抛出SQLException异常这个异常要么被外围的方法从新抛出,要么在单独的try/catch块内捕获。数据库

 
JDBC的经常使用API

1、Connection接口:
    1.createStatement():建立数据库链接
    2.prepareStatement(String sql):建立预处理语句
    3.prepareCall(String sql):建立可调用语句

    4.getAutoCommit():获取自动提交的模式
    5.setAutoCommit():设置自动提交的模式
    
    6.commit():提交所执行的SQL语句
    7.rollback():回滚所执行的SQL语句

    8.getMetaData():获取一个DatabaseMetaData对象,该对象包含了有关数据库的基本信息

    9.close():关闭数据库链接
    10.isClose():判断数据库链接是否超时或被显示关闭

2、Statement接口:
    1.execute(String sql):执行SQL语句,若是返回值是结果集则为true,不然为false
    2.executeQuery(String sql):执行SQL语句,返回值为ResultSet
    3.executeUpdate(String sql):执行SQL语句,返回值为所影响的行数
    
    4.addBatch(String sql):向当前Statement对象的命令列表中添加新的批处理SQL语句
    5.clearBatch():清空当前Statement对象的命令列表
    6.executeBatch():执行当前Statement对象的批处理语句,返回值为每一个语句所影响的函数数组

    7.getConnection():返回建立了该Statement对象的Connection对象

    8.getQueryTimeout():获取等待处理结果的时间
    9.setQueryTimeout():设置等待处理结果的时间
          
3、ResultSet接口:    
    1.first()/beforeFirst():将游标移动到ResultSet中第一条记录(的前面)
    2.last()/afterLast():将游标移动到ResultSet中最后一条记录(的后面)

    3.absolute(int column):将游标移动到相对于第一行的指定行,负数则为相对于最后一条记录
    4.relative(int rows):将游标移动到相对于当前行的第几行,正为向下,负为向上

    5.next():将游标下移一行
    6.previous():将游标上移一行

    7.insertRow():向当前ResultSet和数据库中被插入行处插入一条记录
    8.deleteRow():将当前ResultSet中的当前行和数据库中对应的记录删除
    9.updateRow():用当前ResultSet中已更新的记录更新数据库中对应的记录
    10.cancelUpdate():取消当前对ResultSet和数据库中所作的操做

    11.findColumn(String columnName):返回当前ResultSet中与指定列名对应的索引

    12.getRow():返回ResultSet中的当前行号

    13.refreshRow():更新当前ResultSet中的全部记录

    14.getMetaData():返回描述ResultSet的ResultSetMetaData对象

    15.isAfterLast(): 是否到告终尾
    16.isBeforeFirst(): 是否到了开头
    17.isFirst():是否第一条记录   
    18.isLast(): 是否最后一条记录

    19.wasNull():检查列值是否为NULL值,若是列的类型为基本类型,且数据库中的值为0,那么
这项检查就很重要。因为数据库NULL也返回0,因此0值和数据库的NULL不能区分。若是列的类型为对象,能够简单地将返回值与null比较
    
20.close():关闭当前ResultSet

4、ResultSetMetaData接口:
    1.getColumnCount():返回ResultSet中列的数目
    2.getColumnName():返回列在数据库中的名称
    3.getColumnType():返回列的SQL类型

    4.isReadOnly():表示该数据项是否为只读值
    5.isNullable():表示该列是否能够存储NULL



基于JDBC的数据库链接池技术研究与应用
  
Java应用程序访问数据库的基本原理 

在 Java语言中,JDBC(Java DataBase Connection)是应用程序与数据库沟通的桥梁, 即Java语言经过JDBC技术访问数 据库。JDBC是一种“开放”的方案,它为数据库应用开发人员、数据库前台工具开发人员提供了一种标准的应用程序设计接口,使开发人员能够用纯Java语 言编写完整的数据库应用程序。JDBC提供两种API,分别是面向开发人员的API和面向底层的JDBC驱动程序API,底层主要经过直接的JDBC驱动 和JDBC-ODBC桥驱动实现与数据库的链接。 

通常来讲,Java应用程序访问数据库的过程(如图1所示)是: 
 ①装载数据库驱动程序; 
 ②经过JDBC创建数据库链接; 
 ③访问数据库,执行SQL语句; 
 ④断开数据库链接。 
 
JDBC 做为一种数据库访问技术,具备简单易用的优势。但使用这种模式进行Web应用程序开发,存在不少问题:首先,每一次Web请求都要创建一次数据库链接。建 立链接是一个费时的活动,每次都得花费0.05s~1s的时间,并且系统还要分配内存资源。这个时间对于一次或几回数据库操做,或许感受不出系统有多大的 开销。但是对于如今的Web应用,尤为是大型电子商务网站,同时有几百人甚至几千人在线是很正常的事。在这种状况下,频繁的进行数据库链接操做势必占用很 多的系统资源,网站的响应速度一定降低,严重的甚至会形成服务器的崩溃。不是危言耸听,这就是制约某些电子商务网站发展的技术瓶颈问题。其次,对于每一次 数据库链接,使用完后都得断开。不然,若是程序出现异常而未能关闭,将会致使数据库系统中的内存泄漏,最终将不得不重启数据库。还有,这种开发不能控制被 建立的链接对象数,系统资源会被毫无顾及的分配出去,如链接过多,也可能致使内存泄漏,服务器崩溃。 

数据库链接池(connection pool)的工做原理 

一、基本概念及原理 

数 据库链接池的基本思想就是为数据库链接创建一个“缓冲池”。预先在缓冲池中放入必定数量的链接,当须要创建数据库链接时,只需从“缓冲池”中取出一个,使 用完毕以后再放回去。咱们能够经过设定链接池最大链接数来防止系统无尽的与数据库链接。更为重要的是咱们能够经过链接池的管理机制监视数据库的链接的数 量、使用状况,为系统开发、测试及性能调整提供依据。 

二、服务器自带的链接池 

JDBC的API中没有提供链接池的方法。一些大型的WEB应用服务器如BEA的WebLogic和IBM的WebSphere等提供了链接池的机制,可是必须有其第三方的专用类方法支持链接池的用法。 

链接池关键问题分析 

一、并发问题 

为 了使链接管理服务具备最大的通用性,必须考虑多线程环境,即并发问题。这个问题相对比较好解决,由于Java语言自身提供了对并发管理的支持,使用 synchronized关键字便可确保线程是同步的。使用方法为直接在类方法前面加上synchronized关键字,如: 
public synchronized Connection getConnection()  

二、多数据库服务器和多用户 

对 于大型的企业级应用,经常须要同时链接不一样的数据库(如链接Oracle和Sybase)。如何链接不一样的数据库呢?咱们采用的策略是:设计一个符合单例 模式的链接池管理类,在链接池管理类的惟一实例被建立时读取一个资源文件,其中资源文件中存放着多个数据库的url地址 (<poolName.url>)、用户名(<poolName.user>)、密码 (<poolName.password>)等信息。如 tx.url=172.21.15.123:5000/tx_it,tx.user=yang,tx.password=yang321。根据资源文件提 供的信息,建立多个链接池类的实例,每个实例都是一个特定数据库的链接池。链接池管理类实例为每一个链接池实例取一个名字,经过不一样的名字来管理不一样的连 接池。 

对于同一个数据库有多个用户使用不一样的名称和密码访问的状况,也能够经过资源文件处理,即在资源文件中设置多个具备相同url地址,但具备不一样用户名和密码的数据库链接信息。 

三、事务处理 

咱们知道,事务具备原子性,此时要求对数据库的操做符合“ALL-ALL-NOTHING”原则,即对于一组SQL语句要么全作,要么全不作。 

在 Java语言中,Connection类自己提供了对事务的支持,能够经过设置Connection的AutoCommit属性为false,而后显式的 调用commit或rollback方法来实现。但要高效的进行Connection复用,就必须提供相应的事务支持机制。可采用每个事务独占一个链接 来实现,这种方法能够大大下降事务管理的复杂性。 

四、链接池的分配与释放 

链接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,能够提升链接的复用度,从而下降创建新链接的开销,同时还能够加快用户的访问速度。 

对 于链接的管理可以使用空闲池。即把已经建立但还没有分配出去的链接按建立时间存放到一个空闲池中。每当用户请求一个链接时,系统首先检查空闲池内有没有空闲连 接。若是有就把创建时间最长(经过容器的顺序存放实现)的那个链接分配给他(实际是先作链接是否有效的判断,若是可用就分配给用户,如不可用就把这个链接 从空闲池删掉,从新检测空闲池是否还有链接);若是没有则检查当前所开链接池是否达到链接池所容许的最大链接数(maxConn),若是没有达到,就新建 一个链接,若是已经达到,就等待必定的时间(timeout)。若是在等待的时间内有链接被释放出来就能够把这个链接分配给等待的用户,若是等待时间超过 预约时间timeout,则返回空值(null)。系统对已经分配出去正在使用的链接只作计数,当使用完后再返还给空闲池。对于空闲链接的状态,可开辟专 门的线程定时检测,这样会花费必定的系统开销,但能够保证较快的响应速度。也可采起不开辟专门线程,只是在分配前检测的方法。 

五、链接池的配置与维护 

连 接池中到底应该放置多少链接,才能使系统的性能最佳?系统可采起设置最小链接数(minConn)和最大链接数(maxConn)来控制链接池中的链接。 最小链接数是系统启动时链接池所建立的链接数。若是建立过多,则系统启动就慢,但建立后系统的响应速度会很快;若是建立过少,则系统启动的很快,响应起来 却慢。
能够在开发时,设置较小的最小链接数,开发起来会快,而在系统实际使用时设置较大的,由于这样对访问客户来讲速度会快些。最大链接数是链接池中容许链接的最大数目,具体设置多少,要看系统的访问量,可经过反复测试,找到最佳点。 

如何确保链接池中的最小链接数呢?有动态和静态两种策略。动态即每隔必定时间就对链接池进行检测,若是发现链接数量小于最小链接数,则补充相应数量的新链接,以保证链接池的正常运转。静态是发现空闲链接不够时再去检查。 

链接池的实现 

一、链接池模型 

连 接池类是对某一数据库全部链接的“缓冲池”,主要实现如下功能:①从链接池获取或建立可用链接;②使用完毕以后,把链接返还给链接池;③在系统关闭前,断 开全部链接并释放链接占用的系统资源;④还可以处理无效链接(原来登记为可用的链接,因为某种缘由再也不可用,如超时,通信问题),并可以限制链接池中的连 接总数不低于某个预约值和不超过某个预约值。 

链接池管理类是链接池类的外覆类(wrapper),符合单例模式,即系统中只能有一个连 接池管理类的实例。其主要用于对多个链接池对象的管理,具备如下功能:①装载并注册特定数据库的JDBC驱动程序;②根据属性文件给定的信息,建立链接池 对象;③为方便管理多个链接池对象,为每个链接池对象取一个名字,实现链接池名字与其实例之间的映射;④跟踪客户使用链接状况,以便须要是关闭链接释放 资源。链接池管理类的引入主要是为了方便对多个链接池的使用和管理,如系统须要链接不一样的数据库,或链接相同的数据库但因为安全性问题,须要不一样的用户使 用不一样的名称和密码。 

二、链接池实现 

下面给出链接池类和链接池管理类的主要属性及所要实现的基本接口: 

public class DBConnectionPool implements TimerListener{ 

private int checkedOut;//已被分配出去的链接数 
private ArrayList freeConnections = new ArrayList();//容器,空闲池,根据建立时间顺序存放已建立
还没有分配出去的链接 
private int minConn;//链接池里链接的最小数量 
private int maxConn;//链接池里容许存在的最大链接数 
private String name;//为这个链接池取个名字,方便管理 
private String password;//链接数据库时须要的密码 
private String url;//所要建立链接的数据库的地址 
private String user;//链接数据库时须要的用户名 
public Timer timer;//定时器 

public DBConnectionPool(String name, String URL, String user, Stringpassword, int maxConn) 
public synchronized void freeConnection(Connection con) //使用完毕以后把链接返还给空闲池 
public synchronized Connection getConnection(long timeout)//获得一个链接,timeout是等待时间 
public synchronized void release()//断开全部链接,释放占用的系统资源 
private Connection newConnection()//新建一个数据库链接 
public synchronized void TimerEvent() //定时器事件处理函数 
}

public class DBConnectionManager { 

static private DBConnectionManager instance;//链接池管理类的惟一实例 
static private int clients;//客户数量 
private ArrayList drivers = new ArrayList();//容器,存放数据库驱动程序 
private HashMap pools = new HashMap ();//以name/value的形式存取链接池对象的名字及链接池对象 

private void loadDrivers(Properties props)//装载数据库驱动程序 
private void createPools(Properties props)//根据属性文件提供的信息,建立一个或多个链接池 

private DBConnectionManager()//私有构造函数,在其中调用初始化函数init() 
private void init()//初始化链接池管理类的惟一实例,由私有构造函数调用 
static synchronized public DBConnectionManager getInstance()//若是惟一的实例instance已经建立,直接返回这个实例;不然,调用私有构造函数,建立链接池管理类的惟一实例 

public Connection getConnection(String name)//从名字为name的链接池对象//中获得一个链接 
public Connection getConnection(String name, long time)//从名字为name 的链接池对象中取得一个链接,time是等待时间 

public void freeConnection(String name, Connection con)//释放一个链接name是一个链接池对象的名 
public synchronized void release()//释放全部资源 

}   

三、链接池使用 

上面所实现的链接池在程序开发时如何应用到系统中呢?下面以Servlet为例说明链接池的使用。 
Servlet 的生命周期是:在开始创建servlet时,调用其初始化(init)方法。以后每一个用户请求都致使一个调用前面创建的实例的service方法的线程。 最后,当服务器决定卸载一个servlet时,它首先调用该servlet的 destroy方法。 根据servlet的特色,咱们能够在初始化函数中 生成链接池管理类的惟一实例(其中包括建立一个或多个链接池)。如: 

public void init() throws ServletException 

  // getInstance()?DBConnectionManager()?init()
 connMgr = DBConnectionManager.getInstance();  
}   

而后就能够在service方法中经过链接池名称使用链接池,执行数据库操做。最后在destroy方法中释放占用的系统资源,如:  

public void destroy() {  
connMgr.release(); 
super.destroy();  
}
编程

4 一种简单JDBC链接池的实现数组

 

JDBC链接池 

在标准JDBC对应用的接口中,并无提供资源的管理方法。因此,缺省的资源管理由应用本身负责。虽然在 JDBC规范中,屡次说起资源的关闭/回收及其余的合理运用。但最稳妥的方式,仍是为应用提供有效的管理手段。因此,JDBC为第三方应用服务器 (Application Server)提供了一个由数据库厂家实现的管理标准接口:链接缓冲(connection pooling)。引入了链接池 ( Connection Pool )的概念 ,也就是以缓冲池的机制管理数据库的资源。 

JDBC最经常使用的资源有三类: 

-Connection: 数据库链接。 

-Statement: 会话声明。 

-ResultSet: 结果集游标。 

分别存在如下的关系 : 

安全


这是一种 ‘爷-父-子’ 的关系,对Connection的管理,就是对数据库资源的管理。举个例子: 若是想肯定某个数据库链接 (Connection)是否超时,则须要肯定其(全部的)子Statement是否超时,一样,须要肯定全部相关的ResultSet是否超时;在关闭 Connection前,须要关闭全部相关的Statement和ResultSet。 

所以,链接池(Connection Pool)所起到的做用,不只仅简单地管理Connection,还涉及到 Statement和ResultSet。 

2.3链接池(ConnectionPool)与资源管理 

ConnectionPool以缓冲池的机制,在必定数量上限范围内,控制管理Connection,Statement和ResultSet。任何数据库的资源是有限的,若是被耗尽,则没法得到更多的数据服务。 

在大多数状况下,资源的耗尽不是因为应用的正常负载太高,而是程序缘由。 

在 实际工做中,数据资源每每是瓶颈资源,不一样的应用都会访问同一数据源。其中某个应用耗尽了数据库资源后,意味其余的应用也没法正常运行。因 此,ConnectionPool的第一个任务是限制:每一个应用或系统能够拥有的最大资源。也就是肯定链接池的大小(PoolSize)。 

ConnectionPool 的第二个任务:在链接池的大小(PoolSize)范围内,最大限度地使用资源,缩短数据库访问的使用周期。许多数据库中,链接(Connection) 并非资源的最小单元,控制Statement资源比Connection更重要。以Oracle为例: 

每申请一个链接 (Connection)会在物理网络(如 TCP/IP网络)上创建一个用于通信的链接,在此链接上还能够申请必定数量的Statement。同一链接 可提供的活跃Statement数量能够达到几百。 在节约网络资源的同时,缩短了每次会话周期(物理链接的创建是个费时的操做)。但在通常的应用中,多 数按照2.1范例操做,这样有10个程序调用,则会产生10次物理链接,每一个Statement单独占用一个物理链接,这是极大的资源浪 费。 ConnectionPool能够解决这个问题,让几10、几百个Statement只占用同一个物理链接, 发挥数据库原有的优势。 

经过ConnectionPool对资源的有效管理,应用能够得到的Statement总数到达 : 

(并发物理链接数) x (每一个链接可提供的Statement数量) 

例 如某种数据库可同时创建的物理链接数为 200个,每一个链接可同时提供250个Statement,那么ConnectionPool最终为应用提供的并 发Statement总数为: 200 x 250 = 50,000个。这是个并发数字,不多有系统会突破这个量级。因此在本节的开始,指出资源的耗尽 与应用程序直接管理有关。 

对资源的优化管理,很大程度上依靠数据库自身的JDBC Driver是否具有。有些数据库的JDBC Driver并不支持Connection与Statement之间的逻辑链接功能,如SQLServer,咱们只能等待她自身的更新版本了。 

对资源的申请、释放、回收、共享和同步,这些管理是复杂精密的。因此,ConnectionPool另外一个功能就是,封装这些操做,为应用提供简单的,甚至是不改变应用风格的调用接口。 服务器

3.简单JDBC链接池的实现 

根据第二章中原理机制,Snap-ConnectionPool(一种简单快速的链接池工具)按照部分的JDBC规范,实现了链接池所具有的对数据库资源有效管理功能。 

3.1体系描述 

在 JDBC规范中,应用经过驱动接口(Driver Interface)直接方法数据库的资源。为了有效、合理地管理资源,在应用与 JDBC Driver之间,增长了链接池: Snap-ConnectionPool。而且经过面向对象的机制,使链接池的大部分操做是透明的。参见下 图,Snap-ConnectionPool的体系: 

网络


图中所示,经过实现JDBC的部分资源对象接口( Connection, Statement, ResultSet ), 在 Snap-ConnectionPool内部分别产生三种逻辑资源对象: PooledConnection, PooledStatement 和 PooledResultSet。它们也是链接池主要的管理操做对象,而且继承了JDBC中相应的从属关系。这样的体系有如下几个特色: 

-透明性。在不改变应用原有的使用JDBC驱动接口的前提下,提供资源管理的服务。应用系统,如同原有的 JDBC,使用链接池提供的逻辑对象资源。简化了应用程序的链接池改造。 

-资源封装。复杂的资源管理被封装在 Snap-ConnectionPool内部,不须要应用系统过多的干涉。管理操做的可靠性、安全性由链接池保证。应用的干涉(如:主动关闭资源),只起到优化系统性能的做用,遗漏操做不会带来负面影响。 

- 资源合理应用。按照JDBC中资源的从属关系,Snap-ConnectionPool不只对Connection进行缓冲处理,对Statement也 有相应的机制处理。在2.3已描述,合理运用Connection和Statement之间的关系,能够更大限度地使用资源。因此,Snap- ConnectionPool封装了Connection资源,经过内部管理PooledConnection,为应用系统提供更多的Statement 资源。 

-资源连锁管理。Snap-ConnectionPool包含的三种逻辑对象,继承了JDBC中相应对象之间的从属关系。在内部 管理中,也依照从属关系进行连锁管理。例如:判断一个Connection是否超时,须要根据所包含的Statement是否活跃;判断 Statement也要根据ResultSet的活跃程度。 

3.2链接池集中管理ConnectionManager 

ConnectionPool是Snap- ConnectionPool的链接池对象。在Snap-ConnectionPool内部,能够指定多个不一样的链接池(ConnectionPool) 为应用服务。ConnectionManager管理全部的链接池,每一个链接池以不一样的名称区别。经过配置文件适应不一样的数据库种类。以下图所示: 


经过ConnectionManager,能够同时管理多个不一样的链接池,提供通一的管理界面。在应用系统中经过 ConnectionManager和相关的配置文件,能够将凌乱散落在各自应用程序中的数据库配置信息(包括:数据库名、用户、密码等信息),集中在一 个文件中。便于系统的维护工做。 

3.3链接池使用范例 

对2.1的标准JDBC的使用范例,改成使用链接池,结果以下: 

import java.sql.*;
import net.snapbug.util.dbtool.*;

..ConnectionPool dbConn = ConnectionManager
                   .getConnectionPool("testOracle" );
Statement st = dbConn.createStatement();
ResultSet rs = st.executeQuery( 
             “select * from demo_table” );

some data source operation 
     in herers.close();st.close();
 


在 例子中,Snap-ConnectionPool封装了应用对Connection的管理。只要改变JDBC获取Connection的方法,为获取链接 池(ConnectionPool)(粗体部分),其余的数据操做均可以不作修改。按照这样的方式,Snap-ConnectionPool可帮助应用有 效地管理数据库资源。若是应用忽视了最后资源的释放: rs.close() 和 st.close(),链接池会经过超时(time-out)机制,自 动回收。 

4.小结 

不管是Snap-ConnectionPool仍是其余的数据库链接池,都应当具有一下基本功能: 

-对源数据库资源的保护 

-充分利用发挥数据库的有效资源 

-简化应用的数据库接口,封闭资源管理。 

-对应用遗留资源的自动回收和整理,提升资源的再次利用率。 

在这个前提下,应用程序才能投入更多的精力于各自的业务逻辑中。数据库资源也再也不成为系统的瓶颈。 

注: 在网站 www.snapbug.net可免费下载Snap-ConnectionPool及更详细的文档。

 

附:JDBC链接各类数据库的方法及技巧

1、链接各类数据库方式速查表

  下面罗列了各类数据库使用JDBC链接的方式,能够做为一个手册使用。

  一、Oracle8/8i/9i数据库(thin模式)

Class.forName(“oracle.jdbc.driver.OracleDriver“).newInstance();
String url=“jdbc:oracle:thin:@localhost :1521:orcl“; //orcl为数据库的SIDString user=“test“;String password=“test“;Connection conn= DriverManager.getConnection(url,user,password);  二、DB2数据库Class.forName(“com.ibm.db2.jdbc.app.DB2Driver “).newInstance();String url=“jdbc:db2://localhost:5000/sample“; //sample为你的数据库名String user=“admin“;String password=““;Connection conn= DriverManager.getConnection(url,user,password);  三、Sql Server7.0/2000数据库Class.forName(“com.microsoft.jdbc.sqlserver.SQLServerDriver“).newInstance();String url=“jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=mydb“;//mydb为数据库String user=“sa“;String password=““;Connection conn= DriverManager.getConnection(url,user,password);  四、Sybase数据库Class.forName(“com.sybase.jdbc.SybDriver“).newInstance();String url =“ jdbc:sybase:Tds:localhost:5007/myDB“;//myDB为你的数据库名Properties sysProps = System.getProperties();SysProps.put(“user“,“userid“);SysProps.put(“password“,“user_password“);Connection conn= DriverManager.getConnection(url, SysProps); 五、Informix数据库Class.forName(“com.informix.jdbc.IfxDriver“).newInstance();String url = “jdbc:informix-sqli://123.45.67.89:1533/myDB:INFORMIXSERVER=myserver;user=testuser;password=testpassword“; //myDB为数据库名Connection conn= DriverManager.getConnection(url);  六、MySQL数据库Class.forName(“org.gjt.mm.mysql.Driver“).newInstance();String url =“jdbc:mysql://localhost/myDB?user=soft&password=soft1234&useUnicode=true&characterEncoding=8859_1“//myDB为数据库名Connection conn= DriverManager.getConnection(url);  七、PostgreSQL数据库Class.forName(“org.postgresql.Driver“).newInstance();String url =“jdbc:postgresql://localhost/myDB“ //myDB为数据库名String user=“myuser“;String password=“mypassword“;Connection conn= DriverManager.getConnection(url,user,password);  八、access数据库直连用ODBC的Class.forName(“sun.jdbc.odbc.JdbcOdbcDriver“) ;String url=“jdbc:odbc:Driver={MicroSoft Access Driver (*.mdb)};DBQ=“+application.getRealPath(“/Data/ReportDemo.mdb“);Connection conn = DriverManager.getConnection(url,““,“”);Statement stmtNew=conn.createStatement() ;  2、JDBC链接MySql方式  下面是使用JDBC链接MySql的一个小的教程  一、查找驱动程序  MySQL目前提供的java驱动程序为Connection/J,能够从MySQL官方网站下载,并找到mysql-connector-java-3.0.15-ga-bin.jar文件,此驱动程序为纯java驱动程序,不需作其余配置。二、动态指定classpath  若是须要执行时动态指定classpath,就在执行时采用-cp方式。不然将上面的.jar文件加入到classpath环境变量中。  三、加载驱动程序try{ Class.forName(com.mysql.jdbc.Driver); System.out.println(Success loading Mysql Driver!);}catch(Exception e){ System.out.println(Error loading Mysql Driver!); e.printStackTrace();}  四、设置链接的urljdbc:mysql://localhost/databasename[?pa=va][&pa=va]  3、如下列出了在使用JDBC来链接Oracle数据库时可使用的一些技巧  一、在客户端软件开发中使用Thin驱动程序   在开发Java软件方面,Oracle的数据库提供了四种类型的驱动程序,二种用于应用软件、applets、servlets等客户端软件,另外二种 用于数据库中的Java存储过程等服务器端软件。在客户机端软件的开发中,咱们能够选择OCI驱动程序或Thin驱动程序。OCI驱动程序利用Java本 地化接口(JNI),经过Oracle客户端软件与数据库进行通信。Thin驱动程序是纯Java驱动程序,它直接与数据库进行通信。为了得到最高的性 能,Oracle建议在客户端软件的开发中使用OCI驱动程序,这彷佛是正确的。但我建议使用Thin驱动程序,由于经过屡次测试发现,在一般状况 下,Thin驱动程序的性能都超过了OCI驱动程序。  二、关闭自动提交功能,提升系统性能  在第一次创建与数据库的链接时,在缺省状况下,链接是在自动提交模式下的。为了得到更好的性能,能够经过调用带布尔值false参数的Connection类的setAutoCommit()方法关闭自动提交功能,以下所示:  conn.setAutoCommit(false);  值得注意的是,一旦关闭了自动提交功能,咱们就须要经过调用Connection类的commit()和rollback()方法来人工的方式对事务进行管理。三、在动态SQL或有时间限制的命令中使用Statement对象   在执行SQL命令时,咱们有二种选择:可使用PreparedStatement对象,也可使用Statement对象。不管多少次地使用同一个 SQL命令,PreparedStatement都只对它解析和编译一次。当使用Statement对象时,每次执行一个SQL命令时,都会对它进行解析 和编译。这可能会使你认为,使用PreparedStatement对象比使用Statement对象的速度更快。然而,我进行的测试代表,在客户端软件 中,状况并不是如此。所以,在有时间限制的SQL操做中,除非成批地处理SQL命令,咱们应当考虑使用Statement对象。  此外,使用Statement对象也使得编写动态SQL命令更加简单,由于咱们能够将字符串链接在一块儿,创建一个有效的SQL命令。所以,我认为,Statement对象可使动态SQL命令的建立和执行变得更加简单。  四、利用helper函数对动态SQL命令进行格式化   在建立使用Statement对象执行的动态SQL命令时,咱们须要处理一些格式化方面的问题。例如,若是咱们想建立一个将名字O'Reilly插入表 中的SQL命令,则必须使用二个相连的“''”号替换O'Reilly中的“'”号。完成这些工做的最好的方法是建立一个完成替换操做的helper方 法,而后在链接字符串心服用公式表达一个SQL命令时,使用建立的helper方法。与此相似的是,咱们可让helper方法接受一个Date型的值, 而后让它输出基于Oracle的to_date()函数的字符串表达式。  五、利用PreparedStatement对象提升数据库的整体效率   在使用PreparedStatement对象执行SQL命令时,命令被数据库进行解析和编译,而后被放到命令缓冲区。而后,每当执行同一个 PreparedStatement对象时,它就会被再解析一次,但不会被再次编译。在缓冲区中能够发现预编译的命令,而且能够从新使用。在有大量用户的 企业级应用软件中,常常会重复执行相同的SQL命令,使用PreparedStatement对象带来的编译次数的减小可以提升数据库的整体性能。若是不 是在客户端建立、预备、执行PreparedStatement任务须要的时间长于Statement任务,我会建议在除动态SQL命令以外的全部状况下 使用PreparedStatement对象。  六、在成批处理重复的插入或更新操做中使用PreparedStatement对象   若是成批地处理插入和更新操做,就可以显著地减小它们所须要的时间。Oracle提供的Statement和 CallableStatement并不真正地支持批处理,只有PreparedStatement对象才真正地支持批处理。咱们可使用 addBatch()和executeBatch()方法选择标准的JDBC批处理,或者经过利用PreparedStatement对象的 setExecuteBatch()方法和标准的executeUpdate()方法选择速度更快的Oracle专有的方法。要使用Oracle专有的批 处理机制,能够以以下所示的方式调用setExecuteBatch():PreparedStatement pstmt3D null;try { ((OraclePreparedStatement)pstmt).setExecuteBatch(30); ... pstmt.executeUpdate();}   调用setExecuteBatch()时指定的值是一个上限,当达到该值时,就会自动地引起SQL命令执行,标准的executeUpdate()方 法就会被做为批处理送到数据库中。咱们能够经过调用PreparedStatement类的sendBatch()方法随时传输批处理任务。七、使用Oracle locator方法插入、更新大对象(LOB)   Oracle的PreparedStatement类不彻底支持BLOB和CLOB等大对象的处理,尤为是Thin驱动程序不支持利用 PreparedStatement对象的setObject()和setBinaryStream()方法设置BLOB的值,也不支持利用 setCharacterStream()方法设置CLOB的值。只有locator自己中的方法才可以从数据库中获取LOB类型的值。可使用 PreparedStatement对象插入或更新LOB,但须要使用locator才能获取LOB的值。因为存在这二个问题,所以,我建议使用 locator的方法来插入、更新或获取LOB的值。  八、使用SQL92语法调用存储过程  在调用存储过程时,咱们可使用SQL92或Oracle PL/SQL,因为使用Oracle PL/SQL并无什么实际的好处,并且会给之后维护你的应用程序的开发人员带来麻烦,所以,我建议在调用存储过程时使用SQL92。  九、使用Object SQL将对象模式转移到数据库中   既然能够将Oracle的数据库做为一种面向对象的数据库来使用,就能够考虑将应用程序中的面向对象模式转到数据库中。目前的方法是建立Java bean做为假装的数据库对象,将它们的属性映射到关系表中,而后在这些bean中添加方法。尽管这样做在Java中没有什么问题,但因为操做都是在数据 库以外进行的,所以其余访问数据库的应用软件没法利用对象模式。若是利用Oracle的面向对象的技术,能够经过建立一个新的数据库对象类型在数据库中模 仿其数据和操做,而后使用JPublisher等工具生成本身的Java bean类。若是使用这种方式,不但Java应用程序可使用应用软件的对象模式,其余须要共享你的应用中的数据和操做的应用软件也可使用应用软件中的 对象模式。  十、利用SQL完成数据库内的操做  我要向你们介绍的最重要的经验是充分利用SQL的面向集合的方法来解决数据库处理需求,而不是使用Java等过程化的编程语言。   若是编程人员要在一个表中查找许多行,结果中的每一个行都会查找其余表中的数据,最后,编程人员建立了独立的UPDATE命令来成批地更新第一个表中的数 据。与此相似的任务能够经过在set子句中使用多列子查询而在一个UPDATE命令中完成。当可以在单一的SQL命令中完成任务,何须要让数据在网上流来 流去的?我建议用户认真学习如何最大限度地发挥SQL的功能.

相关文章
相关标签/搜索