JDBC详解(2)

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应用的分层
分层就是对功能的隔离,降低层与层间的耦合性。

JDBC详解(2),古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。