要取得数据库连接,必须有几个操作:

  • 注册 Driver 实现对象
  • 取得 Connection 实现对象
  • 关闭 Connection 实现对象

注册 Driver 实现对象

下面看一下 Driver 接口文档说明:

每个驱动程序类必须实现的接口。

Java SQL 框架允许多个数据库驱动程序。每个驱动程序都应该提供一个实现 Driver 接口的类。

DriverManager 会试着加载尽可能多的它可以找到的驱动程序,然后,对于任何给定连接请求,它会让每个驱动程序依次试着连接到目标 URL。

强烈建议每个 Driver 类应该是小型的并且是单独的,这样就可以在不必引入大量支持代码的情况下加载和查询 Driver 类。

在加载某一 Driver 类时,它应该创建自己的实例并向 DriverManager 注册该实例。

这意味着用户可以通过调用以下程序加载和注册一个驱动程序 Class.forName(“foo.bah.Driver”)。

这意味着当加载了实现 Driver 接口的类的时候,它将会自动调用 DriverManager. registerDriver() 方法,将该驱动注册到驱动管理器当中去。下面是 mysql 驱动实现类的部分代码:

1
2
3
4
5
6
7
8
9
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
}

可以看出,在 static 区块中进行了注册 Driver 实例的操作。使用 JDBC 时,要求加载 .class 文件的方式有四种:

  1. 使用 Class.forName()。
  2. 自行创建 Driver 接口实现类的实例。
  3. 启动 JVM 时指定 jdbc.drivers 属性。
  4. 设置 JAR 中 /services/java.sql.Driver 文件。

第一种方式刚才已经说明。第二种方式就是直接编写代码:

1
java.sql.Driver driver = new com.mysql.jdbc.Driver();

由于创建对象属于对类的主动使用,会自动加载类的 class 文件,自然也就会运行类的静态区块完成驱动程序注册。

第三种方式就是运行 java 命令时如下:

1
java –Djdbc.drivers=com.mysql.jdbc.Driver;ooo.XXXDriver

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

第四种方式则是 Java SE 6 之后 JDBC 4.0 的新特性,只要在驱动程序实现的 JAR 文件 /services 文件夹中,放置一个 java.sql.Driver 文件,当中编写 Driver 接口的实现类名称全名,DriverManager 会自动读取这个文件并找到指定类进行注册。

首先看一下 DriverManager 的 registerDriver 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
if (!initialized) {
initialize();
}
// DriverInfo的一个存放Driver信息的实体类有3个属性
// Driver driver;
//Class driverClass;
//String driverClassName;
DriverInfo di = new DriverInfo();

di.driver = driver;
di.driverClass = driver.getClass();
di.driverClassName = di.driverClass.getName();

// Not Required -- drivers.addElement(di);

writeDrivers.addElement(di);
println("registerDriver: " + di);

/* update the read copy of drivers vector */
readDrivers = (java.util.Vector) writeDrivers.clone();

}

initialized 静态变量表示是否初始化过驱动。

下面是 initialize() 方法 的具体内容:

1
2
3
4
5
6
7
8
9
10
11
12
static void initialize() {

if (initialized) {
return;
}

initialized = true;

loadInitialDrivers();

println("JDBC DriverManager initialized");
}

如果 initialized为真的时候直接return,如果为假则加载驱动执行 loadInitialDrivers 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}

AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator driversIterator = loadedDrivers.iterator();

try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});

println("DriverManager.initialize: jdbc.drivers = " + drivers);

if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}

取得 Connection 实现对象

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

1
Connection conn = DriverManager.getConnection(jdbcUrl, username, password);

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

实际上除了“协议”在 JDBC URL 中总是 jdbc 开始之外,JDBC URL 格式各家数据库都不相同,必须查询数据库产品使用手册。以 MySQL 为例,“子协议”是桥接的驱动程序、数据库产品名称或连接机制。例如,若使用 MySQL,子协议名称是 mysql。“数据源标识”标出数据库的地址、端口、名称、用户、密码等信息。举个例子来说,MySQL 的 JDBC URL 编写方式如下:

1
2
3
jdbc:mysql://主机名称:连接端口/数据库名称?参数=值&参数=值
jdbc:mysql://localhost:3306/demo?user=root&password=123456
jdbc:mysql://localhost:3306/demo?user=root&password=123&useUnicode=true&characterEncoding=UTF8

通过 DriverManager 获取数据库连接的简单代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private static String url = "jdbc:oracle:thin:@127.0.0.1:1521:ORCL";
private static String user = "scott";
private static String password = "tiger";
public static Connection createconn()
{
Connection connection = null;
try
{
Class.forName("oracle.jdbc.driver.OracleDriver");
connection = DriverManager.getConnection(url, user, password);
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
catch (SQLException e)
{
e.printStackTrace();
}
return connection;
}

SQLException 是在处理 JDBC 时经常遇到的一个异常对象,为数据库操作过程发生错误时的代表对象。SQLException 是检查异常(Checked Exception) , 必须使用 try…catch 明确处理,在异常发生时尝试关闭相关资源。

下面让我们看一下 DriverManager 的 getConnection 的全过程以便于更加了解 JDBC 的获取连接。

下面是 getConnection() 方法代码:

getConnection() 方法有3个重载方法,我们选3个参数的方法进行分析,别的方法基本类似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();

if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}

return (getConnection(url, info, Reflection.getCallerClass()));
}

private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized (DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}

if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}

println("DriverManager.getConnection(\"" + url + "\")");

SQLException reason = null;

for(DriverInfo aDriver : registeredDrivers) {

if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}

} else {
println(" skipping: " + aDriver.getClass().getName());
}

}

if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}

println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}

可以看出主要是循环遍历 drivers,并且看具体 driver 的 connect 方法返回结果,如果不为 null 则直接返回。

由于 DriverManager 可以注册多个驱动,这样就需要解决如何找到正确的驱动进行解析 URL。每个 JDBC 驱动程序使用一个专门的 JDBC URL 作为自我标识的一种方法,所以循环遍历 drivers 就可以找到正确的驱动进行解析 URL,如果都没有则会抛出异常。

关闭 Connection 实现对象

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

—EOF—