Adventures in JDBC and the PostgreSQL JDBC driver: Part 3 - Analyzing the JDBC driver registration process
Adventures in JDBC and the PostgreSQL JDBC driver: Part 3 - Analyzing the JDBC driver registration process
In last part of the series of the articles, we have made a sample project to load the PostgreSQL JDBC driver into the running Java virtual machine. In this part, we will examine the JDBC driver startup process.
In our example, we have written a DirectConnection
class to load and use the JDBC driver. The code to load the JDBC driver is this line at the beginning of the class:
Class.forName("org.postgresql.Driver");
The above line of code used the java.lang.Class.forName(...)
method to invoke the class loader to load the org.postgresql.Driver
class. The org.postgresql.Driver
class implements the java.sql.Driver
interface, and it provides the methods to connect to the underlying database server.
Database vendors should implement their own drivers. From the users perspective, they can use the standard JDBC APIs to connect to the underlying database system without worrying about the differences between database vendors.
Here is the class diagram of the org.postgresql.Driver
:
From the above class diagram, we can see the org.postgresql.Driver
class has a connect(...)
method to handle the connection to the underlying database server. Here is the relative code in connect(...)
method to make the connection:
return makeConnection(url, props);
From above code, we can see the connect(...)
method calls the makeConnection(...)
method for database connection. Here is the code of makeConnection(...)
method:
private static Connection makeConnection(String url, Properties props) throws SQLException {
return new PgConnection(hostSpecs(props), user(props), database(props), props, url);
}
From above code, we can see that an instance of org.postgresql.PgConnection
class is created. PgConnection
is a huge class that wraps the calls and operations to underlying database. We will check the detail of this class later.
In org.postgresql.Driver
, there is static code that will be called during class loading. Here is the relative code:
static {
try {
// moved the registerDriver from the constructor to here
// because some clients call the driver themselves (I know, as
// my early jdbc work did - and that was based on other examples).
// Placing it here, means that the driver is registered once only.
register();
} catch (SQLException e) {
throw new ExceptionInInitializerError(e);
}
}
The above static code will call register()
method to register the driver itself into java.sql.DriverManager
. Here is the code of register()
method:
/**
* Register the driver against {@link DriverManager}. This is done automatically when the class is
* loaded. Dropping the driver from DriverManager's list is possible using {@link #deregister()}
* method.
*
* @throws IllegalStateException if the driver is already registered
* @throws SQLException if registering the driver fails
*/
public static void register() throws SQLException {
if (isRegistered()) {
throw new IllegalStateException(
"Driver is already registered. It can only be registered once.");
}
Driver registeredDriver = new Driver();
DriverManager.registerDriver(registeredDriver);
Driver.registeredDriver = registeredDriver;
}
From above code, we can see the register()
method in org.postgresql.Driver
class register itself into the java.sql.DriverManager
class. The java.sql.DriverManager
is provided by JDK. It’s not an interface, instead it is a class provided by JDBC library that for managing all the database drivers loaded into Java virtual machine. All the driver implementations need to register itself into DriverManager during class loading phase. The org.postgresql.Driver
follows this rule as we see in above. Here is the class diagram of the java.sql.DriverManager
class:
From the above diagram, we can see that the java.sql.DriverManager
provides methods like registerDriver(...)
and deregisterDriver(...)
for the drivers to register or deregister themselves. In addition, it provides getConnection(...)
methods to the users. In our own DirectConnection
example, we use the java.sql.DriverManager
to get the connection, and here is the relative code:
String url = "jdbc:postgresql://localhost/weli";
Properties props = new Properties();
props.setProperty("user","weli");
props.setProperty("password","");
props.setProperty("ssl","false");
Connection conn = DriverManager.getConnection(url, props);
The above code defines the properties of our local database server, and pass it to java.sql.DriverManager
to get the connection. Because the org.postgresql.Driver
has been registered into java.sql.DriverManager
, so it can find the the correct driver and call it the make physical connections to underlying database system.
The url to connect to our PostgreSQL database server is jdbc:postgresql://localhost/weli
. The java.sql.DriverManager
used this information to find the correct driver to use. On the other side, the org.postgresql.Driver
class needs to detect the jdbc:postgresql
string correctly to handle the connection request sent by java.sql.DriverManager
. Here is the relative code in the connect(...)
method of org.postgresql.Driver
:
public java.sql.Connection connect(String url, Properties info) throws SQLException {
// get defaults
Properties defaults;
if (!url.startsWith("jdbc:postgresql:")) {
return null;
}
...
The above code shows that the connect(...)
method of the org.postgresql.Driver
will check if the request is for “jdbc:postgresql:”. If the connection request isn’t for PostgreSQL database, it will just return null and let java.sql.DriverManager
to try other database drivers. The getConnection(...)
method in java.sql.DriverManager
will traverse the registered database drivers to see who can handle the connection request from the user. Here is the relative code in getConnection(...)
method:
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
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());
}
}
From the above code, we can see java.sql.DriverManager
will check each registered database driver, and try to use it to handle the connect request. If a driver can handle the connection, it will return the java.sql.Connection
class. For PostgreSQL driver, it will return the PgConnection
class instance. We have seen this in makeConnection(...)
method of org.postgresql.Driver
class, which is called by the connect(...)
method.
Until now, we have learned the loading process of the JDBC driver. In addition, we hav checked how does JDBC manages the drivers and use them to interact with the underlying database system. In the next part of the articles, let’s check the java.sql.Connection
and org.postgresql.PGConnection
classes in detail.