Search code examples
javatomcatjdbcjndiapache-commons-dbcp

Why does JNDI return a DataSource with a NULL uri instead of a lookup failure?


I looked for a similar question, but only found similar ones such as Godaddy JNDI Problem---Cannot create JDBC driver of class '' for connect URL 'null' which doesn't answer my generic question.

Tomcat 7.0.8 .. the following code

Context initialContext = new InitialContext();
datasource = (DataSource) initialContext.lookup("java:comp/env/" + "blah");
Connection c = null;
c = datasource.getConnection();

throws this error.

org.apache.tomcat.dbcp.dbcp.SQLNestedException: Cannot create JDBC driver of class '' for connect URL 'null'
   at org.apache.tomcat.dbcp.dbcp.BasicDataSource.createConnectionFactory(BasicDataSource.java:1452)
   at org.apache.tomcat.dbcp.dbcp.BasicDataSource.createDataSource(BasicDataSource.java:1371)
   at org.apache.tomcat.dbcp.dbcp.BasicDataSource.getConnection(BasicDataSource.java:1044)
...
Caused by: java.lang.NullPointerException
   at sun.jdbc.odbc.JdbcOdbcDriver.getProtocol(JdbcOdbcDriver.java:527)
   at sun.jdbc.odbc.JdbcOdbcDriver.knownURL(JdbcOdbcDriver.java:496)
   at sun.jdbc.odbc.JdbcOdbcDriver.acceptsURL(JdbcOdbcDriver.java:319)
   at java.sql.DriverManager.getDriver(DriverManager.java:386)
   at org.apache.tomcat.dbcp.dbcp.BasicDataSource.createConnectionFactory(BasicDataSource.java:1437)
   ... 24 more

I'm not asking why it failed or how to fix it.

My question is, why didn't the LOOKUP fail?

Why did lookup create a useless BasicDataSource with a null URL? Is there a better way to detect if a JNDI name 'doesn't exist' than to try using it and see if it blows up? That approach reminds me of how they test bridges.

I finally bit the bullet and moved a bunch of code from raw JDBC code to JNDI, and many mysteries still remain.

I'd like to avoid downcasting the BasicDataSource if possible, to at least preserve an illusion of database independence.

TIA


Solution

  • As I understand JNDI, it is meant for keeping references to objects or object factories.

    Object business is straightforward - configurer provides a reference to the object, app uses it, as simple as that. But for more complex or generic cases it's more convenient to provide a reference to an object factory which gives far more freedom on how objects should be created.

    As you haven't posted your webapp's web.xml nor Tomcat's conf/server.xml, it's hard to elaborate more, but I would guess you have something similar in your web.xml

    <resource-ref>
      <description>DB Connection</description>
      <res-ref-name>jdbc/myDB</res-ref-name>
      <res-type>javax.sql.DataSource</res-type>
      <res-auth>Container</res-auth>
    </resource-ref>
    

    If you read Tomcat's JNDI howto, you should see this:

    Providing that Tomcat is able to identify an appropriate resource factory to use to create the resource and that no further configuration information is required, Tomcat will use the information in /WEB-INF/web.xml to create the resource.

    Now, let's look at Tomcat's implementation of the above. On line 112, you can see that if given res-type equals to javax.sql.DataSource, it uses org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory to create a new data source.

    That's it. At this point the job of JNDI is over as it has no influence on how objects are created - and rightfully so! Don't forget that JNDI is meant to be storing references to any kind of objects (mail connections, security realms, etc.). It would be both impossible and unreasonable to write a test for every different type of object. And even so, just by having database settings it would be impossible to determine if these are enough to establish a live connection to the real database.

    That's why you have a separate call, datasource.getConnection(), and that's why it throws SQLException which you are supposed to catch and handle.

    p.s. you could possibly argue that BasicDataSourceFactory should be more smart and have a list of mandatory properties/arguments. Maybe. But this is the subject to another question, as originally you have asked why didn't the LOOKUP fail ;)