Java数据库连接
Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。JDBC也是Sun Microsystems的商标[1]。JDBC是面向关系型数据库的。
当前版本 | JDBC 4.3(2017年9月21日 | )
---|---|
操作系统 | 跨平台 |
类型 | Data access API |
网站 | Java SE 7 |
在J2SE中,提供了一个称之为JDBC-ODBC桥(JDBC-ODBC Bridge[2])的API。通过ODBC,JDBC-ODBC桥驱动程式可以访问所有支持ODBC的关系型数据库。与JDBC API不同的是,这个驱动程式并不是由Java代码而是由机器代码(machine code)编写,并且不是开放原始码的[3]。
驱动程式类型
JDBC驱动程式共分四种类型:
类型1:JDBC-ODBC桥
这种类型的驱动把所有JDBC的调用传递给ODBC,再让后者调用数据库本地驱动代码(也就是数据库厂商提供的数据库操作二进制代码库,例如Oracle中的oci.dll)。
优点:
- 只要有对应的ODBC驱动(大部分数据库厂商都会提供),几乎可以访问所有的数据库。
缺点:
- 执行效率比较低,不适合大数据量存取的应用;
- 由于需要客户端预装对应的ODBC驱动,不适合Internet/Intranet应用。
类型2:本地API驱动
这种类型的驱动通过客户端加载数据库厂商提供的本地代码库(C/C++等)来访问数据库,而在驱动程式中则包含了Java代码。
优点:
- 速度快于第一类驱动(但仍比不上第3、第4类驱动)。
缺点
- 由于需要客户端预装对应的数据库厂商代码库,仍不适合Internet/Intranet应用。
类型3:网络协议驱动
这种类型的驱动给客户端提供了一个网络API,客户端上的JDBC驱动程式使用套接字(Socket)来调用伺服器上的中间件程序,后者在将其请求转化为所需的具体API调用。
优点:
- 不需要在客户端加载数据库厂商提供的代码库,单个驱动程式可以对多个数据库进行访问,可扩展性较好。
缺点:
- 在中间件层仍需对最终数据进行配置;
- 由于多出一个中间件层,速度不如第四类驱动程式。
类型4:本地协议驱动
这种类型的驱动使用Socket,直接在客户端和数据库间通信。
优点:
- 访问速度最快;
- 这是最直接、最纯粹的Java实现。
缺点:
- 几乎只有数据库厂商自己才能提供这种类型的JDBC驱动。
- 需要针对不同的数据库使用不同的驱动程式。
API概述
JDBC API主要位于JDK中的java.sql包中(之后扩展的内容位于javax.sql包中),主要包括(斜体代表接口,需驱动程式提供者来具体实现):
- DriverManager:负责加载各种不同驱动程式(Driver),并根据不同的请求,向调用者返回相应的数据库连接(Connection)。
- Driver:驱动程式,会将自身加载到DriverManager中去,并处理相应的请求并返回相应的数据库连接(Connection)。
- Connection:数据库连接,负责进行与数据库间的通讯,SQL执行以及事务处理都是在某个特定Connection环境中进行的。可以产生用以执行SQL的Statement。
- Statement:用以执行SQL查询和更新(针对静态SQL语句和单次执行)。
- PreparedStatement:用以执行包含动态参数的SQL查询和更新(在伺服器端编译,允许重复执行以提高效率)。
- CallableStatement:用以调用数据库中的存储过程。
- SQLException:代表在数据库连接的建立和关闭和SQL语句的执行过程中发生了例外情况(即错误)。
数据类型的映射
SQL类型 | Java类型 |
---|---|
CHAR | java.lang.String |
VARCHAR | java.lang.String |
LONGVARCHAR | java.lang.String |
NUMERIC | java.math.BigDecimal |
DECIMAL | java.math.BigDecimal |
BIT | boolean |
TINYINT | byte |
SMALLINT | short |
INTEGER | int |
BIGINT | long |
REAL | float |
FLOAT | double |
DOUBLE | double |
BINARY | byte[] |
VARBINARY | byte[] |
LONGVARBINARY | byte[] |
DATE | java.sql.Date |
TIME | java.sql.Time |
TIMESTAMP | java.sql.Timestamp |
BLOB | java.sql.Blob |
CLOB | java.sql.Clob |
Array | java.sql.Array |
REF | java.sql.Ref |
Struct | java.sql.Struct |
注:这种类型匹配不是强制性标准,特定的JDBC厂商可能会改变这种类型匹配。例如Oracle中的DATE类型是包含时分秒,而java.sql.Date仅仅支持年月日。
例子
利用Class.forName()方法来加载JDBC驱动程式(Driver)至DriverManager:
Class.forName( "com.somejdbcvendor.TheirJdbcDriver" );
然后,从DriverManager中,通过JDBC URL,用户名,密码来获取相应的数据库连接(Connection):
Connection conn = DriverManager.getConnection(
"jdbc:somejdbcvendor:other data needed by some jdbc vendor", // URL
"myLogin", // 用户名
"myPassword" ); // 密碼
不同的JDBC驱动程式的URL是不同的,它永远以“jdbc:”开始,但后面的内容依照驱动程式类型不同而各异。在获取Connection之后,便可以建立Statement用以执行SQL语句。下面是一个插入(INSERT)的例子:
Statement stmt = conn.createStatement();
stmt.executeUpdate( "INSERT INTO MyTable( name ) VALUES ( 'my name' ) " );
查询(SELECT)的结果存放于结果集(ResultSet)中,可以按照顺序依次访问:
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery( "SELECT * FROM MyTable" );
while ( rs.next() ) {
int numColumns = rs.getMetaData().getColumnCount();
for ( int i = 1 ; i <= numColumns ; i++ ) {
// 與大部分Java API中下標的使用方法不同,字段的下標從1開始
// 當然,還有其他很多的方式(ResultSet.getXXX())獲取數據
System.out.println( "COLUMN " + i + " = " + rs.getObject(i) );
}
}
rs.close();
stmt.close();
但是,通常,Java程序员们更倾向于使用PreparedStatement。下面的例子使用上例中的conn对象:
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement( "SELECT i.*, j.* FROM Omega i, Zappa j
WHERE i = ? AND j = ?" );
// 使用問號作为參數的標示
// 進行參數設置
// 與大部分Java API中下标的使用方法不同,字段的下標從1開始,1代表第一个問號
// 當然,還有其他很多針對不同類型的類似的PreparedStatement.setXXX()方法
ps.setString(1, "Poor Yorick");
ps.setInt(2, 8008);
// 结果集
rs = ps.executeQuery();
while ( rs.next() ) {
int numColumns = rs.getMetaData().getColumnCount();
for ( int i = 1 ; i <= numColumns ; i++ ) {
// 與大部分Java API中下标的使用方法不同,字段的下標從1開始
// 當然,還有其他很多的方式(ResultSet.getXXX())獲取數據
System.out.println( "COLUMN " + i + " = " + rs.getObject(i) );
}
}
catch (SQLException e) {
// 異常處理
}
finally { // 使用finally进行资源释放
try {
rs.close();
ps.close();
} catch( SQLException e){} // 异常處理:忽略close()时的错误
}
如果数据库操作失败,JDBC将抛出一个SQLException。一般来说,此类异常很少能够恢复,唯一能做的就是尽可能详细的打印异常日记。推荐的做法是将SQLException翻译成应用程式领域相关的异常(非强制处理异常)并最终回滚数据库和通知用户。
一个数据库事务代码如下:
boolean autoCommitDefault = conn.getAutoCommit();
try {
conn.setAutoCommit(false); //关闭自动提交,从而将之后执行的多个SQL语句视为同一个事务(自动提交会使每一个SQL语句执行后提交一次而重新启用新的事务)
/* 在此基于有事務控制的conn執行你的代碼 */
conn.commit(); //最终提交该次事务的修改
} catch (Throwable e) {
try {
conn.rollback(); //回退到上一个事务开始时的状态
} catch (Throwable ignore) {}
throw e;
} finally {
try { conn.setAutoCommit(autoCommitDefault); } catch (Throwable ignore) {}
}