JDBC基础知识
一、采用JDBC访问数据库的基本步骤:
A.载入JDBC驱动程序
B.定义连接URL
C.建立连接
D.创建Statement对象
E.执行查询或更新
F.结果处理
G.关闭连接
二、载入JDBC驱动程序:
1.为了使代码尽可能地灵活,我们要避免对类名的引用进行硬编码(hard-coding),因此我们可以采用从Properties文件中载入驱动程序的方法,也可以使用在服务器中配置数据源(DataSource)的方法来避免在代码中硬编码
2.在开发过程中要保证CLASSPATH设定中包括驱动程序JAR文件所在的路径。在WEB服务
器上部署时要将JAR文件放在Web应用的WEB-INF/lib目录下。如果多个Web应用使用相同的数据库驱动程序可以将JAR文件放置在服务器使用的公共目录<%CATALINA_HOME%>/common/lib中
三、定义连接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:数据库名
四、建立连接:
1.一个数据库连接(Connection)可以通过其自身的getMetaData()来获取它的自身信息
2.默认情况下一个数据库的连接是自动提交模式的(auto-commit),也就是说每当一个SQL语句
被执行后其改变结果都会被自动提交,如果auto-commit模式被关闭,那么方法commit()必须被显式调用以提交改变结果,否则的话所有对数据库操作的结果都不会被保存
五、创建Statement对象:
在同一时间下,每个Statement对象只能打开一个ResultSet对象。所以,假如有两个同样结果的结果集在交叉访问,那么这两个结果集必定为两个不同的Statement对象所创建。如果在打开一个新的结果集的时候存在一个已经打开的结果集,则这个已经存在的结果集会被隐式的关闭
六、执行查询或更新:
在Statement对象中可以执行如下的操作:
A.查询操作:executeQuery(SQL语句) B.维护操作:executeUpdate(SQL语句)
C.批处理操作:executeBath()
七、结果处理:
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为至
八、关闭连接:
在关闭数据库连接时应该以ResultSet、Statement、Connection的顺序进行
JDBC-PreparedStatement(预备语句)
一、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();}
二、使用PreparedStatement的好处:
1.依赖于服务器对预编译查询的支持,以及驱动程序处理原始查询的效率,预备语句在性能上的优势可能有很大的不同。
2.安全是预备语句的另外一个特点,我们推荐在通过HTML表单接受用户输入,然后对数据库进行更新时,一定要使用预备语句或存储过程。
3.预备语句还能够正确地处理嵌入在字符串中的引号以及处理非字符数据(比如向数据库发送序列化后的对象)
JDBC-CallableStatement(可调用语句)
一、使用CallableStatement(可调用语句)的优缺点:
1.优点:语法错误可以在编译时找出来,而非在运行期间;数据库存储过程的运行可能比常规的
SQL查询快得多;程序员只需知道输入和输出参数,不需了解表的结构。另外,由于数据库语言能够访问数据库本地的一下儿功能(序列,触发器,多重游标),因此用它来编写存储过程可能要比使用Java编程语言要简易一些。
2.缺点:存储过程的商业逻辑在数据库服务器上运行,而非客户机或Web服务器。而行业的发展趋势是尽可能多地将商业逻辑移出数据库,将它们放在JavaBean组件(或者在大型的系统中,EnterPrise JavaBean组件)中,在Web构架上采用这种方式的主要动机是:数据库访问和网络I/O常常是性能的瓶颈。
二、使用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。
1、创建 CallableStatement 对象
CallableStatement 对象是用 Connection 方法 prepareCall 创建的。下例创建 CallableStatement 的实例,其中含有对已储存过程 getTestData 调用。该过程有两个变量,但不含结果参数:
CallableStatement cstmt = con.prepareCall("{call getTestData(?, ?)}");
其中?占位符为IN、OUT还是INOUT参数,取决于已储存过程getTestData。
2、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 对象(小数点后面带三位数):
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 值的特殊机制。
3、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);
4、先检索结果,再检索 OUT 参数
由于某些 DBMS 的限制,为了实现最大的可移植性,建议先检索由执行CallableStatement 对象所产生的结果,然后再用 CallableStatement.getXXX 方法来检索 OUT 参数。如果 CallableStatement 对象返回多个 ResultSet 对象(通过调用 execute 方法),在检索 OUT 参数前应先检索所有的结果。这种情况下,为确保对所有的结果都进行了访问,必须对 Statement 方法 getResultSet、getUpdateCount 和getMoreResults 进行调用,直到不再有结果为止。
检索完所有的结果后,就可用 CallableStatement.getXXX 方法来检索 OUT 参数中的值。
5、检索作为OUT参数的NULL值
返回到 OUT 参数中的值可能会是JDBC NULL。当出现这种情形时,将对 JDBC NULL 值进行转换以使 getXXX 方法所返回的值为 null、0 或 false,这取决于getXXX 方法类型。对于 ResultSet 对象,要知道0或false是否源于JDBCNULL的唯一方法,是用方法wasNull进行检测。如果 getXXX 方法读取的最后一个值是 JDBC NULL,则该方法返回 true,否则返回 flase。
JDBC-Transation(事务处理)
一、Transation(事务处理)的概念:
在更新数据库时,默认情况下,更改是永久性写入到数据库。然而这种默认行为可以通过编写程序来关闭。在自动交付关闭的情况下,如果在更新时发生问题,则对数据库的每个更改都能够取消(或者说回退到最初的值)。如果更新成功,那么之后可以将这些更改永久性提交给数据库。这种方式也称为事务管理。
我们需要确保,要么所有的操作都发生,要么所有的操作都不发生。这就是事务管理的原则。
二、在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
一、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():判断数据库连接是否超时或被显示关闭
二、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():设置等待处理结果的时间
三、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
四、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)的工作原理
1、基本概念及原理
数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量、使用情况,为系统开发、测试及性能调整提供依据。
2、服务器自带的连接池
JDBC的API中没有提供连接池的方法。一些大型的WEB应用服务器如BEA的WebLogic和IBM的WebSphere等提供了连接池的机制,但是必须有其第三方的专用类方法支持连接池的用法。
连接池关键问题分析
1、并发问题
为了使连接管理服务具有最大的通用性,必须考虑多线程环境,即并发问题。这个问题相对比较好解决,因为Java语言自身提供了对并发管理的支持,使用synchronized关键字即可确保线程是同步的。使用方法为直接在类方法前面加上synchronized关键字,如:
public synchronized Connection getConnection()
2、多数据库服务器和多用户
对于大型的企业级应用,常常需要同时连接不同的数据库(如连接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地址,但具有不同用户名和密码的数据库连接信息。
3、事务处理
我们知道,事务具有原子性,此时要求对数据库的操作符合“ALL-ALL-NOTHING”原则,即对于一组SQL语句要么全做,要么全不做。
在Java语言中,Connection类本身提供了对事务的支持,可以通过设置Connection的AutoCommit属性为false,然后显式的调用commit或rollback方法来实现。但要高效的进行Connection复用,就必须提供相应的事务支持机制。可采用每一个事务独占一个连接来实现,这种方法可以大大降低事务管理的复杂性。
4、连接池的分配与释放
连接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,可以提高连接的复用度,从而降低建立新连接的开销,同时还可以加快用户的访问速度。
对于连接的管理可使用空闲池。即把已经创建但尚未分配出去的连接按创建时间存放到一个空闲池中。每当用户请求一个连接时,系统首先检查空闲池内有没有空闲连接。如果有就把建立时间最长(通过容器的顺序存放实现)的那个连接分配给他(实际是先做连接是否有效的判断,如果可用就分配给用户,如不可用就把这个连接从空闲池删掉,重新检测空闲池是否还有连接);如果没有则检查当前所开连接池是否达到连接池所允许的最大连接数(maxConn),如果没有达到,就新建一个连接,如果已经达到,就等待一定的时间(timeout)。如果在等待的时间内有连接被释放出来就可以把这个连接分配给等待的用户,如果等待时间超过预定时间timeout,则返回空值(null)。系统对已经分配出去正在使用的连接只做计数,当使用完后再返还给空闲池。对于空闲连接的状态,可开辟专门的线程定时检测,这样会花费一定的系统开销,但可以保证较快的响应速度。也可采取不开辟专门线程,只是在分配前检测的方法。
5、连接池的配置与维护
连接池中到底应该放置多少连接,才能使系统的性能最佳?系统可采取设置最小连接数(minConn)和最大连接数(maxConn)来控制连接池中的连接。最小连接数是系统启动时连接池所创建的连接数。如果创建过多,则系统启动就慢,但创建后系统的响应速度会很快;如果创建过少,则系统启动的很快,响应起来却慢。
可以在开发时,设置较小的最小连接数,开发起来会快,而在系统实际使用时设置较大的,因为这样对访问客户来说速度会快些。最大连接数是连接池中允许连接的最大数目,具体设置多少,要看系统的访问量,可通过反复测试,找到最佳点。
如何确保连接池中的最小连接数呢?有动态和静态两种策略。动态即每隔一定时间就对连接池进行检测,如果发现连接数量小于最小连接数,则补充相应数量的新连接,以保证连接池的正常运转。静态是发现空闲连接不够时再去检查。
连接池的实现
1、连接池模型
连接池类是对某一数据库所有连接的“缓冲池”,主要实现以下功能:①从连接池获取或创建可用连接;②使用完毕之后,把连接返还给连接池;③在系统关闭前,断开所有连接并释放连接占用的系统资源;④还能够处理无效连接(原来登记为可用的连接,由于某种原因不再可用,如超时,通讯问题),并能够限制连接池中的连接总数不低于某个预定值和不超过某个预定值。
连接池管理类是连接池类的外覆类(wrapper),符合单例模式,即系统中只能有一个连接池管理类的实例。其主要用于对多个连接池对象的管理,具有以下功能:①装载并注册特定数据库的JDBC驱动程序;②根据属性文件给定的信息,创建连接池对象;③为方便管理多个连接池对象,为每一个连接池对象取一个名字,实现连接池名字与其实例之间的映射;④跟踪客户使用连接情况,以便需要是关闭连接释放资源。连接池管理类的引入主要是为了方便对多个连接池的使用和管理,如系统需要连接不同的数据库,或连接相同的数据库但由于安全性问题,需要不同的用户使用不同的名称和密码。
2、连接池实现
下面给出连接池类和连接池管理类的主要属性及所要实现的基本接口:
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()//释放所有资源
}
3、连接池使用
上面所实现的连接池在程序开发时如何应用到系统中呢?下面以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可以解决这个问题,让几十、几百个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连接各种数据库的方法及技巧
一、连接各种数据库方式速查表
下面罗列了各种数据库使用JDBC连接的方式,可以作为一个手册使用。
1、Oracle8/8i/9i数据库(thin模式)
Class.forName(“oracle.jdbc.driver.OracleDriver“).newInstance();
String url=“jdbc:oracle:thin:@localhost:1521:orcl“; //orcl为数据库的SID
String user=“test“;
String password=“test“;
Connection conn= DriverManager.getConnection(url,user,password);
2、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);
3、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);
4、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);
5、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);
6、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);
7、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);
8、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() ;
二、JDBC连接MySql方式
下面是使用JDBC连接MySql的一个小的教程
1、查找驱动程序
MySQL目前提供的java驱动程序为Connection/J,可以从MySQL官方网站下载,并找到mysql-connector-java-3.0.15-ga-bin.jar文件,此驱动程序为纯java驱动程序,不需做其他配置。
2、动态指定classpath
如果需要执行时动态指定classpath,就在执行时采用-cp方式。否则将上面的.jar文件加入到classpath环境变量中。
3、加载驱动程序
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();
}
4、设置连接的url
jdbc:mysql://localhost/databasename[?pa=va][&pa=va]
三、以下列出了在使用JDBC来连接Oracle数据库时可以使用的一些技巧
1、在客户端软件开发中使用Thin驱动程序
在开发Java软件方面,Oracle的数据库提供了四种类型的驱动程序,二种用于应用软件、applets、servlets等客户端软件,另外二种用于数据库中的Java存储过程等服务器端软件。在客户机端软件的开发中,我们可以选择OCI驱动程序或Thin驱动程序。OCI驱动程序利用Java本地化接口(JNI),通过Oracle客户端软件与数据库进行通讯。Thin驱动程序是纯Java驱动程序,它直接与数据库进行通讯。为了获得最高的性能,Oracle建议在客户端软件的开发中使用OCI驱动程序,这似乎是正确的。但我建议使用Thin驱动程序,因为通过多次测试发现,在通常情况下,Thin驱动程序的性能都超过了OCI驱动程序。
2、关闭自动提交功能,提高系统性能
在第一次建立与数据库的连接时,在缺省情况下,连接是在自动提交模式下的。为了获得更好的性能,可以通过调用带布尔值false参数的Connection类的setAutoCommit()方法关闭自动提交功能,如下所示:
conn.setAutoCommit(false);
值得注意的是,一旦关闭了自动提交功能,我们就需要通过调用Connection类的commit()和rollback()方法来人工的方式对事务进行管理。
3、在动态SQL或有时间限制的命令中使用Statement对象
在执行SQL命令时,我们有二种选择:可以使用PreparedStatement对象,也可以使用Statement对象。无论多少次地使用同一个SQL命令,PreparedStatement都只对它解析和编译一次。当使用Statement对象时,每次执行一个SQL命令时,都会对它进行解析和编译。这可能会使你认为,使用PreparedStatement对象比使用Statement对象的速度更快。然而,我进行的测试表明,在客户端软件中,情况并非如此。因此,在有时间限制的SQL操作中,除非成批地处理SQL命令,我们应当考虑使用Statement对象。
此外,使用Statement对象也使得编写动态SQL命令更加简单,因为我们可以将字符串连接在一起,建立一个有效的SQL命令。因此,我认为,Statement对象可以使动态SQL命令的创建和执行变得更加简单。
4、利用helper函数对动态SQL命令进行格式化
在创建使用Statement对象执行的动态SQL命令时,我们需要处理一些格式化方面的问题。例如,如果我们想创建一个将名字O'Reilly插入表中的SQL命令,则必须使用二个相连的“''”号替换O'Reilly中的“'”号。完成这些工作的最好的方法是创建一个完成替换操作的helper方法,然后在连接字符串心服用公式表达一个SQL命令时,使用创建的helper方法。与此类似的是,我们可以让helper方法接受一个Date型的值,然后让它输出基于Oracle的to_date()函数的字符串表达式。
5、利用PreparedStatement对象提高数据库的总体效率
在使用PreparedStatement对象执行SQL命令时,命令被数据库进行解析和编译,然后被放到命令缓冲区。然后,每当执行同一个PreparedStatement对象时,它就会被再解析一次,但不会被再次编译。在缓冲区中可以发现预编译的命令,并且可以重新使用。在有大量用户的企业级应用软件中,经常会重复执行相同的SQL命令,使用PreparedStatement对象带来的编译次数的减少能够提高数据库的总体性能。如果不是在客户端创建、预备、执行PreparedStatement任务需要的时间长于Statement任务,我会建议在除动态SQL命令之外的所有情况下使用PreparedStatement对象。
6、在成批处理重复的插入或更新操作中使用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()方法随时传输批处理任务。
7、使用Oracle locator方法插入、更新大对象(LOB)
Oracle的PreparedStatement类不完全支持BLOB和CLOB等大对象的处理,尤其是Thin驱动程序不支持利用PreparedStatement对象的setObject()和setBinaryStream()方法设置BLOB的值,也不支持利用setCharacterStream()方法设置CLOB的值。只有locator本身中的方法才能够从数据库中获取LOB类型的值。可以使用PreparedStatement对象插入或更新LOB,但需要使用locator才能获取LOB的值。由于存在这二个问题,因此,我建议使用locator的方法来插入、更新或获取LOB的值。
8、使用SQL92语法调用存储过程
在调用存储过程时,我们可以使用SQL92或Oracle PL/SQL,由于使用Oracle PL/SQL并没有什么实际的好处,而且会给以后维护你的应用程序的开发人员带来麻烦,因此,我建议在调用存储过程时使用SQL92。
9、使用Object SQL将对象模式转移到数据库中
既然可以将Oracle的数据库作为一种面向对象的数据库来使用,就可以考虑将应用程序中的面向对象模式转到数据库中。目前的方法是创建Java bean作为伪装的数据库对象,将它们的属性映射到关系表中,然后在这些bean中添加方法。尽管这样作在Java中没有什么问题,但由于操作都是在数据库之外进行的,因此其他访问数据库的应用软件无法利用对象模式。如果利用Oracle的面向对象的技术,可以通过创建一个新的数据库对象类型在数据库中模仿其数据和操作,然后使用JPublisher等工具生成自己的Java bean类。如果使用这种方式,不但Java应用程序可以使用应用软件的对象模式,其他需要共享你的应用中的数据和操作的应用软件也可以使用应用软件中的对象模式。
10、利用SQL完成数据库内的操作
我要向大家介绍的最重要的经验是充分利用SQL的面向集合的方法来解决数据库处理需求,而不是使用Java等过程化的编程语言。
如果编程人员要在一个表中查找许多行,结果中的每个行都会查找其他表中的数据,最后,编程人员创建了独立的UPDATE命令来成批地更新第一个表中的数据。与此类似的任务可以通过在set子句中使用多列子查询而在一个UPDATE命令中完成。当能够在单一的SQL命令中完成任务,何必要让数据在网上流来流去的?我建议用户认真学习如何最大限度地发挥SQL的功能