一、在java程序中,通过JDBC API访问数据库包括以下步骤:
1) 获得要访问的数据库的驱动程序的类库,把它放在classpath中。
2) 在程序中加载并注册JDBC驱动器,之中JDBC-ODBC驱动器是在JDK中自带的,默认已经注册。
一下给出加载JDBC-ODBC驱动器,加载并注册SQLServer驱动器、Orcale驱动器和MySQL驱动器代码:
//加载JdbcOdbcDriver类
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
//加载SQLServerDriver类
Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver");
//注册SQLServer类
java.sql.DriverManager.registerDriver(new com.microsoft.jdbc.sqlserver.SQLServerDriver());
//加载OracleDriver类
Class.forName("oracle.jdbc.driver.OracleDriver");
//注册OracleDriver类
java.sql.DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
//加载MySQL Driver类
Class.forName("com.mysql.jdbc.Driver");
//注册MySQL Driver类
java.sql.DriverManager.registerDriver(new com.mysql.jdbc.Driver());//不是必要步骤
有些驱动器的Driver类在被加载的时候能自动创建本身的实例,然后调用DriverManager.registerDriver()方法注册自身
attention:通常情况下将负责加载数据库驱动的代码放在static块中,因为static块的特点就是只在其所在类第一次被加载时执行,即第一次访问数据库时执行,这样就可以避免反复加载数据库驱动,减少对资源的浪费,同时提高了访问数据库的速度。
public class JDBC{
static{
try{
Class.forName();
}catc(ClassNotFoundException e){
e.printStackTrace(); }//输出捕获的异常信息
}
public static void main(String arga[]){
}
}
3)建立与数据库的连接
Connection con=java.sql.DriverManager.getConnection(dburl,user,password);
参数说明:dburl:表示连接数据库的JDBC URL
user:表示用户名
password:表示用户密码
JDBC URL 的一般格式是:
jdbc:drivertype:driversubtype://parameters
参数说明:drivertype:表示驱动器类型
driversubtype:表示驱动器子类型(可选参数)
parameters:表示设定数据库服务器的IP地址、端口号和数据库名称。
一下给出了几种常见的数据库JDBC URL形式
如果通过JDBC-ODBC Driver连接数据库
jdbc:odbc:databasesource
对于Oracle数据库
jdbc:oracle:thin:@localhost::sid
对于SQLServer数据库
jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=?
对于MySQL数据库
jdbc:mysql://localhost:3306/?
说明:?为数据库名
try{
Connetion con=DriverManager.getConnecion();
}catch(SQLException e){
e.printStackTrace(); }
4)创建Statement对象,准备执行SQL语句:
Statement stmt=con.createStatement();
& PreparedStatement pst=con.prepareStatement();
注: 运用preparedStatement不是线程安全的,当对一个PreparedStatement对象
打开多个ResultSet对象是会导致SQLException。
而Statement对象是线程安全的,可以被多个线程并发访问;虽然每次访问数据库时,
都会创建新的数据库连接,运行性能比较差。
5)执行SQL语句
String sql="select * from ?where…";
ResultSet rs=stmt.executeQuery(sql);
6)遍历ResultSet对象中的记录
7)一次关闭ResultSet、Statement、Connection对象:
rs.close();
stmt.close();
con.close();
注意:ResultSet、Statement、Connection对象一旦调用close()方法就会立刻释放其所占用的系统资源,不允许在访问其方法等。
二、处理SQLException
Ⅰ、getErrorCode():返回数据库系统提供的错误编号。
Ⅱ、getSQLState():返回数据库系统提供的错误状态。
SQLException的子类SQLWarning,它表示访问数据库时产生的警告信息。警告信息不会影响程序的执行流程,程序也无法在catch语句中捕获SQLWarning。程序可以通过ResultSet、Statement、Connection对象的getWarnings()方法获得SQLWarning对象。SQLWarning采用串联模式,它的getnextWarning()方法返回后续的SQLWarning对象。
三、输出JDBC日志
以下代码使JDBC实现把日志输出到控制台:
在程序开头用:
DriverManager.setLogWriter(new PrintWriter(System.out,true));
四、获得新插入记录的主键值
表的主键定义成auto_increment类型
create table 表名 (ID bigint primary key auto_increment not null)
为了获得主键值,必须在Statement的executeUpdate()方法中设置Statement.RETURN_GENERATED_KEY参数,接着通过Statement的getGeneratedKeys()方法就能得到包含主键值的ResultSet对象。
五、设置批量抓去属性
ResultSet对象并不会存放很多条数据,而是在遍历结果集时,ResultSet对象才会到数据库中抓取相应的数据。抓去过程对程序完全透明。
ResultSet、Statement、Connection接口中都提供了以下方法。
(1)setFetchSize(int size):设置抓去的数目。
(2)setFetchDirection(int direction):设置抓取的方向。
参数有三个:1>ResultSet.FETCH_FORWARD(单向)
2>ResultSet.FETCH_REVERSE(双向)
3>ResultSet.FETCH_UNKNOWN(未知)
其中:Connection接口中的setFetchXxx()方法决定了由它创建的所有Statement对象的默认抓取属性。
Statement接口中的setFetchXxx()方法决定了由它创建的所有ResultSet对象的默认抓取属性。
ResultSet接口中的setFetchXxx()方法仅仅决定当前ResultSet对象的抓取属性。
另注意:setFetchXxx()方法仅仅向JDBC驱动器提供了批量抓取的建议,JDBC驱动器有可能会忽略这个建议。
六、检测驱动器使用JDBC版本
通过DatabaseMetaData类的getJDBCMajorVersion()和getJDBCMinorVersion()方法来检测驱动器所用的JDBC版本。
七、元数据
在SQL中,用来描述数据库及其组成部分的数据成为元数据。元数据可以提供数据库结构和表的详细信息。
在JDBC API中,Connection接口的getMetaData()方法返回一个DatabaseMetaData对象,表示连接数据库的元数据。
ResultSet接口的getMetaData方法返回一个ResultSetMetaData对象,表示相应结果集的元数据。
ParameterMetaData接口的getParameterMetaData()方法返回一个ParameterMetaData对象。
【1】ResultSetMetaData
ResultSetMetaData具有一下的方法:
①int getColumnCount():返回结果集包含的列数;
②int getColumnLabel(int i):返回结果集中第i列的字段名,结果集中第1列字段的索引为1.
③int getColumnType(int i):返回结果集中第i列的字段的SQL类型。Types.VARCHAR表示SQL类型中的VARCHAR类型
④String getColumnTypeName(int i):返回结果集中第i列的字段的SQL类型。
ResultSet result;
ResultSetMetaData metaData=result.getMetaData();
【2】DatabaseMetaData
1>getTable()方法。它返回数据库中符合参数给定条件的所有表。给方法的完整的定义如下:
public ResultSet getTables(String catalog,String schemapattern,String tableNamePattern,String[]types)throws SQLException
各参数说明:
catalog:指定表所在的目录。如果不支持目录就设为null
schemaPattern:指定表所在的Schema,如不限制则设为null
tableNamePattern:指定表名必须匹配的字符串模式
types:指定表的类型。表的类型包括:"TABLE","VIEW","SYSTEM TABLE",GLOBAL TEMPORARY","LOCAL TEMPORARY","ALLIAS" "SYNONYM".
getTable()方法返回一个ResultSet对象,它包括5个字段:即5列
·TABLE_CAT: 第1个字段,表示表的 目录,可以为null。
·TABLE_SCHEM: 第2个字段,表示的 Schema,可以为null。
·TABLE_NAME: 第3个字段,表示表的 名字。
·TABLE_TYPE: 第4个字段,表示表的 类型。
·REMARKS: 第5个字段,表示表的 注释。
2>getJDBCMajorVersion() 和getJDBCMinorVersion()方法。它返回int型的数值,分别表示驱动器使用的JDBC的主版本号和次版本号。
3>getMaxConnections()方法,它返回int型数值,表示数据库允许同时建立的连接的最大数目。
4>getMaxStatements()方法,它返回int型的数值,表示一个Connection对象允许同时打开的Statement对象的最大数目。如果此对象没有限制或者是未知则返回0.
5>supportsXxx()方法。它判断驱动器或底层数据库系统是否支持某种特性。
例如:supportsOuterJoins()方法判断数据库是否支持外连接;supportsGroupBy()方法判断数据库是否支持group by语句。
八、可滚动及可更新的结果集
默认情况下,结果集的油表只能从上往下移动,只要调用ResultSet对象的next()方法,就能使游标下移一行。当到达结果集的末尾时,next()方法就会返回false,否则返回true。此外,默认情况下,只能对结果集执行读操作,不允许更新结果集的内容。
结果集的开头是指第一条记录的前面的位置,这是游标的初始位置。结果集的末尾是指最后一条记录的后面位置。
为了获得可滚动或可更新的ResultSet对象,需要通过Connection接口的一下方法构造Statement或PreparedStatement对象:
createStatement(int type,int concurrency) //创建Statement对象
PrepareStatement(String aql,int type,int concurrency) //创建PreparedStatement对象
type参数可以是:
·ResultSet.TYPE_FORWARD_ONLY:游标只能从上往下移动,即结果集不能滚动。这是默认值。
·ResultSet.TYPE_SCROLL_INSENSITIVE:游标可以上下移动,即结果集可以滚动。当程序对结果集的内容作了修改,游标对此不敏感。
·ResultSet.TYPE_SCROLL_SENSITIVE:游标可以上下移动,即结果集可以滚动。当程序对结果集的内容作了修改,游标对此敏感。
concurrency参数可以是:
·CONCUR_READ_ONLY:结果集不能被更新。
·CONCUR_UPDATABLE:结果集可以被更新。
值得注意的是:即使在创建Statement或PreparedStatement时,把type和concurrency参数分别设为可滚动和可更新,实际上得到的结果集有可能仍然不允许滚动和更新,这有两个方面的原因:
··底层JDBC驱动器有可能不支持可滚动和可更新的结果集。程序可以通过DatabaseMetaData类的supportsResultSetType()和supportsResultSetConcurrency()方法,来了解驱动器所支持的type和concurrency类型。
··某些查询语句的结果集不允许被更新。例如:JDBC规范规定,只有对一张表查询,并且查询字段包含表中所有的主键,这样的查询语句的结果集才可以被更新。
ResultSet接口提供了一系列用于移动游标分方法。
1>first():使游标移动到第一条记录。
2>last():使游标移动到最后一条记录。
3>beforeFirst():使游标移动到结果集的开头。
4>afterLast():使游标移动到结果集的末尾。
5>previous():使游标从当前位置向上(或者说向前)移动一行。
6>next():使游标从当前位置向下或者说是向后移动一行。
7>relative(int n):使游标从当前位置移动n行。n>0,就向下移动,否则向上移动。当n=1,等价于调用next()方法;当n=-1就等价于previous()方法。
8>absolute(int n):使游标移动到第n行。参数n指游标的绝对位置。
使用以上方法需要注意两点:
第一:除了beforeFirst()和afterLast()方法返回void类型之外,其余方法都返回boolean类型,如果游标移动到目标位置到达结果集 的开头或者是结尾,就返回false,否则返回true。
第二:只有当结果集可以滚动时,才可以调用以上所有方法。如果结果集不可以滚动,则只能调用next()方法,当程序调用其他方法时,这些方法会抛出SQLException。
ResultSet接口的一下方法判断游标是否在特定位置。
1>isFirst():判断游标是否在第一行。
2>isLast():判断游标是否在最后一行。
3>isBeforeFirst():判断游标是否在结果集的开头。
4>isAfterLast():判断游标是否在结果集的末尾。
5>getRow():返回游标当前所在的位置的行号。
下面介绍如何在结果集中插入、更新、删除记录、
(1)插入记录
ResultSet接口的moveToInsertRow()方法把游标移动到特定的插入行。
值得注意的是,程序无法控制在结果集中添加记录的位置,因此新纪录到底插入到哪一行对程序是透明的。ResultSet接口的insertRow()方法会向数据库中插入记录。ResultSet接口中的moveToCurrentRow()方法把游标移动到插入前的位置,即调用moveToInsertRow()方法前所在的位置。
(2)更新记录
ResultSet接口中的updateRow()方法会更新数据库中的相应的记录。
(3)删除记录
ResultSet接口中的deleteRow()方法会删除数据库中的相应的记录。
九、处理Blob和Clob类型的数据
在数据库中有两种特殊的处理SQL数据类型。
<<<<1>>>>Blob(Binary large object):存放大容量的二进制数据。
<<<<2>>>>Clob(Character object):存放大容量的由字符组成的文本数据。
【1】数据库通过调用ResultSet对象的getBlob()方法返回一个Blob对象,Blob对象实际上仅仅持有数据库中相应FILE字段的引用。
①Blob blob=(new ResultSet()).getBlob();
为获取数据库中的Blob数据,可以调用Blob对象的getBinaryStream()方法获得一个输入流,然后从这个输入流中读取Blob数据。
②InputStream in=blob.getBinaryStream();
PreparedStatement的setBinaryStream()方法向数据库中写入Blob数据,该方法定义为:
③public void setBinaryStream(int parameterIndex,InputStream in,int length)throws SQLException
上述InputStream类型的对象指定了Blob数据源,参数length指定了Blob数据的字节数。
MySQL中Blob数据分为4中类型:TINYBLOB(容量为256B)、BLOB(容量为64KB)、MEDIUMBLOB(容量为4GB)。
PreparedStatement stmt=con.prepareStatement("insert into ABLOB(ID,FILE)values(?,?)");
stmt.setLong(1,1);
FileInputStream fin=new FileInputStream("test.gif");
smt.setBinaryStream(2,fin,fin.available());
stmt.executeUpdate();
fin.close();
stmt.close();
注:MySQL数据库限制了接收和发送的数据包的大小。过大会抛出com.mysql.jdbc.PacketToolbigException。
可以修改MySQL服务器的max_allowed_packet系统属性,把它设为更大值。
【2】Clob数据处理方式与Blob相似
①ResultSet接口的getClob()方法:从结果集中获得Clob对象。
②Clob接口的getCharacterStream()方法,返回一个Reader对象,用于读取Clob数据中的字符。
③PreparedStatement接口的setCharacterStream()方法:想数据库中写入Clob数据。
完整定义:public void setCharacter(int parameterIndex,Reader reader,int length)throws Exception
十、控制事务
~1、事务是指一组相互依赖的操作行为
数据库事务必须具备ACID特征,ACID是Atomic(原子性)、Consistency(一致性)、Isolation(隔离性)、Durability(持久性)。
·原子性:指整个数据库事务是不可分割的工作单元。
·一致性:指数据库事务不能破坏关系数据库的完整性及业务逻辑上的一致性。
·隔离性:指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。
·持久性:指的是只要事务成功结束,它对数据库所做的更新操作就必须永久保存下来。
数据库采用日志来保证事务的原子性、一致性、持久性。
数据库通过锁机制来实现隔离性。
~2、声明事务的边界的概念。
数据库系统向客户程序只要向数据库声明了一个事务,数据库系统就会自动保证事务的ACID特性。声明事务包含以下内容
·事务开始的边界
·事务的正常结束边界(COMMIT):提交事务,永久保存被事务更新后的数据库状态。
·事务的异常结束边界(ROLLBACK):撤销事务,或者说回滚事务,使数据库退回到执行事务前的初始状态。
~3、数据库系统支持两种事务模式。
·自动提交模式:每个SQL语句都是一个独立的事务,当数据库系统执行完一个SQl事务语句后,会自动提交事务。
·手工提交模式:必须由数据库的客户程序显式指定事务开始边界和结束边界。
~4、数据库表的分类
·INNODB:支持数据库事务
·BDB支持数据库事务
·MyISAM不支持数据库事务(默认表类型)
create table 表名{}type=INNODB;
对于已存在的表,可以采用一下形式的DDL语句修改它的表类型:
alter table 表名 type=INNODB;
~5、通过JDBC API声明事务边界
Connection接口提供了以下用于控制事务的方法:
·setAutoCommit(boolean sutocommit):设置是否自动提交事务
·commit():提交事务
·rollback():撤销事务
十一、保存点
Connection接口的setSavepoint()方法用于在事务中设置保存点。它有两种重载形式:
public SavePoint setSavepoint()throws SQLException
public SavePoint setSavepoint(String name)throws SQLException
第一个不带参数的setSavepoint()方法设置匿名的保存点,第二个setSavepoint(String name)方法name参数表示保存点的名字。
这两个setSavepoint()方法都会返回一个表示保存点的Savepoint对象
Connection接口的releaseSavepoint(Savepoint point)方法取消已经设置的保存点。
Connection接口的rollback(Savepoint point)方法使事务回滚到参数指定的保存点。
可以通过DatabaseMetaData接口的supportsSavepoint()方法可以判断驱动器是否支持保存点,false表示不支持保存点。
十二、批量更新
Statement接口中提供了支持批量更新的两个方法。
·addBatch(String sql):加入一条SQL语句。
·executeBatch():执行批量更新。
Statement接口的executeUpdate(String sql)方法返回一个整数值,表示数据库中受SQL语句影响的记录数。而executeBatch()方法则返回一个int数组,数组中每个元素分别表示受每条SQL语句影响的记录数。
批量更新需要注意:
1>必须把批量更新中的所有操作放在单个事务中。
2>批量更新中可以包括SQL update /delete/insert语句等,但是不能包括select查询语句否则Statement的executeBatch()方法会抛出BatchUpdateException异常。
3>可以通过DatabaseMetaData接口中的supportsBatchUpdate()方法可以判断驱动器是否支持批量更新。
BatchUpdateException还有一个getUpdateCounts()方法,返回一个int类型的数组,数组中元素分别表示受已经执行成功的每条SQL语句影响的记录数。
十三、设置事务的隔离级别
数据库提供了4中事务隔离级别。
-1、Serializable:串行化
-2、Repeatable Read:可重复读
可以看到其他事务已经提交的新插入的记录,但是不能看到其他事务对已有记录的更新
-3、Read Committed:读已提交数据
可以看到其他事务已经提交的新插入的记录,而且能看到其他事务已经提交的对已有记录的更新
-4、Read UnCommitted:读未提交数据
可以看到其他事务没有提交的新插入的记录,而且能看到其他事务没有提交的对已有记录的更新
注:隔离级别越高,越能保证数据库中数据的完整性和一致性,但是对并发性能的影响也越大。
Connection接口的setTransactionIsolation(int level)用来设置数据库使用的隔离级别,这种设置只对当前的连接有效。
level可选值:Connection.TRANSACTION_READ_UNCOMMITTED
Connection.TRANSACTION_READ_COMMITTED
Connection.TRANSACTION_READ_REPEATABLED_READ
Connection.TRANSACTION_READ_SERIALIZABLE
十四、数据库连接池
数据库连接池的基本原理是,事先建立一定数量的数据库连接,这些数据库连接存放在连接池中,当Java应用程序执行一个数据库事务时:只需从连接池中取出空闲的数据库连接;当Java应用程序执行完成事务后,再将数据库连接放回连接池。
使用数据库连接池需要注意:
·限制连接池中最多可以容纳的连接数目,皮面过度消耗系统资源。
·当客户请求连接,而连接池中所有的连接都已经被占用时,该如何处理?一种方法是让客户一直等待,直到有空闲连接,还有一种方式是为客户分配一个新的临时的连接。
·当客户不再使用连接时,需要把连接重新放入连接池。
·限制连接池中允许处于空闲状态的连接的最多数目。
十五、DataSource数据源
DataSource接口的最主要的功能就是获得数据库连接,它的getConnection()方法提供这一服务。
DataSource接口并不强求其实现必须带有连接池,不过,多数DataSource实现都是用了连接池。
假定某种应用程序服务器发布了一个JNDI名字为“jdbc/SAMPLEDB”的数据源,Java应用程序通过JNDI API中的javax.naming.Context接口来获得这个数据源的引用。
Context ctx=new InitialContext();
DataSource ds=(DataSource)ctx.lookup("java:comp/env/jdbc/SAMPLEDB");
得到的DataSource对象的引用后,就可以通过DataSource对象的getConnection()方法获得数据库连接对象Connection:
Connection con=ds.getConnection();