一个 Connection
对象,表示了与某个数据源的一条链接,数据源的种类能够是关系型数据库,文件系统等等之类,只要有对应的 JDBC 驱动,均可以称之为数据源。应用程序使用 JDBC API 来维护多条链接,这些链接可能访问的是多个数据源,也可能访问的只是一个数据源。从 JDBC 驱动的角度来看,一个 Connection
对象就意味着一个客户端会话,一个会话会保持许多状态,例如用户 ID,一系列的 SQL Statement 以及结果集,也保存了当前使用的事务处理策略。java
能够经过如下两种方式之一来获取一条链接:sql
DriverManager
这个类以及各类各样的驱动实现DataSource
类更推荐使用 DataSource
对象来获取链接,由于这加强了应用的可移植性,使得代码更容易维护了,而且使得对链接池和分布式事务的使用更加地透明。全部的 Java EE 组件,都会使用 DataSource 对象来获取链接。数据库
这一章将会介绍各类不一样的 JDBC 驱动以及如何使用 Driver
接口、DriverManager
类以及基本的 DataSource
接口。关于链接池和分布式事务的介绍分别在第11章和第12章作介绍。服务器
这种类型的 JDBC 驱动是对另一种访问 API 的映射,好比说 ODBC,通常须要依赖本地库,这就致使了它的可移植性不行。JDBC-ODBC 桥就是这种类型的驱动。微信
这种类型的 JDBC 驱动一部分是用 Java 语言写的,一部分是用本地代码写的。这种驱动使用一个本地的客户端库来链接数据源。因为对本地代码的使用,可移植性也不行。网络
这种类型的驱动使用纯 Java 语言编写,可是通讯的时候须要通过一个中间件,使用的是与数据库具体协议无关的独立协议。这个中间件转发客户端的请求给后面的数据源。架构
这种类型的驱动使用纯 Java 语言编写,而且使用网络协议或者文件 IO 与具体的数据源通讯,客户端直接与数据源链接。app
编写 JDBC 驱动,必须实现 Driver 接口,而且实现类中必须包含一个静态初始化块,当驱动被加载时,这块代码会被调用。这块代码的主要工做是讲本身注册给 DriverManager,以下代码所示:分布式
public class AcmeJdbcDriver implements java.sql.Driver { static { java.sql.DriverManager.registerDriver(new AcmeJdbcDriver()); } }
驱动的实现类必须提供一个无参构造函数,当 DriverManager 想要与 Driver 交互时,它会直接调用它的方法,Driver 接口包含了一个 acceptsURL 方法,DriverManager 能够调用这个方法来判断该驱动是否能处理对应的 JDBC URL。函数
当 DriverManager 想要创建一条数据库链接时,它会调用驱动实现类的 connect 方法,并把 URL 做为参数穿给它,这个方法会返回一个 Connection 对象,或者是当没法创建数据库链接时,抛出一个 SQLException。若是驱动实现类没法解析 URL,这个方法将会返回 null。
## 9.2.1 加载一个实现了 java.sql.Driver 接口的驱动类
DriverManager 初始化的时候,会先经过 “java.drivers” 这个系统属性来尝试加载驱动,如如下例子:
java -Djdbc.drivers=com.acme.jdbc.AcmeJdbcDriver Test
DriverManager 的 getConnection 方法可以支持 Java SE 的 SPI 服务发现机制,JDBC 4.0 的驱动必须包含如下文件 “META-INF/services/java.sql.Driver”,这个文件会包含实现了 Driver 接口的类名
当 DriverManager 的 deregisterDriver 方法被调用时,若是想要被通知到,那么 JDBC 驱动就得有对应的实现了 DriverAction 接口的类,DriverAction 的具体实现类并不但愿直接被上层应用拿来使用,因此实现 JDBC 驱动的时候,应该将这个类定义为私有的类,以防止被直接使用。
JDBC 驱动的静态初始化块里面,必须调用 DriverManager.registerDriver(java.sql.Driver, java.sql.DriverAction) 方法,这样当一个 JDBC 驱动被 DriverManager 注销的时候,才能被通知到,以下所示:
public class AcmeJdbcDriver implements java.sql.Driver { static DriverAction da; static { java.sql.DriverManager.registerDriver(new AcmeJdbcDriver(), da); } }
DriverManager 类与 Driver 接口一块儿协做,维护全部可用的 JDBC 驱动。当应用程序经过一个 URL 来获取一个链接的时候, DriverManager 负责找到一个适用该 URL 的驱动,用这个驱动来获取对应的数据源的链接。
DriverManager 的关键方法以下所示:
这个方法会将某个驱动加进可用驱动的集合里,它在一个驱动被装载的时候隐式地调用,通常状况下是驱动的静态代码块里调用这个方法。
这个方法用来获取一个链接,要调用这个方法,必须提供一个 JDBC URL,DriverManager 会使用这个 URL 来轮询全部已经注册的驱动,并找到一个能够识别这个 URL 的驱动,驱动会返回一个 Connection 给 DriverManager,而后再把它交给应用程序。
JDBC URL 的格式以下所示:
jdbc:<subprotocol>:<subname>
subprotocol 定义是要链接的是哪一种类型的数据库,subname 则会根据 subprotoco 的不一样而不一样。
如下代码示范了如何从 DriverManager 获取一个链接:
String url = "jdbc:derby:sample"; String user = "SomeUser"; String passwd = "SomePwd"; Connection con = DriverManager.getConnection(url, user, passwd);
DriverManager 类也提供了另一些获取链接的方法:
这个方法适用于不须要提供用户名和密码的状况
这个方法容许在 prop 参数里加入用户名和密码,以及其它属性
DriverPropertyInfo 这个类提供了一个驱动能够理解的全部的属性,详见 Java JDBC API DOC
这个类表明了一个代码基所拥有的权限。当前惟必定义的权限是 setLog 权限。当一个 Applet 调用了 DriverManager 的 setLogWriter 或者 setLogStream 方法时,SecurityManager 将会检查是否有权限。若是没有权限,将会抛出一个 java.lang.SecurityException 异常
DataSource 这个接口是在 JDBC2.0 的可选属性里引进的,这是 JDBC 规范推荐的用来获取数据源链接的方式。实现了 DataSource 接口的 JDBC 驱动会返回和经过 DriverManager 获取的相同的 Connection 实例,使用 DataSource 接口使应用程序更加具备可移植性,由于应用程序不须要为某个特定的驱动提供相关的链接信息,仅仅须要提供一个逻辑的数据源名。逻辑数据源名用来映射到 JNDI 提供的 DataSource 实例。这个 DataSource 实例表明了一个物理上的数据源,并提供获取相应链接的方法。若是关于数据源的属性或者信息发生了变化,DataSource 对象能够感知到对应的变化,彻底不须要改变应用代码。
实现 DataSource 接口时,应该透明地提供如下功能:
还须要注意的是,DataSource 的实现类必须提供一个无参构造函数
接下来的3个小节主要讨论:
JDBC API 定义了一系列的属性来描述 DataSource 的实现,具体的属性有哪些,取决于具体的 DataSource 实现,也就是说,取决于该实现是一个基本的 DataSource 对象,仍是 ConnectionPoolDataSource,或者是 XADataSource,不管什么实现,它们都会有共同的属性 description,如下是标准的 DataSource 属性:
属性名 | 数据类型 | 描述 |
---|---|---|
databaseName | String | 数据库名 |
dataSourceName | String | 数据源名,用来命名底层的 XADataSource 或者是 ConnectionPoolDataSource |
description | String | 对此 DataSource 的描述信息 |
networkProtocol | String | 网络协议 |
password | String | 数据库密码 |
portNumber | int | 数据库监听端口 |
roleName | String | 初始 SQL roleName |
serverName | String | 数据库服务器名 |
user | String | 数据库用户名 |
DataSource 的属性遵循 JavaBean 1.01 规范。具体 DataSource 实现能够添加属性,可是不能与原有的有冲突。这些属性必须提供对应的 setter 和 getter 方法,当一个新的 DataSource 初始化的时候,这些属性也应该相应进行初始化,如如下代码所示,这里的实现是一个 VendorDataSource:
VendorDataSource vds = new VendorDataSource(); vds.setServerName("my_database_server"); String name = vds.getServerName();
DataSource 的属性,设计的初衷是不该该直接被应用代码获取,应该在具体的实现类里提供获取的方法,而不是在 DataSource 上定义 public 的属性,想要获取属性值,能够经过“自省”的方式(反射)来获取。
Java Naming and Directory Interface (JNDI) API 提供让应用经过网络访问远程服务的统一方式,本小节将描述如何使用 JNDI 来注册并访问一个 JDBC 数据源对象。更详细的信息能够查阅 JNDI 规范。
使用 JNDI API,应用能够经过指定一个逻辑名来访问一个数据源,在这里 JNDI 须要使用到命名服务,来将逻辑名映射到对应的数据源。这个特性极大地加强了应用的可移植性,由于不少数据源的配置,能够在不修改应用层代码的状况下进行修改,例如端口号和服务器名。事实上,应用能够透明地访问另外一个彻底不一样的数据源,只须要修改对应的配置。在三层架构的环境中,这个特性很重要,应用服务器会将访问不一样数据源的细节隐藏起来,不须要对应用开放。
如下代码实例了如何使用 JNDI 来注册一个数据源对象:
// Create a VendorDataSource object and set some properties VendorDataSource vds = new VendorDataSource(); vds.setServerName("my_database_server"); vds.setDatabaseName("my_database"); vds.setDescription("data source for inventory and personnel"); // Use the JNDI API to register the new VendorDataSource object. // Reference the root JNDI naming context and then bind the // logical name "jdbc/AcmeDB" to the new VendorDataSource object. Context ctx = new InitialContext(); ctx.bind("jdbc/AcmeDB", vds);
一旦一个 DataSource 注册在 JNDI 的命名服务后,应用可使用它来获取一条到物理数据源的链接,以下代码所示:
// Get the initial JNDI naming context Context ctx = new InitialContext(); // Get the DataSource object associated with the logical name // "jdbc/AcmeDB" and use it to obtain a database connection DataSource ds = (DataSource)ctx.lookup("jdbc/AcmeDB"); Connection con = ds.getConnection("user", "pwd");
Connection.close(), Connection.isclosed() 和 Connection.isValid() 这些方法能够用来关闭一条链接和判断一条链接是否还处于活跃状态。
An application calls the method当应用使用完一条链接后,能够调用 Connection.close() 来关闭这条链接,在这条链接上全部的 Statement 对象也会被关闭。
一条链接关闭后,除了 close(), isClosed() 和 isValid() 方法外,调用其它的方法将会抛出一个 SQLException。
这个方法用来判断一条链接的 close() 方法是否已经被调用过,这个方法不能用来判断链接是否还可用。
可是有写 JDBC 驱动可能会加强 isClosed() 方法,使得能够利用这个方法来判断一条链接是否还可用。在这里,为了最大的可移植性,应用应该经过 Connection.isValid() 来判断一条链接是否还可用。
这个方法用来标识一条链接是否还可用,若是不可用,那么除了 close(),isClosed() 和isValid() 方法以外,调用其它方法将会抛出 SQLException