1.加载驱动程序. 

 注册驱动程序有多方法,Class.forName();是一种显式地加载.当一个驱动程序类被Classloader装载后,在溶解的过程中,DriverManager会注册这个驱动类的实例.这个调用是自动发生的,也就是说DriverManager.registerDriver()方法被自动调用了, 

 Class.forName("oracle.jdbc.driver.OracleDriver"); 


 当然我们也可以直接调用DriverManager.registerDriver()来注册驱动程序,但是.MS的浏览中APPLET在调用这个方法时不能成功,也就是说MS在浏览器中内置的JVM对该方法的实现是无效的. 

 DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver()); 


 另外我们还可以利用系统属性jdbc.drivers来加载多个驱动程序: 

 System.setProperty("jdbc.drivers","driver1:driver2:.....:drivern");多个驱动程序之间用":"隔开,这样在连结时JDBC会按顺序搜索,直到找到第一个能成功连结指定的URL的驱动程序. 

 System.setProperty("jdbc.drivers","oracle.jdbc.driver.OracleDriver"); 


 2.通过DriverManager到得一个与数据库连结的句柄 

 在成功注册驱动程序后,我们就可以用DriverManager的静态方法getConnection来得到和数据库连结的引用: 

 Connection conn = DriverManager.getConnection(url); 

 如果连结是成功的,则返回Connection对象conn,如果为null或抛出异常,则说明没有和数据库建立连结. 

 对于getConnection()方法有三个重载的方法, 

 一种是最简单的只给出数据源即:getConnection(url), 

url = "jdbc:oracle:thin:@127.0.0.1:1521:ORCL"; 

conn = DriverManager.getConnection(url); 

 另一种是同时给出一些数据源信息即getConnection(url,Properties), 

Properties info = new Properties(); 

info.setProperty("user", "wzg"); 

info.setProperty("password", "wzg"); 

conn = DriverManager.getConnection(url, info); 

 另外一种就是给出数据源,用户名和密码:getConnection(url,user,passwod), 

String url = "jdbc:oracle:thin:@127.0.0.1:1521:ORCL"; 

String user ="wzg"; 

String password = "wzg"; 

conn = DriverManager.getConnection(url, user, password); 


Oracle的URL值是由连接数据库的协议和数据库的IP地址及端口号还有要连接的库名(DatebaseName) 

         Oracle URL的格式 

         jdbc:oracle:thin:(协议)@XXX.XXX.X.XXX:XXXX(IP地址及端口号):XXXXXXX(所使用的库名) 

         例:jdbc:oracle:thin:@192.168.0.39:1521:TARENADB 


 对于数据源信息.如果我们想在连结时给出更多的信息可以把这些信息压入到一个Properties,当然可以直接压入用户名密码,别外还可以压入指定字符集,编码方式或默认操作等一些其它信息. 


 3.通过连结句柄绑定要执行的语句. 

 在得到一个连结后,也就是有了和数据库找交道的通道.我们就可以做我们想要的操作了.还是先来介绍一些一般性的操作:如果我们要对数据库中的表进行操作,要先缘故绑定一个语句: 

 Statement stmt = conn.createStatement(); 

 然后利用这个语句来执行操作.可以有两种结果返回,如果执行的查询操作,返回为结果集ResultSet,如果执行更新操作,则返回操作的记录数int. 

 注意,SQL操作严格区分只有两个,一种就是读操作(查询操作),另一种就是写操作(更新操作),所以,create,insert,update,drop,delete等对数据有改写行为的操作都是更新操作. 


 ResultSet rs = stmt.executeQuery("select * from table where xxxxx"); 

 int x = stmt.executeUpdate("delete from table where ......"); 


 如果你硬要用executeQuery执行一个更新操作是可以的,但不要把它赋给一个句柄,当然稍微有些经验的程序员是不会这么做的. 

 至于对结果集的处理,我们放在下一节讨论,因为它是可操作的可选项,只有查询操作才返回结果集, 

 对于一次操作过程的完成,一个非常必要的步骤是关闭数据库连结,在你没有了解更多的JDBC知识这前,你先把这一步骤作为JDBC操作中最最重要的一步, 


 例子: 

 try{ 

 
Class.forName("org.gjt.mm.mysql.Driver"); 

 }catch(Exception e){ 

 
System.out.println("没有成功加载驱动程序:"+e.toString()); 

 
return; 

 } 

 Connection conn = null; 

 try{ 

 
conn = DriverManager.getConnection("jdbc:mysql://host:3306/mysql","user","passwd"); 

 
Statement stmt = conn.createStatement(); 

 
ResultSet rs = stmt.executeQuery("select * from table"); 

 
//rs 处理 

 
[rs.close();] 

 
[stmt.close();] 

 } 

 catch(Exception e){ 

 
System.out.println("数据库操作出现异常:"+e.toString()); 

 } 

 finally{ 

 
try{ 

 
conn.close(); 

 
}catch(Exception){ 

 
} 

 } 

 不管你以前是学习到的关于数据库流程是如何操作的,从现在开始,请你一定要把数据库关闭的代码写到finally块中,切切!  


 关于Statement对象: 

 前面说过,Statement对象是用来绑定要执行的操作的,在它上面有三种执行方法: 

 即用来执行查询操作的executeQuery(), 

ResultSet rs = stmt.executeQuery("select * from table"); 

 用来执行更新操作的executeUpdate() 

int x = stmt.executeUpdate("delete from table where ......"); 

 和用来执行动态的未知的操作的execute(). 

boolean flg = st.execute(sql); 

f(flg){ 

ResultSet rs= st.getResultSet(); 

}else{ 

int count = st.getUpdateCount(); 

} 

 execute(String sql),这个方法的返回值是boolean类型,如果返回true就表示sql是一个select语句,可以通过getResultSet()获得结果集,如果是false,sql就是DML语句或者是DDL语句。 


 JDBC在编译时并不对要执行的SQL语句检测,只是把它看着一个String,只有在驱动程序执行SQL语句时才知道正确与否. 


 注意: 

一个Statement对象同时只能有一个结果集在活动.这是宽容性的,就是说即使没有调用ResultSet的close()方法,只要打开第二个结果集就隐含着对上一个结果集的关闭.所以如果你想同时对多个结果集操作,就要创建多个Statement对象,如果不需要同时操作,那么可以在一个Statement对象上须序操作多个结果集. 


Connection conn = null; 

Statement stmt = null; 

conn = .......; 

stmt = conm.createStatement(xxxxxx); 

ResultSet rs = stmt.executeQuery(sql1); 

while(rs.next()){ 

 
str = rs.getString(xxxxx); 

 
ResultSet rs1 = stmt.executeQuery("select * from 表 where 字段=str"); 

} 

 当stmt.executeQuery("select * from 表 where 字段=str");赋给rs1时,这时隐含的操作是已经关闭了rs,所以如果要同时操作多个结果集一定要让它他绑定到不同的Statement对象上.好在一个connection对象可以创建任意多个Statement对象,而不需要你重新获取连结. 


 关于获取和设置Statement的选项:只要看看它的getXXX方法和setXXX方法就明白了,这儿作为基础知识只提一下以下几个: 

 setQueryTimeout,设置一个SQL执行的超时限制. 

 setMaxRows,设置结果集能容纳的行数. 

 setEscapeProcessing,如果参数为true,则驱动程序在把SQL语句发给数据库前进行转义替换,否则让数据库自己处理,当然这些默认值都可以通过get方法查询. 


 Statement的两个子类: 

 PreparedStatement:对于同一条语句的多次执行,Statement每次都要把SQL语句发送给数据库,这样做效率明显不高,而如果数据库支持预编译,PreparedStatement可以先把要执行的语句一次发给它,然后每次执行而不必发送相同的语句,效率当然提高,当然如果数据库不支持预编译,PreparedStatement会象Statement一样工作,只是效率不高而不需要用户工手干预.另外PreparedStatement还支持接收参数.在预编译后只要传输不同的参数就可以执行,大大提高了性能. 

PreparedStatement ps = conn.prepareStatement("select * from 表 where 字段=?"); 

ps.setString(1,参数); 

ResultSet rs = ps.executeQuery(); 


 CallableStatement:是PreparedStatement的子类,它只是用来执行存储过程的. 

CallableStatement sc = conn.prepareCall("{call query()}"); 

ResultSet rs = cs.executeQuery(); 


 SQL语句如果执行的是查询操作,那就要返回一个ResultSet对象,要想把查询结果最后明白地显示给用户,必须对ResultSet进行处理.ResultSet返回的是一个表中符合条件的记录,对ResultSet的处理要逐行处理,而对于每一行的列的处理,则可以按任意顺序 

 (注意,这只是JDBC规范的要求,有些JDBC实现时对于列的处理仍然要求用户按顺序处理,但这是极少数的).事实上,虽然你可以在处理列的时候可以按任意顺序,但如果你按从左到右的顺序则可以得到较高的性能. 


 这儿从底层来讲解一下ResultSet对象,ResultSet对象实际维护的是一个二维指针,第一维是指向当前行,最初它指向的是结果集的第一行之前,所以如果要访问第一行,就要先next(),以后每一行都要先next()才能访问,然后第二维的指针指向列,只要当你去rs.getXXX(列)时,才通过Connection再去数据库把真实的数据取出来,否则没有什么机器能真的把要取的数据都放在内存中.所以,千万要记住,如果Connection已经关闭,那是不可能再从ResultSet中取到数据的. 
有人问可不可以取到一个ResultSet把它写到Session中然后关闭Connection,这样就不要每次都连结了.想法非常好,但是是错误的!当然在javax.sql包中JDBC高级应用中有CacheRow和WebCacheRow可以把结果集缓存下来,但那和我们自己开一个数据结构把ResultSet的行集中所有值一次取出来保存起来没有什么两样.

访问行中的列,可以按字段名或索引来访问.下面是一个简单的检索结果的程序:
 ResultSet rs = stmt.executeQuery("select a1,a2,a3 from table"); 

 while(rs.next()){ 

 
int i = rs.getInt(1); 

 
String a = rs.getString("a2"); 

.............. 

 } 


 对于用来显示的结果集,用while来进行next()是最普通的,如果next()返回false,则说明已经没有可用的行了.但有时我们可能连一行都没有,而如果有记录又不知道是多少行,这时如果要对有记录和没有记录进行不同的处理,应该用以下流程进行判断: 


 if(rs.next()){ 

 
//因为已经先next()了,所经对记录应该用do{}while();来处理 

 
do{ 

 
int i = rs.getInt(1); 

 
String a = rs.getString("a2"); 

 
}while(rs.next()); 

} 

 esle{ 

 
System.out.println("没有取得符合条件的记录!"); 

 } 


 类型转换: 

 ResultSet的getXXX方法将努力把结果集中的SQL数据类型转换为JAVA的数据类型,事实大多数类型是可以转换的, 

 但仍然有不少糊弄是不能转换的,如你不能将一个SQL的float转换成JAVA的DATE,你无法将 VARCHAR "我们"转换成JAVA的Int. 


 较大的值: 

 对于大于Statement中getMaxFieldSize返回值的值,用普通的getBytes()或getString()是不能读取的,好在JAVA提供了读取输入流的方法, 

 对于大对象,我们可以通过rs.getXXXStream()来得到一个InputStream,XXX的类型包括Ascii,Binay,Unicode. 

 根据你存储的字段类型来使用不同的流类型,一般来说,二进制文件用getBinayStream(),文本文件用getAsciiStyream(), 

 对于Unicode字符的文本文件用getUnicodeStream(),相对应的数据库字段类型应该为:Blob,Clob和Nlob. 


 SQLException是检查异常必须处理要么throws ,要么try{}catch(){} 

 getErrorCode()可以获得错误码,可以对错误进行查询。 


 源数据 

 JDBC中有两种源数据,一种是数据库源数据,另一种是ResultSet源数据。 

 源数据就是描述存储用户数据的容器的数据结构。 

 ResultSet rs=ps.executeQuery(sql); 

 ResultSetMetaData rsmd=rs.getMetaData(); 


 rsmd.getColumnCount()返回列的个数. 

 getColumnLabel(int)返回该int所对应的列的显示标题 

 getColumnName(int)返回该int所对应的列的在数据库中的名称. 

 getColumnType(int)返回该int所对应的列的在数据库中的数据类型. 

 getColumnTypeName(int)返回该int所对应的列的数据类型在数据源中的名称. 

 isReadOnly(int)返回该int所对应的列是否只读. 

 isNullable(int)返回该int所对应的列是否可以为空 


 数据库源数据 

 DatabaseMetaData 

 getURL(),获得连接数据库的URL 

 getDatabaseProductName() 获得数据库产品的名称 

 getDriverVersion() 获得JDBC驱动程序的String形式的版本号 

 getTables()获得数据库中该用户的所有表 

 getUserName() 获得数据库用户名。 


 resultSet getTables(String catalog, String schemaPattern,String tableNamePattern,String[] types)  

 可以得到该库中"表"的所有情况,这里的表包括表,视图,系统表,临时空间,别名,同义词 

 对于各参数: 

 String catalog,表的目录,可能为null,"null"匹配所有 

 String schemaPattern,表的大纲,同上 

 String tableNamePattern,表名,同上 

 String[] types,表的类型,"null"匹配所有,可用的类型为:TABLE,VIEW,SYSEM TABLE,GLOBAL TEMPORARY,LOCAL  TEMPORARY,ALIAS,SYNONYM 


 例如: 

 DatabaseMetaData dbmd = conn.getMetaData(); 

 ResultSet rs = dbmd.getTables(null,null,null,null); 

 ResultSetMetaData rsmd = rs.getMetaData(); 

 int j = rsmd.getColumnCount(); 

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

 
out.print(rsmd.getColumnLabel(i)+"/t"); 

 } 

 out.println(); 

 while(rs.next()){ 

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

 
out.print(rs.getString(i)+"/t"); 

 
} 

 
out.println(); 

 } 

 对于更详细的表中的列的信息,可以用dbmd(不是rsmd).getColumns(String catalog,String schemaPattern,String tableNamePattern,String columnNamePattern) 

 不仅可以获得rsmd中的信息,还可以获得列的大小,小数位数,精度,缺省值,列在表中 

 的位置等相关信息. 

 还有两个方法,调用和获取表信息一样,可以获得存储过程和索引的信息: 

 ResultSet getProcedures(String catalog,String schemaPattern,String procedurePattern); 

 ResultSet getIndexINFO(String catalog,String schemaPattern,String table,boolean unique,boolean approximate); 


 事务(Transaction) 

 事务是针对原子操作的,要求原子操作不可再分,要求原子操作必须同时成功同时失败。事务是捆绑的原子操作的边界。 


 JDBC中使用事务,先要使用连接调用setAutoCommite(false)方法,把自动提交(commit)置为false。打开事务就要关闭自动提交。不用事务是要把setAutoCommite(true) 

 在处理事务时,在发送sql语句后执行成功并确认时,就在try块中使用连接调用commit()方法来发送提交信息,在发送sql语句后执行失败时, 

 会在catch语句块中使用连接调用rollback()方法来发送回滚信息,也可以在需要时做回滚操作(主观原因)。 


 JDBC事务并发产生的问题和事务隔离级别 

 1,脏读(dirty read),读取到了没有提交的数据。 

 2,不可重复读(UnPrpeatable Read),两次读取到了不同的数据,就是要保持在同一时间点上两次读取到的数据相同,不能够使查询数据时进行改变。 

 3,幻读(phantom),在两次查询同一时间点数据时,数据数量发生改变,要保持在同一时间点上两次读取到的数据相同。 


 事务隔离级别 

 TRANSACTION_NONE不使用事务。 

 TRANSACTION_READ_UNCOMMITTED 可以读取为提交数据。 

 TRANSACTION_READ_COMMITTED可以避免脏读,不能够读取没提交的数据,最常用的隔离级别  大部分数据库的默认隔离级别 

 TRANSACTION_REPEATABLE_READ可以避免脏读,重复读取, 

 TRANSACTION_SERIALIZABLE可以避免脏读,重复读取和幻读,(事务串行化)会降低数据库效率 


 以上的五个事务隔离级别都是在Connection类中定义的静态常量,使用setTransactionIsolation(intlevel) 方法可以设置事务隔离级别。 


 JDBC2.0新特性 

 可滚动结果集(可双向滚动),这种结果集不但可以双向滚动,相对定位,绝对定位,并且可以修改数据信息。 

 滚动特性 

 next(),此方法是使游标向下一条记录移动。 

 previous() ,此方法可以使游标上一条记录移动,前提前面还有记录。 

 absolute(int row),可以使用此方法跳到指定的记录位置。定位成功返回true,不成功返回false,返回值为false,则游标不会移动。 

 afterLast() ,游标跳到最后一条记录之后,(结果集一回来时就有的位置)。 

 beforeFirst() ,游标跳到第一条记录 之前,(结果集一回来时就有的位置)。(跳到游标初始位) 

 first(),游标指向第一条记录。 

 last(),有彪指向最后一条记录。 

 relative(int rows) ,相对定位方法,参数值可正可负,参数为正,游标从当前位置向下移动指定值,参数为负,游标从当前位置向上移动指定值。 


 TYPE_FORWARD_ONLY ,单向,该常量指示指针只能向前移动的 ResultSet 对象的类型。不可滚动。 

 TYPE_SCROLL_INSENSITIVE ,双向,该常量指示可滚动但通常不受其他的更改影响的 ResultSet 对象的类型。 

 TYPE_SCROLL_SENSITIVE ,双向,该常量指示可滚动并且通常受其他的更改影响的 ResultSet 对象的类型。该特性某些数据库不支持。 


 要使用可滚动结果集时,要在Statement创建时指定参数,才可以使用 

 Statement st=null;(int,int)(可滚动特性,可更新特性) 

 st=con.createStatement(ReusltSet.TYPE_SCROLL_INSENSITIVE,ResuleSet.CONCUR_UPDATABLE) 


 ResultSet结果集中,先使用moveToInsertRow(),将游标移到和结果集结构类似的缓冲区中 

 然后可以使用updateXxx(intcolumn,columnType value)方法来更新指定列数据,再使用insertRow() 方法插入记录,最后将游标指回原位, 

 moveToCurrentRow() 。 


 能否使用可更新结果集,要看使用的数据库驱动是否支持,还有只能用于单表且表中有主键字段(可能会是联合主键),不能够有表连接, 

 会取所有非空字段且没有默认值。结果集用select * from t也不行,不能用*,不能排序 

 能否使用JDBC2.0ResultSet的新特性要看数据库驱动程序是否支持。 


 批处理更新 

 Statement.addBatch(String sql), 方法会在批处理缓存中加入一条sql语句 

 executeBatch() ,执行批处理缓存中的所有sql语句。 


 PreparedStatement.   先准备一组参数 

 addBatch() 将一组参数添加到此 PreparedStatement 对象的批处理命令中。 

 executeBatch() 将一批命令提交给数据库来执行,如果全部命令执行成功,则返回更新计数组成的数组。 


 PreparedStatement中使用批量更新时,要先设置好参数后使用addBatch()方法加入缓存。 

 注意:批量更新中只能使用更新或插入语句 


 SQL3.0中的行类型 

 Array,数组 

 Sturct,结构类似给该列类型起个描述性的别名 

 Blob,大的二进制数据文件        

 create table t_blob( 

     idnumber(12) primary key, 

     filename varchar(20), 

     blobData blob); 

  ps=con.prepareStatement("insert intot_blob" +"values(?,?,empty_blob())");。 


 Clob,大文本文件对象。 

 在使用上述大对象的时候,在使用JDBC插入记录时要先插入一个空的占位对象,然后使用 

 select blobdata from t_blob where id =" + id + " for update 这样的语法来对获得的大对象,进行实际的写入操作  

 Blod通过getBinaryOutputStream()方法获取流进行写入。getBinaryStream()方法获得流来获取blob中存储的数据。 


 clob的操作也和blob相同。getAsciiStream()方法用于读取存储的文本对象,getAsciiOutputStream()方法之获得流用来向文件对象写入的。 


 JDBC2.0扩展 

 JNDI和DataSourse 

 JNDI,(命名路径服务)也用于存储数据,但是他所存储的是一写零散的信息。 

 JNDI的方法是在javax.naming包下 

 bind(String name, Object obj) 将名称绑定到对象资源,建立指定的字符串和对象资源的关联 

 lookup(String name) ,通过指定的字符串获得先前绑定的资源 


 以下是将资源和JNDI命名绑定的方法 

  public static void bind(String context, Object obj) throwsNamingException 

     { 

         Properties pro = new Properties(); 

        //Weblogic的JNDI服务器参数 

        pro.put(InitialContext.INITIAL_CONTEXT_FACTORY,"weblogic.jndi.WLInitialContextFactory"); 

        pro.put(InitialContext.PROVIDER_URL, "t3://localhost:7001"); 

    

        Context ctx = new InitialContext(pro); 

        ctx.bind(context, obj);//建立指定的字符串和对象资源的关联 

     } 


 DataSourse(数据源),包含了连接数据库所需的信息,可以通过数据源或的数据库连接,有时由于某些连接数据库的信息会变更, 

 所以经常使用包含数据库连接信息的数据源。 

 通过JNDI获得绑定的资源 

  public static Object lookup(String context)throws NamingException 

     { 

         Properties pro = new Properties(); 

        //Weblogic的JNDI服务器参数 

        pro.put(InitialContext.INITIAL_CONTEXT_FACTORY,"weblogic.jndi.WLInitialContextFactory"); 

        pro.put(InitialContext.PROVIDER_URL, "t3://localhost:7001"); 

        Context ctx = new InitialContext(pro); 

        return ctx.lookup(context);//通过指定的字符串获得先前绑定的资源。 

     } 


 连接池,保持连接池中有指定个数的连接,并在程序使用过之后不关闭连接,再放回连接池中等待其他的程序在需要时来取用, 

 这样可以大量的节省销毁和创建连接的资源消耗。 


 JTA分布式的事务 

 分布式事务是针对多个不同数据库同时操作,要保证原子操作的不可分,也不用再自己写commit,和rollback,全部都交给中间服务器来处理。 

 (两阶段提交),也就是在中间服务器发送sql语句等待数据库回应,都回应操作成功才提交,否则同时回滚。 


 RowSet 

 行集,这是一个JavaBean(事件机制),它增强了ResultSet的功能,通过RowSet可以获得数据源,设置隔离级别,也可以发送查寻语句, 

 也实现了离线的操作遍历,RowSet也支持预编译的Statement。 

 RowSet中的方法大致上和ResultSet相同,当需要使用时请查阅JAVA API参考文档。 


 面向对象的数据库设计 

 Id通常是用来表示记录的唯一性的,通常会使用业务无关的数字类型 

 Object id 对象的id,sequence只有Oracle才可用,对象id(OID)使用高低位算法先生成高位,在生成低位,通过运算获得对象id。 

 类应当对象到表,属性对应字段,对象对应记录。 

 类继承关系对应表, 

 1,每个类建一个表,为父子类每个类都对应的创建表,这种方法类关系清晰,但是如果类比较多就不适合了 

 2,只有具体类才建表,也就是把父类中的属性均匀分配到子类的表中,也就是父类不建表,这种表关系不能使用多态 

 3,所有类对应一张表,这种方法是在标中加上一个字段来区分父子类,但是只能用于类属性较少的情况下,而且数据会有冗余。 

 类关联关系对应表 

 1,一对一关联,类关系对应成表时有两种做法,一是引用主键,也就是一方引用另一方的主键既作为外键有作为自身的主键。二是外键引用,一方引用另一方的主键作为自身的外键,并且自己拥有主键。 

 2,一对多关联,也就是多端引用一端的主键当作外键,多端自身拥有主键。 

 3,多对多关系,多对多关系是通过中间表来实现的,中间表引用两表的主键当作联合主键,就可以实现多对多关联。 


 JDCB应用的分层 

 分层就是对功能的隔离,降低层与层间的耦合性。