670 likes | 926 Views
第十讲. 课 题 JDBC 与数据库( JDBC 与 SQL 语句、存储过程的应用) 目的要求 掌握 JDBC 存取各种不同数据库的方法 教学重点 掌握 JDBC 与不同数据库的连接字符串、连接池 教学难点 JSP 如何存取存储过程中输入参数、输出参数、输入输出 参数 教学课时 4 (含 2 课时上机) 教学方法 讲解、示例与启发式教学相结合 . 教学内容和步骤 10.1 JDBC 与数据库编程 10.1.1 JDBC 的 Driver 可分为以下四种类型
E N D
课 题 JDBC与数据库(JDBC与SQL语句、存储过程的应用) 目的要求 掌握JDBC存取各种不同数据库的方法 教学重点 掌握JDBC与不同数据库的连接字符串、连接池 教学难点 JSP如何存取存储过程中输入参数、输出参数、输入输出 参数 教学课时 4(含2课时上机) 教学方法 讲解、示例与启发式教学相结合
教学内容和步骤 10.1 JDBC与数据库编程 10.1.1 JDBC的Driver可分为以下四种类型 (1)JDBC-ODBC Bridge和ODBC Driver 这种驱动器器通过ODBC驱动器提供数据库连接。使用这种驱动器,要求每一台客户机都装入ODBC的驱动器。 (2)Native-API partly-Java Driver 这种驱动器将JDBC指令转化成所连接使用的DBMS的操作形式。各客户机使用的数据库可能是Oracle,可能是Sybase,也可能是Access,都需要在客户机上装有相应DBMS的驱动程序。
(3)JDBC-Net All-Java Driver 这种驱动器将JDBC指令转化成独立于DBMS的网络协议形式,再由服务器转化为特定DBMS的协议形式。有关DBMS的协议由各数据库厂商决定。这种驱动器可以联接到不同的数据库上,最为灵活。目前一些厂商已经开始添加JDBC的这种驱动器到他们已有的数据库中介产品中。要注意的是,为了支持广域网存取,需要增加有关安全性的措施,如防火墙等等。
(4)Native-protocol All-Java Driver 这种驱动器将JDBC指令转化成网络协议后不再转换,由DBMS直接使用。相当于客户机直接与服务器联系,对局域网适用。 在这四种驱动器中,后两类“纯Java”(All-Java)的驱动器效率更高,也更具有通用性。但目前第一、第二类驱动器比较容易获得,使用也较普遍。 10.1.2 关于JDBC Url 语法如下: jdbc:<子协议>:<子名称>
<子协议>---驱动程序名称或数据库连接机制,子协议名称的典型例子就是ODBC ,如:jdbc:odbc:fff <子名称>---一种标识数据库的方法。子名称可以依据不同的子协议而发生变化 如:jdbc:dbnet//womat:356//fff ODBC子协议: Jdbc:odbc:<数据源名称>[;<属性名>=<属性值>] 如: jdbc:odbc:sqlconn:UID=sa;PWD=ww
10.2 JDBC编程 10.2.1 程序基本结构 一般的JDBC程序都完成三项功能:与数据库建立连接;传送SQL 声明以及对返回结果进行处理。下面我们通过一个具体例子说明这三项功能的实现过程。 例1: Creage.java给出了一个简单的JDBC程序,此程序执行后创建一张名为testTable的表,表中包括两个域,域名分别为id和name。 import java.net.URL; import java.sql.*; class Create{ public static void main (String[] args){ String url="jdbc:odbc:demo";
String query="CREATE TABLE testTable" + "(id INT,name CHAR(10))"; try{ //下载jdbc-odbc bridge 驱动器 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");//关于此句参见下面Cyclone的注释 //与驱动器建立连接 ,这里可使用不同的特定驱动,如jdbcfororacle或jdbcforsqlserver Connection con=DriverManager.getConnection(url,"user","password"); //创建一个Statement对象 Statement stmt=con.createStatement(); //执行SQL声明 stmt.executeUpdate(query);
System.out.println("Create successfully!"); //关闭 stm stmt.close(); //关闭连接 con.close(); }catch(SQLException ex){ //SQL异常信息 System.out.println("\n***SQLException caught ***\n"); while(ex!=null){ System.out.println("SQLState:"+ex.getSQLState()); System.out.println("Message:"+ex.getMessage()); System.out.println("Vendor:"+ex.getErrorCode());
ex=ex.getNextException(); System.out.println(""); } }catch(java.lang.Exception ex){ ex.printStackTrace(); } } } 2.2 Statement类及其子类 1)Statement接口
Statement stmt=con.createStatement(); //执行SQL声明 int count1=stmt.executeUpdate("INSERT INTO testTable(id,name) VALUES(1,'wu')"); int count2=stmt.executeUpdate("INSERT INTO testTable(id,name) VALUES(2,'wang')"); 2)PreparedStatement接口 String data[][]={{"5","xu"},{"6","yan"}}; PreparedStatement pstmt=con.prepareStatement("INSERT INTO testTable (id,name) VALUES(?,?)");
//参数赋值,执行SQL声明 for (int i=0;i<data.length;i++){ pstmt.setInt(1,Integer.parseInt(data[i][0])); pstmt.setString(2,data[i][1]); pstmt.executeUpdate(); } 3)CallableStatement接口 CallableStatement cstmt=con.prepareCall("{call Search(?)}"); //参数赋值 cstmt.setInt(1,934678); //执行储存过程。 cstmt.execute();
2.3 结果集ResultSet及ResultSetMetaData String query = "SELECT * FROM testTable"; Statement stmt=con.createStatement(); //发出查询要求,获得结果集 ResultSet rs=stmt.executeQuery(query); //显示结果集各行各列 System.out.println("The detail of testTable is:"); ResultSetMetaData rsmd=rs.getMetaData(); //获得结果集列数 in numCols=rsmd.getColumnCount(); //显示列标题 for(int i=1;i<=numCols;i++) {
if(i>1) System.out.print(","); System.out.print(rsmd.getColumnLabel(i)); } System.out.println(""); //显示结果集信息 while(rs.next()) { //显示一行 for(int i=1;i<=numCols;i++){
if(i>1) System.out.print(","); System.out.print(rs.getString(i)); } 2.4 DatabaseMetaData DatabaseMetaData dma = con.getMetaData(); //驱动器和URL信息 System.out.println("\nConnected to" + dma.getURL()); System.out.println("Driver" + dma.getDriverName()); System.out.println("Version" + dma.getDriverVersion()
//数据库信息 System.out.println("\nDataBase name:" + dma.getDatabaseProductName()+dma.getDatabaseProductVersion()); System.out.println("DataBase supports SQL keywords:\n\t" + dma.getSQLKeywords()); //数据库功能信息函数 dma.supportsANSI92EntryLevelSQL() dma.supportsANSI92FullSQL() dma.supportsStoredProcedures()
10.2.2 JDBC数据类型及类型转换 ⑴ JDBC的数据类型 JDBC的sql包中除了与数据库连接有关的抽象接口及与驱动器有关的DriverManager、DriverPropertyInfo等类型外,还定义了若干数据类,用以代表数据库中可能用到的SQL类型。下面我们就对它们逐一进行简略介绍。
① sql.Date sql包中的日期类Date是util包中Date类的子类,实际上也是util.Date类的子集。它只处理年月日,而忽略小时和分秒,用以代表SQL的DATE信息。 Date类的构造方法为: public Date(int year, int mouth, int day) 其中参数格式同util.Date类的构造方法一样,年参数为所需设定的年份减去1900所得的整数值,月参数为0至11,日参数为1至31。如1998年1月23日所对应创建日期类的方法调用为:
Date d=new Date(98,0,23); Date类还提供两个与String类互相转换的方法,分别是: public static Date valueOf(String s) 将字符串类参数转换为日期类对象。其中String类参数S的格式为“年-月-日”,加“1997-04-12”。 public String toString() 将日期类对象转换为String类对象表示,同样采用“年-月-日”的格式。
② sql.Time 该类是util.Date类的子类,也是它的一个子集。在Time类里,只处理小时和分秒,代表SQL的TIME类型。它与sql.Date合起来才表示完整的util.Date类信息。 Time类的构造方法为: public Time(int hour,int minute,int second) 其中小时参数值为0至23,分秒参数取值均为0至59。 与sql.Date一样,Time类也定义了两个与String类互相转换的函数ValueOf和String。不同的是String类对象的格式为“小时:分:秒”,如“12:26:06”。
③ sql.Timestamp 这个类也是util.Date类的子类,其中除了包含年月日、小时和分秒和信息之外,还加入了纳秒信息(nanosecond),1纳秒即1毫微秒。Timestamp类用来代表SQL时间戳(Timestamp)类型信息。 Timestamp类的构造方法为: public Timestamp(int year, int mouth, int date, int hour, int minute, int second, int nano)其中纳秒参数的取值从0至999,999,999,其余各参数同前。 Timestamp类特别定义了设置和获得纳秒信息的方法,分别是 public getnanos() 获取时间戳的纳秒部分 public void setNanos(int n) 以给定数值设置时间戳的纳秒部分
④ sql.Types Types类是Object类的直接子类。在这个类中以静态常量的形式定义了可使用的SQL的数值类型。所有这些类型常量都以前缀 public final static int的形式标明是公有静态整数,且不可改动。具体的类型名和含义如表11.1所示。其中OTHER用来代表数据库定义的特殊数据,可以用getObject或setObject方法将其映射为一个Java的Object对象。
⑵ SQL与Java 由于SQL数据类型与Java的数据类型不一致,因而在使用Java类型的应用程序与使用SQL类型的数据库之间,需要某种读写类型转换机制。实际上我们前面介绍的ResultSet类的“get”系列方法,Statement及其子类的“set“系列方法和registerOutParameter方法,都是这一转换机制的组成部分。 需要进行的读写转换包括下面情况: 从数据库中读取数值后,存放在ResultSet对象中的是SQL类型的数据。而调用“get”系列方法时,JDBC才将SQL类型转换为指定的Java类型。在一般情形下,SQL类型相对应的Java类型如表11-2所示。
⑵ SQL与Java 由于SQL数据类型与Java的数据类型不一致,因而在使用Java类型的应用程序与使用SQL类型的数据库之间,需要某种读写类型转换机制。实际上我们前面介绍的ResultSet类的“get”系列方法,Statement及其子类的“set“系列方法和registerOutParameter方法,都是这一转换机制的组成部分。 需要进行的读写转换包括下面情况: 从数据库中读取数值后,存放在ResultSet对象中的是SQL类型的数据。而调用“get”系列方法时,JDBC才将SQL类型转换为指定的Java类型。在一般情形下,SQL类型相对应的Java类型如表11-2所示。
⑶ 字符类型 JDBC 字符串数据类型为 CHAR、VARCHAR 和 LONGVARCHAR。
⑷ 二进制字符串类型 JDBC 二进制字符串类型为 BINARY、VARBINARY 和 LONGVARBINARY。
⑸ 精确数字类型 JDBC 精确数字类型直接映射到其对应的 SQL Server类型。
⑹ 近似数字类型 JDBC 近似数值数据类型为 REAL、DOUBLE和 FLOAT。
⑺ 日期时间类型 JDBC TIMESTAMP 类型映射到 SQL Server datetime 和 smalldatetime 类型。datetime 类型以两个 4 字节整数进行存储。smalldatetime 类型可存放相同的信息(日期和时间),但精度较低,为两个 2 字节的小整数。
10.3 JDBC与数据库的连接字符串 ⑴ MySQL(http://www.mysql.com)mm.mysql-2.0.2-bin.jar Class.forName( "org.gjt.mm.mysql.Driver" ); cn= DriverManager.getConnection( "jdbc:mysql://MyDbComputerNameOrIP:3306 /myDatabaseName", sUsr, sPwd ); ⑵ Oracle(http://www.oracle.com/ip/deploy/database/oracle9i/)classes12.zip Class.forName( "oracle.jdbc.driver.OracleDriver" ); cn= DriverManager.getConnection( "jdbc:oracle:thin:@ MyDbComputerNameOrIP:1521:ORCL", sUsr, sPwd );
⑶ Sybase(http://jtds.sourceforge.net)jconn2.jar Class.forName( "com.sybase..jdbc.SybDriver" ); cn = DriverManager.getConnection( "jdbc:sybase:Tds:MyDbComputerNameOrIP:2638", sUsr, sPwd ); //(Default-Username/Password: "dba"/"sql") ⑷ Microsoft SQLServer(http://www.microsoft.com) Class.forName( "com.microsoft.jdbc.sqlserver.SQLServerDriver" ); cn= DriverManager.getConnection( "jdbc:microsoft:sqlserver:// MyDbComputerNameOrIP:1433;databaseName=master", sUsr, sPwd );
⑸ ODBC Class.forName( "sun.jdbc.odbc.JdbcOdbcDriver" ); Connection cn = DriverManager.getConnection( "jdbc:odbc:" + sDsn, sUsr, sPwd ); ⑹ informix Class.forName("com.informix.jdbc.IfxDriver").newInstance(); String url ="jdbc:informix-sqli://123.45.67.89:1533/testDB:INFORMIXSERVER =myserver; user=testuser;password=testpassword";
⑺ sybase Class.forName( "com.sybase.jdbc.SybDriver" ) url="jdbc:sybase:Tds:127.0.0.1:2638/asademo"; SybConnection connection= (SybConnection) DriverManager.getConnection(url,"dba","sql");
例2: queryBook.jsp <%@page contentType="text/html;charset=gb2312"%> <%@page language="java" import="java.sql.*,java.io.*"%> <HTML> <BODY> <CENTER> <FONT SIZE = 5 COLOR = blue>图书信息浏览</FONT> <table border=3> <tr><td><b><center>ID号</td> <td><b><center>书名</td> <td><b><center>出版社</td> <td><b><center>价格</td></tr> <%
Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver").newInstance();Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver").newInstance(); Connection con=java.sql.DriverManager.getConnection("jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName=jspdb","sa",""); Statement stmt=con.createStatement(); ResultSet rst=stmt.executeQuery("select * from book;"); while(rst.next()) {
out.println("<tr>"); out.println("<td>"+rst.getString("bkld")+"</td>"); out.println("<td>"+rst.getString("bkName")+"</td>"); out.println("<td>"+rst.getString("bkPublisher")+"</td>"); out.println("<td>"+rst.getFloat("bkPrice")+"元"+"</td>"); out.println("</tr>"); }
rst.close(); stmt.close(); con.close(); %> </table> </BODY> </HTML>
10.4 连接池 ⑴ 连接池原理 连接池技术的核心思想是:连接复用,通过建立一个数据库连接池以及一套连接使用、分配、管理策略,使得该连接池中的连接可以得到高效、安全的复用,避免了数据库连接频繁建立、关闭的开销。另外,由于对JDBC中的原始连接进行了封装,从而方便了数据库应用对于连接的使用(特别是对于事务处理),提高了开发效率,也正是因为这个封装层的存在,隔离了应用的本身的处理逻辑和具体数据库访问逻辑,使应用本身的复用成为可能。连接池主要由三部分组成:连接池的建立、连接池中连接的使用管理、连接池的关闭。
⑵ 连接池的管理 连接池管理策略是连接池机制的核心。当连接池建立后,如何对连接池中的连接进行管理,解决好连接池内连接的分配和释放,对系统的性能有很大的影响。连接的合理分配、释放可提高连接的复用,降低了系统建立新连接的开销,同时也加速了用户的访问速度。下面介绍连接池中连接的分配、释放策略。
连接池的分配、释放策略对于有效复用连接非常重要,我们采用的方法是一个很有名的设计模式:Reference Counting(引用记数)。该模式在复用资源方面应用的非常广泛,把该方法运用到对于连接的分配释放上,为每一个数据库连接,保留一个引用记数,用来记录该连接的使用者的个数。具体的实现方法是:
当客户释放数据库连接时,先判断该连接的引用次数是否超过了规定值,如果超过就删除该连接,并判断当前连接池内总的连接数是否小于minConn(最小连接数),若小于就将连接池充满;如果没超过就将该连接标记为开放状态,可供再次复用。可以看出正是这套策略保证了数据库连接的有效复用,避免频繁地建立、释放连接所带来的系统资源开销。当客户释放数据库连接时,先判断该连接的引用次数是否超过了规定值,如果超过就删除该连接,并判断当前连接池内总的连接数是否小于minConn(最小连接数),若小于就将连接池充满;如果没超过就将该连接标记为开放状态,可供再次复用。可以看出正是这套策略保证了数据库连接的有效复用,避免频繁地建立、释放连接所带来的系统资源开销。
⑶ 连接池的配置 数据库连接池中到底要放置多少个连接,才能使系统的性能更佳,用minConn和maxConn来限制。minConn是当应用启动的时候连接池所创建的连接数,如果过大启动将变慢,但是启动后响应更快;如果过小启动加快,但是最初使用的用户将因为连接池中没有足够的连接不可避免的延缓了执行速度。因此应该在开发的过程中设定较小minConn,而在实际应用的中设定较大minConn。maxConn是连接池中的最大连接数,可以通过反复试验来确定此饱和点。为此在连接池类ConnectionPool中加入两个方法getActiveSize()和getOpenSize(),ActiveSize 表示某一时间有多少连接正被使用,OpenSize表示连接池中有多少连接被打开,反映了连接池使用的峰值。将这两个值在日志信息中反应出来, minConn的值应该小于平均ActiveSize,而maxConn的值应该在activeSize和OpenSize之间。
⑷ 连接池的关键技术 ① 事务处理 前面讨论的是关于使用数据库连接进行普通的数据库访问。对于事务处理,情况就变得比较复杂。因为事务本身要求原则性的保证,此时就要求对于数据库的操作符合"All-All-Nothing"原则,即要么全部完成,要么什么都不做。如果简单的采用上述的连接复用的策略,就会发生问题,因为没有办法控制属于同一个事务的多个数据库操作方法的动作,可能这些数据库操作是在多个连接上进行的,并且这些连接可能被其他非事务方法复用。Connection本身具有提供了对于事务的支持,可以通过设置Connection的AutoCommit属性为false,显式的调用 commit或rollback方法来实现。但是要安全、高效的进行连接复用,就必须提供相应的事务支持机制。方法是:采用显式的事务支撑方法,每一个事务独占一个连接。这种方法可以大大降低对于事务处理的复杂性,并且又不会妨碍连接的复用。