Servlet&JSP 第九章 整合数据库

一、JDBC入门

1、JDBC简介

(1)数据库本身是个独立运行的应用程序,编写的应用程序是利用网络通信协议与数据库进行命令交换,以进行数据的增删查找。通常应用程序会利用一组专门与数据库进行通信协议的程序库,以简化与数据库沟通时的程序编写。不同的数据库通常会有不同的通信协议,用以连接不同数据库的程序库在API上也会有所不同。

(2)JDBC全名Java DataBase Connectivity,是Java数据库连接的标准规范。具体而言,它定义一组标准类与接口,应用程序需要连接数据库时就调用这组标准API,而标准API中的接口会由数据库厂商实现,通常称为JDBC驱动程序(Driver)。

(3)JDBC标准主要分为两个部分:JDBC应用程序开发者接口以及JDBC驱动程序开发者接口。如果应用程序需要连接数据库,就是调用JDBC应用程序开发者接口,相关API在java.sql与javax.sql两个包中。JDBC驱动程序开发者接口则是数据库厂商要实现驱动程序时的规范。

例1、使用JDBC连接数据库
Connection conn=DriverManager.getConnection(...);
Statement stmt=conn.createStatement();
ResultSet rs=stmt.executeQuery(“select * from user”);

假设这段代码是连接MySQL数据库,则需要在Classpath中设置JDBC驱动程序,也就是在Classpath中设置一个JAR文件。

如果将来要换为Oracle数据库,那么只要置换Oracle驱动程序,也就是在Classpath改设为Oracle驱动程序的JAR文件。

(4)如果开发应用程序需要操作数据库,是通过JDBC所提供的接口来设计程序,则理论上在必须更换数据库时,应用程序无须进行更改,只需要更换数据库驱动程序实现,即可对另一个数据库进行操作。

(5)JDBC希望达到的目的是希望在编写数据库操作程序时,可以有个统一的接口,无须依赖于特定的数据库API,希望达到“写一个Java程序,操作所有的数据库”。

(6)厂商在实现JDBC驱动程序时,按方式可将驱动程序分为4种类型:

Type 1:JDBC-ODBC Bridge Driver。ODBC是由Microsoft主导的数据库连接标准,ODBC在Microsoft的系统上最为成熟,Microsoft Access数据库访问就是使用ODBC。Type 1驱动程序会将JDBC调用转换为对ODBC驱动程序的调用,由ODBC驱动程序来操作数据库。由于JDBC与ODBC并非一对一的对应,所以部分调用无法直接转换,ODBC驱动程序本身有跨平台的限制。

Type 2:Native API Driver。这个类型的驱动程序会以原生的方式。调用数据库提供的原生程序库(通常由C/C++实现),JDBC的方法调用会转换为原生程序库中的相关API调用。不能跨平台,速度上有机会称为4种类型中最快的驱动程序。

Type 3:JDBC-Net Driver。这个类型的JDBC驱动程序会将JDBC的方法调用,转换为特定的网络协议调用,目的是与远程与数据库特定的中介服务器或组件进行协议操作,而中介服务器或组件再真正与数据库进行操作。这个类型的驱动程序是可以跨平台的,弹性高。

Type 4:Native Protocol Driver。这个类型的驱动程序实现通常由数据库厂商直接提供,驱动程序实现会将JDBC的调用转换为与数据库特定的网络协议,以与数据库进行沟通操作。可跨平台,最常见的驱动程序类型。

2、连接数据库

为了要连接数据库系统,必须要有厂商实现的JDBC驱动程序,可以将驱动程序JAR文件放在Web应用程序的/WEB-INF/lib文件夹中。基本数据库操作相关的JDBC接口或类位于java.sql包中。要取得数据库连接,必须有几个操作:注册Driver实现对象、取得Connection实现对象、关闭Connection实现对象。

(1)注册Driver实现对象

实现Driver接口的对象是JDBC进行数据库访问的起点,MySQL实现的驱动程序,com.mysql.jdbc.Driver类实现了java.sql.Driver接口,管理Driver实现对象的类是java.sql.DriverManager,基本是必须调用其静态方法registerDriver()进行注册:

DriverManager.registerDriver(com.mysql.jdbc.Driver());

实际上只要想办法加载Driver接口的实现类.class文件,就会完成注册。

例2、通过java.lang.Class类的forName(),动态加载驱动程序类
try{
Class.forName(“com.mysql.jdbc.Driver”);
}catch(ClassNotFoundException e){
Threw new RuntimeException(“找不到指定的类”);
}

使用JDBC时,要求加载.class文件的方式有4种:使用Class.forName()、自行创建Driver接口实现类的实例(java.sql.Driver driver=new com.mysql.jdbc.Driver();)、启动JVM时指定jdbc.drivers属性(运行Java命令:>java -Djdbc drivers=com.mysql.jdbc.Driver;ooo.XXXDriver YourProgram)、设置JAR中/services/java.sql.Driver文件。

应用程序可能同时连接多个厂商的数据库,所以DriverManager也可以注册多个驱动程序实例,如果需要指定多个驱动程序类时,就是用分号隔开。

(2)取得Connection实现对象

Connection接口的实现对象,是数据库连接的代表对象。要取得Connection实现对象,可以通过DriverManager的getConnection():

Connection conn=DriverManager。getConnection(jdbcURL,username,password);

除了基本的用户名、密码之外,还必须提供JDBC URL,其定义了连接数据库时的协议、子协议、数据源标识:协议:子协议:数据源标识

MySQL中,“子协议”是桥接的驱动程序、数据库产品名称或连接机制,若使用MySQL,子协议的名称是mysql,“数据源标识”标出数据库的地址、端口、名称、用户、密码等信息。

MySQL的 JDBC URL编写方式

jdbc:mysql://主机名称:连接端口/数据库名称?参数1=值&参数2=值

主机名称可以是本机或其他连接主机名称、地址,MySQL连接端口默认是3306

例3、连接demo数据库,并指明用户名和密码
jdbc:mysql://localhost:3306/demo?Username=root&password=123456

如果需要使用中文访问,还必须给定参数useUnicode及characterEncoding,表明是否使用useUnicode,并指定字符编码方式。

例4、数据库表格编码使用UTF8
jdbc:mysql://localhost:3306/demo?Username=root&password=123456&useUnicode=true&characterEncoding=UTF8

有时会将JDBC URL编写在XML配置文档中,此时不能直接在XML中写&符号,而必须改写为&;替代字符

例5、jdbc:mysql://localhost:3306/demo?Username=root&password=123456&useUnicode=true&;characterEncoding=UTF8

SQLException是在处理JDBC时经常遇到的一个异常对象,为数据库操作过程发生错误时的代表对象,SQLException是受检异常,必须使用try...catch明确处理,在异常发生时尝试关闭相关资源。SQLException有个子类SQLWarning,如果数据库执行过程中发生了一些警示信息,会创建SQLWarning但不会抛出,而是以链接方式收集起来,可以使用Connection、Statement、ResultSet的getWarnings()来取得第一个SQLWarning,使用这个对象的getNextWarning()可以取得下一个SQLWarning,由于它是SQLException的子类,所以必要时也可以当作异常抛出。

(3)关闭Connection实现对象

取得Connection对象之后,可以使用isClosed()方法测试与数据库的连接是否关闭,在操作完数据库后,若确定不再需要连接,则必须使用close()来关闭与数据库的连接,以释放连接时相关的必要资源,如连接相关对象、授权资源等。

DriverManager会在循环中逐一取出注册的每一个Driver实例,使用指定的JDBC URL来调用Driver的connect()方法,尝试取得Connection实例。Driver的connect()方法在无法取得Connection时会返回null,所以简单来说,DriverManager就是逐一使用Driver实例尝试连接。如果连接成功就返回Connection对象,如果其中有异常发生,DriverManager会记录第一个异常,并继续尝试其他的Driver。在所有的Driver都试过了也无法取得连接,若原先尝试过程中有记录异常就抛出,没有的话,也是抛出异常告知没有适合的驱动程序。

3、使用Statement、ResultSet

(1)若要执行SQL,必须取得java.sql.Statement对象,它是SQL语句的代表对象,可以使用Connection的createStatement()来创建Statement对象:

Statement stme=conn.createStatement();

(2)取得Statement对象之后,可以使用executeUpdate()、executeQuery()等方法来执行SQL。executeUpdate()主要是用来执行create table、insert、drop table、alter table等会改变数据库内容的SQL。executeUpdate()会返回int结果,表示数据变动的笔数。

例6、stmt.executeUpdate(“insert into message values(1,‘justin’,“+”‘justin@email.com’,‘message...’)”)

Statement的executeQuery()方法则是用于select等查询数据库的SQL,executeQuery()会返回java.sql.ResuleSet对象,代表查询的结果,查询的结果会是一笔一笔的数据。

(3)可以使用ResultSet的next()来移动至下一笔数据,它会返回true或false表示是否有下一笔数据,接着可以使用getXXX()来取得数据,如getString()、getInt()、getFloat()、getDouble()等方法,分别取得相对应的字段类型数据。getXXX()方法都提供有依据字段名称取得数据,或是依据字段顺序取得数据的方法。

例7、指定字段名称取得数据
ResultSet rs=stmt.executeQuery(“select * from message”);
while(rs.next()){
int id=rs.getInt(“id”);
String name=rs.getString(“name”);
String email=rs.getString(“email”);
//...
}

例8:使用查询结果的字段顺序来显示结果(索引是从1开始)
ResultSet rs=stmt.executeQuery(“select * from message”);
while(rs.next()){
int id=rs.getInt(1);
String name=rs.getString(2);
String email=rs.getString(3);
//...
}

(4)Statement的execute()可以用来执行SQL,并可以测试所执行的SQL是执行查询或更新,返回true的话表示SQL执行将返回ResultSet表示查询结果,此时可以使用getResultSet()取得ResultSet对象。如果execute()返回是false,表示SQL执行会返回更新笔数或没有结果,此时可以使用getUpdateCount()取得更新笔数。如果事先无法得知是进行查询或更新,就可以使用execute()。

例9、if(stmt.execute(sql)){
ResultSet rs=stmt.getResultSet(); //取得查询结果
...
}else{
int updated=stmt.getUpdateCount(); //取得更新笔数
...  
}

(5)视需求而定,Statement或ResultSet在不使用时,可以使用close()将之关闭,以释放相关资源,Statement关闭时,所关联的ResultSet也会自动关闭。

4、使用PrepareStatement、CallableStatement

(1)如果有些操作只是SQL语句中某些参数会有所不同,其余的SQL子句皆相同,则可以使用java.sql.PrepareStatement。可以使用Connection的prepareStatement()方法创建好一个预编译的SQL语句,当中参数会有变动的部分,先指定“?”这个占位字符。

例10、PrepareStatement stmt=conn.prepareStatement(“insert into message values(?,?,?)”);

等到需要真正指定参数执行时,在使用相对应的setInt()、setString()等方法,指定“?”处真正应该有的参数。

例11、stmt.setInt(1,2);
stmt.setString(2,“momor”);
stmt.setString(3,“momor@email.com”);
stmt.executeUpdate();
stmt.clearParameters();

要让SQL执行生效,需执行executeUpdate()或executeQuery()方法(如果是查询的话),在这次的SQL执行完毕后,可以调用clearParameters()清除所设置的参数,之后就可以再使用这个PrepareStatement实例。必要的话,可以考虑制作语句池将一些频繁使用的PrepareStatement重复使用,减少生成对象的负担。

(2)在驱动程序支持的情况下,使用PrepareStatement,可以将SQL语句预编译为数据库的运行命令,由于已经是数据库的可执行命令,运行速度可以快许多。而Statement对象实在执行时将SQL直接送到数据库,有数据库做解析、直译再执行。

例12、SQL Injection隐患(SQL注入)
select * from user where username=‘caterpillar’and password=‘’ or ‘1’=‘1’

and子句之后的判断是永远成立,也就是说,用户不用输入正确的密码,也可以查询出所有的密码。以串接的方式组合SQL语句基本上都会有SQL Injection的隐患。

例13、使用PrepareStatement避免SQL Injection隐患
PrepareStatement stmt=conn.getPrepareStatement(“select * from user where username=? and password=?”);
Stmt.setString(1,username);
Stmt.setString(2,password);

在这里username和password被视作是SQL中纯粹的字符串,而不会被当作SQL语法来解释。

由于+串接字符串会产生新的String对象,如果串接字符串动作经常进行(例如在循环中进行SQL串接动作),那么是性能负担上的隐忧,如果真的非得串接SQL,至少要考虑使用StringBuffered或StringBuilder。

(3)如果编写数据库的存储过程,并想使用JDBC来调用,则可使用java.sql.CallableStatement。调用的基本语法:

{?=call <程序名称> [<自变量1>,<自变量2>,...]}
{call <程序名称> [<自变量1>,<自变量2>,...]}

CallableStatement必须调用prepareCall()创建CallableStatement时异常,使用setXXX()设置参数,如果是查询操作,使用executeQuery()。如果是更新操作,使用executeUpdate()。可以使用registerOutParameter()注册输出参数等。

(4)在使用PrepareStatement或CallableStatement时,必须注意SQL类型与Java数据类型的对应,因为两者本身并不是一对一对应,java.sql.Types定义了一些常数代表SQL类型。

 

时间日期在JDBC中,并不是使用java.util.Date,在JDBC中要表示日期,是使用java.sql.Date,其日期格式是“年、月、日”;要表示时间的话则是使用java.sql.Time,其时间格式为“时、分、秒”;如果要表示“时、分、秒、微秒”的格式,则是使用java.sql.Timestamp。

二、JDBC进阶

1、使用DataSource取得连接

(1)要取得数据库连接,必须打开网络连接(中间经过实体网络),连接至数据库服务器后,进行协议交换(数次的网络数据往来)以进行验证码名称、密码等确认动作。也就是取得数据库连接是件耗资源的动作,尽量利用已打开的连接,也就是重复利用取得的Connection实例,是改善数据库连接性能的一个方式,采用连接池是基本方式。

(2)Java EE的环境中,将取得连接等与数据库来源相关的行为规范在javax.sql.DataSource接口,实际如何取得Connection则由实现接口的对象负责。

(3)为了让应用程序在需要取得某些与系统相关的资源对象时,能与实际的系统资源配置、实体机器位置、环境架构等无关,在Java应用程序中可以通过JDNI来取得所需要的资源对象。

例14、在Web应用程序中获得DataSource实例
try{
Context initContext=new InitialContext();
Context envContext=(Context)envContext.lookup(“jdbc/demo”);
}catch(NamingException ex){
...
}

在创建Context对象的过程中会收集环境相关数据,之后根据JNDI名称jdbc/demo向JNDI服务器查找DataSource实例并返回,在这个代码段中不会知道实际的资源配置、实体机器配置、环境架构等信息,应用程序不会与这些信息发生相关。

(4)设置JNDI相关资源,则可以要求Web应用程序在封装为WAR文件时,必须在META-INF文件夹中包括一个context.xml。

例15、
<Resource 那么=“jdbc/demo” auth=“Container” type=“javax.sql.DataSource”
maxActive=”100” maxIndle=”30” maxWait=”1000” 
username=”root” password=”123456”
driverClassName=”com.mysql.jdbc.Driver”
url=”jdbc:mysql://localhost:3306/demo?Username=root&password=123456&useUnicode=true&;characterEncoding=UTF8
>

name属性是设置JNDI名称,username和password是数据库用户名与密码,driverClassName为驱动程序类名称,url为JDBC URL,因为是编写在xml文件中,所以&要以&;取代,其他的属性设置则是与DBCP有关,这是内置在Tomcat中的连接池机制。当应用程序部署之后,Tomcat会根据META-INF中context.xml的设置,寻找指定的驱动程序,所以必须将驱动程序的JAR文件放置在Tomcat的lib目录中,接着Tomcat就回为JDNI名称设置相关的资源。

2、使用ResultSet卷动、更新数据

(1)ResultSet时,默认可以使用next()移动数据光标至下一个数据,而后使用getXXX()方法来取得数据。实际上,ResultSet不仅可以使用previous()、first()、last()等方法前后移动数据光标,还可以调用updateXXX()、updateRow()等方法进行数据修改。

(2)在使用Connection的createStatement()或prepareStatement()方法创建Statement或PrepareStatement实例时,可以指定结果集类型与并行方式:

createStatement(int resultSetType,int resultSetConcurrency);
prepareStatement(String sql,int resultSetType,int resultSetConcurrency);

结果集类型可以指定三种设置:

ResultSet.TYPE_FORWARD_ONLY(默认):ResultSet只能前进数据光标。

ResultSet.TYPE_SCROLL_INSENSITIVE:ResultSet可以前后移动数据光标,取得的ResultSet不会反应数据库中的数据修改。

ResuleSet.TYPE_SCROLL_SENSITIVE:ResultSet可以前后移动数据光标,取得的ResultSet会反应数据库中的数据修改。

更新设置可以有两种指定:

ResultSet.CONCUR_READ_ONLY(默认):只能用ResultSet进行数据读取,无法进行更新。

ResultSet.CONCUR_UPDATABLE:可以使用ResultSet进行数据更新。

例16、Statement指定前后移动数据光标并使用ResultSet进行更新
Statement stmt=conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_UPDATABLE);

例17、使用PrepareStatement指定前后移动数据光标并使用ResultSet进行更新
PrepareStatement stmt=conn.prepareStatement(“select * from message”,ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_UPDATABLE);

(3)在数据光标移动的API上,可以使用absolute()、afterLast()、beforeFirst()、first()、last()进行绝对位置移动,使用relative()、previous()、next()进行相对位置移动,这些方法如果成功移动就会返回true,也可以使用isAfterLast()、isBeforeFirst()、isFirst()、isLast()判断目前位置。

例18、
Statement stmt=conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
ResultSet rs=stmt.executeQuery();
rs.absolute(2); //移至第二行
rs.next(); //移至第三行
rs.first(); //移至第一行
boolean b=ra.isFirst(); //b为true

(4)如果使用ResultSet进行数据修改,则有些条件限制:必须选择单一表单、必须选择主键、必须选择所有NOT NULL的值。

(5)在取得ResultSet之后要进行数据更新,必须移动至要更新的行,调用updateXXX()方法(XXX是类型),而后调用updateRow()方法完成更新。如果调用cancelRowUpdates()可取消更新,但必须在调用updateRow()之前进行更新的取消。

例19、使用ResultSet更新数据
Statement stmt=conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
ResultSet rs=stmt.executeQuery();
rs.next();
rs.updateString(3,“caterpillar@openhome.cc”);
rs.updateRow();

(6)如果取得ResultSet后想直接进行数据的新增,则要先调用moveToInsertRow(),之后调用updateXXX()设置要新增的数据各个字段,然后调用insertRow()新增数据。

例20、使用ResultSet新增数据
Statement stmt=conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
ResultSet rs=stmt.executeQuery();
rs.moveToInsertRow();
rs.updateString(2,“momor”);
rs.insertRow();
rs.moveToCurrentRow();

(7)如果取得ResultSet后想直接进行数据的删除,则要移动数据光标至想删除的列,调用deleteRow()删除数据列。

例21、使用ResultSet删除数据
Statement stmt=conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
ResultSet rs=stmt.executeQuery();
rs.absolute(3);
rs.deleteRow();

3、批次更新

(1)每一次执行executeUpdate(),都会向数据库发送一次SQL,如果大量更新的SQL有一万次,就等于通过网络进行了一万次的信息传送,网络传送信息实际上必须启动I/O、进行路由等动作,这样进行大量更新,性能上不好。

(2)可使用addBatch()方法来收集SQL,并用executeBatch()方法将所收集的SQL传送出去。addBatch()使用了ArrayList来收集SQL。所有收集的SQL,最后会串为一句SQL,然后传送给数据库,节省了I/O、网络路由等操作所消耗的时间。使用executeBatch()时,SQL执行的顺序就是addBatch()时的顺序,executeBatch()会返回int[],代表每笔SQL造成的数据异动列数。执行executeBatch()时,先前已打开的ResultSet会被关闭,执行过后收集SQL会被清空,任何的SQL错误会抛出BatchUpdateException,可以使用这个对象的getUpdateCounts()取得int[],代表先前执行成功的SQL做造成的异动笔数。

例22、Statement使用批次更新
Statement stmt=conn.createStatement();
while(someCondition){
stmt.addBatch(“insert into message(name,email) values(‘...’,’...’));
}
stmt.executeBatch();
例23、PrepareStatement使用批次更新
PrepareStatement stmt=conn.prepareStatement();
while(someCondition){
“insert into message(name,email) values(‘...’,’...’);
stmt.setString(1,”..”);
stmt.setString(2,”..”);
stmt.setString(3,”..”);
stmt.addBatch(); //收集参数
}
stmt.executeBatch(); //送出所有参数

(3)批次更新仅用在更新操作,SQL不能是select,否则会抛出异常。

4、Blob与Clob

如果要将文件写入数据库,可以在数据库表格字段上使用BLOB或CLOB数据类型。BLOB全名Binary Large Object,用于存储大量的二进制数据,如图片、影音文件等。CIOB全名Character Large Object,用于存储大量的文字数据。

(1)JDBC中提供了java.sql.Blob与java.sql.Clob两个类分别代表BLOB与CLOB数据。以Blob为例,写入数据时,可以通过PrepareStatement的setBlob()来设置Blob对象,读取数据时,可以铜鼓ResultSet的getBlob()取得Blob对象。

Blob拥有getBinaryStream()、getBytes()等方法,可以取得代表字段来源的InputStream或字段的byte[]数据。

Clob拥有getCharacterStream()、getAsciiStream()等方法,可以取得Reader或InputStream等数据,。

(2)可以把BLOG字段对应byte[]或输入/输出串流。在写入数据时,可以使用PrepareStatement的setBytes()来设置要存入的byte[]数据,使用setBinaryStream()来设置代表输入来源的InputStream。在读取数据时,可以使用ResultSet的getBytes()以byte[]取得字段中储存的数据,或以getBinaryStream()取得代表字段来源的InputStream。

例24、取得代表数据来源的InputStream后,进行数据库储存的片段
InputStream in=readFileAsInputStream(“...”);
PrepareStatement stmt=conn.prepareStatement(“insert into images(src,img) values(?,?)”);
Stmt.setString(1,”..”);
Stmt.setBinaryStream(2,in);
Stmt.executeUpdate();

例25、取得代表字段数据源的InputStream的片段
PrepareStatement stmt=conn.prepareStatement(“select img from images”);
ResultSet rs=stmt.executeQuery();
While(rs.next()){
InputStream in=rs.getBinaryStream(1);
//使用InputStream作数据库读取
}

5、事务简介

1)事务的四个基本要求是原子性、一致性、隔离行为与持续性,简称ACID。

原子性:一个事务是一个单元工作,当中可能包括数个步骤,这些步骤必须全部执行成功,若有一个失败,则整个事务声明失败,事务中其他步骤必须撤销曾经执行过的行为,回到事务前的状态。在数据库上执行单元工作为数据库事务。

一致性:事务作用的数据集合咋爱事务前后必须移至,若事务成功,整个数据集合必须是数据操作后的状态;若事务失败,整个数据集合必须与开始事务前一样没有变更,不能发生整个数据集合部分有变更,部分没有变更的状态。

隔离行为:在多人使用的环境下,每个用户可能进行自己的事务,事务与事务之间,必须互不干扰。

持续性:事务一旦成功,所有变更必须保存下来,即使系统故障,事务的结果也不能遗失。

(2)在原子性的要求上,在JDBC可以操作Connection的setAutoCommit()方法,给它false自变量,提示数据库启动事务,在下达一串的SQL命令后,自行调用Connection的commit(),提示数据库确认(COMMIT)操作,如果中间发生错误,则调用rollback(),提示数据库撤销(ROLLBACK)所有的执行。

如果在事务管理时,仅想要撤回某个SQL执行点,则可以设置储存点。

例26、point=conn.setSavePoint(); //设置储存点
conn.rollback(point); //撤回储存点
conn.releaseSavePoint(point);//释放储存点

3)在隔离行为的支持上,JDBC可以通过Connection的getTransactionIsolation()取得数据库目前的隔离行为设置,通过setTransactionIsolation()可提示数据库设置指定的隔离行为。可设置常数是定义在Connection上的:TRANSACTION_NONE、TRANSACTION_UNCOMMITTED、TRANSACTION_COMMITTED、TRANSACTION_REPEATABLE_READ、TRANSACTION_SERIALZABLE。

TRANSACTION_NONE表示对事务不设置隔离行为,仅适用于没有事务功能、以只读功能为主、不会发生同时修改字段的数据库。有事务功能的数据库,可能不理会TRANSACTION_NONE的设置提示。

(4)多个事务并行时,可能引发的数据不一致问题:

更新遗失:基本上就是指某个事务对字段进行更新的信息,因另一个事务的介入而遗失更新效力。如果要避免更新遗失问题,可以设置隔离层级为“可读取未确认”,也就是A事务已更新但未确认的数据,B事务仅可作读取动作,不可作更新动作。JDBC可通过Connection的setTransactionIsolation()设置为TRANSACTION_UNCOMMITTED来提示数据库指定此隔离行为。数据库对此隔离行为的基本做法是,A事务在更新但未确认,延后B事务的更新需求至A事务确认后。提示数据库“可读取未确认”的隔离层次之后,数据库至少得保证事务能避免更新遗失问题,通常这也是具备事务功能的数据库引擎会采取的最低隔离层级,一般不会默认采用这种隔离层级。

脏读:两个事务同时进行,其中一个事务更新数据但未确认,另一个事务就读取数据,就可能发生脏读问题,也就是所谓读到脏数据。不干净、不正确的数据。如果要避免脏读问题,可以设置隔离层级为“可读取确认”,也就是事务读取的数据必须是其他事务已确认的数据。JDBC可通过Connection的setTransactionIsolation()设置为TRANSACTION_COMMITTED来提示数据库指定此隔离行为。数据库对此隔离行为的基本做做法之一是,读取的事务不会组织其他事务,未确认更新的事务会阻止其他事务。提示数据库“可读取确认”的隔离层次之后,数据库至少得保证事务能避免脏读与更新遗失问题。

无法重复的读取:某个事务两次读取同一字段的数据并不一致,例如,事务A在事务B更新前后进行数据的读取,则事务A会得到不同的结果。如果要避免无法重复的读取问题,可以设置隔离层级为“可重复读取”。JDBC可通过Connection的setTransactionIsolation()设置为TRANSACTION_REPEATABLE_READ来提示数据库指定此隔离行为。此隔离行为。数据库对此隔离行为的基本做做法之一是,读取事务在确认前不阻止其他读取事务,但会阻止其他更新事务。提示数据库“可重复读取”的隔离层次之后,数据库至少得保证事务能避免重复读取、脏读与更新遗失问题。

幻读:同一事务期间,读取到的数据笔数不一致,例如,事务A第一次读取得到5笔数据,此时事务B新增了1笔数据,导致事务B再次读取得到6笔数据。如果隔离行为设置为可重复读取,但发生幻读现象,可以设置隔离层级为“可循序”,也就是在有事务时若头数据不一致的疑虑,事务必须可以按照顺序逐一进行。JDBC可通过Connection的setTransactionIsolation()设置为TRANSACTION_SERIALZABLE来提示数据库指定此隔离行为。

 

如果想通过JDBC得知数据库是否支持某个隔离行为设置,可以通过Connection的getMetaData()取得DatabaseMetadata对象,通过DatabaseMetadata的supportsTransactionIsolationLevel()得知是否支持某个隔离行为。

例27、DatabaseMetadata meta=conn.getMetaData();
boolean isSupported=meta.supportsTransactionIsolationLevel(Connection.
TRANSACTION_READ_COMMITTED);

6、metadata简介

Metadata即“关于数据的数据”,在JDBC中,可以通过Connection的getMetaData()取得DatabaseMetadata对象,通过这个对象提供的方法,可以取得数据库整体的信息,而ResultSet表示查询到的数据,而数据本身的字段、类型等信息,则可以通过ResultSet的getMetaData()方法,取得ResultSetMetaData对象,通过这个对象提供的相关方法,就可以取得字段名称、字段类型等信息。

7、RowSet简介

1)JDBC定义了javax.sql.RowSet接口,用以代表数据的列集合,这里的数据并不一定是数据库中的数据,可以是试算表数据、XML数据或任何具有行集合概念的数据源。

RowSet是ResultSet子接口,所以具有ResultSet的行为,可以使用RowSet对行集合进行增删查改,RowSet也新增了一些行为,比如,通过setCommand()设置查询命令。通过execute()执行查询命令以填充数据等。

RowSet定义了行集合基本行为,其下有JdbcRowSet、CachedRowSet、FilteredRowSet、JoinRowSet与WebRowSet五个标准行集合子接口,定义在javax.sql.rowset包中。

(2)JdbcRowSet是连接式的RowSet,也就是操作JdbcRowSet期间,会保持与数据库的连接,可视为取得、操作ResultSet的行为封装,可简化JDBC程序的编写,或作为JavaBean使用。JdbcRowSet也有setAutoCommit()与commit()方法,可以进行事务控制。

CachedRowSet则为离线式的RowSet,在查询并填充完数据后,就会断开数据源的连接,而不用占据相关连接资源,必要时也可以再与数据源连接进行数据同步。

例28、使用RowSet查询数据(JdbcRowSet)
JdbcRowSet rowset=new JdbcRowSetImpl();
rowset.setUrl(“jdbc:mysql://localhost:3306/demo”);//设置JDBC URL
rowset.setUsername(“root”);//设置用户名称
rowset.setPassword(“123456”);//设置密码
rowset.setCommand(“select * from message where id=?”);//设置查询SQL
rowset.setInt(1,1);
rowset.execute();

如果在查询之后,想要离线进行操作,则可以使用CachedRowSet或其子接口实现对象,视需求而定,可以直接使用close()关闭CachedRowSet,若在相关更新操作之后,想再与数据源进行同步,则可以调用acceptChanges()方法。

例29、
Conn.setAutoCommit(false);//conn是Connection
rowSet.acceptChanges(conn);//rowSet是CachedRowSet
Conn.setAutoCommit(true);

WebRowSet是CachedRowSet的子接口,不仅具备离线操作,还能进行XML读写。

FilteredRowSet可以对行集合进行过滤,实现类似SQL中where等条件式的功能。可以通过setFilter()方法,指定实现javax.sql.rowset.Predicate的对象,其定义如下:

boolean evaluate(Object value,int column)
boolean evaluate(Object value,String columnName)
boolean evaluate(RowSet rs)

Predicate的evaluate()方法返回true,表示该行包括在过滤后的行集合中,通过setMatchColumn()指定要结合的列,然后使用addRowSet()来加入RowSet进行结合。

例30、rs1.setMatchColumn(1);
Rs2.setMatchColumn(2);
JdbcRowSet jrs=new JdbcRowSetImpl();
jrs.addRowSet(rs1);
jrs.addRowSet(rs2);

三、使用SQL标签库

JSTL提供了SQL标签库,可以在JSP页面上直接进行数据库增删查找,但无须任何JDBC代码。

1、数据源、查询标签

(1)在进行任何数据库来源之前,得先设置数据源,这可以使用<sql:setDataSource>标签来设置。

(2)如果要进行数据库查询,可以使用<sql:query>标签,如果已经使用<sql:setDataSource>设置数据源,则可以直接进行SQL查询。

2、更新、参数、事务标签

(1)如果想通过SQL标签库对数据库进行更新操作,则可以使用<sql:update>标签。

(2)如果字段是日期时间格式,则可以使用<sql:paramDate>标签。

(3)如果有必要指定事务隔离行为,则可以通过<sql:transaction>标签指定。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值