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:

/assets/jdbc/org.postgresql.Driver.png

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:

/assets/jdbc/DriverManager.png

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.