JDBC具体解释(2)

时间:2021-08-03 09:56:13

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);

第二种就是给出数据源,username和password: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地址及port号还有要连接的库名(DatebaseName)

        Oracle URL的格式

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

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



对于数据源信息.假设我们想在连结时给出很多其他的信息能够把这些信息压入到一个Properties,当然能够直接压入usernamepassword,别外还能够压入指定字符集,编码方式或默认操作等一些其他信息.



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() 获得数据库username。



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的JNDIserver參数

       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的JNDIserver參数

       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,所有都交给中间server来处理。

(两阶段提交)。也就是在中间server发送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应用的分层

分层就是对功能的隔离。减少层与层间的耦合性。