How can I bind a DataSource to an InitialContext f

2020-02-02 11:11发布

问题:

I'm trying to run JUnit tests on database "worker" classes that do a jndi lookup on an InitialContext to get a DataSource. The worker classes are usually running on a Glassfish v3 App Server which has the appropriate jdbc resources defined.

The code runs just fine when deployed on the App Server but doesn't run from the JUnit testing environment, because obviously it can't find the jndi resources. So I tried to setup an InitialContext in the test class that binds a datasource to the appropriate context, but it doesn't work.

Here is the code I have in the test

@BeforeClass
public static void setUpClass() throws Exception {
    try {
        // Create initial context
        System.setProperty(Context.INITIAL_CONTEXT_FACTORY,
            "org.apache.naming.java.javaURLContextFactory");
        System.setProperty(Context.URL_PKG_PREFIXES,
            "org.apache.naming");
        InitialContext ic = new InitialContext();

        ic.createSubcontext("java:");
        ic.createSubcontext("java:/comp");
        ic.createSubcontext("java:/comp/env");
        ic.createSubcontext("java:/comp/env/jdbc");

        // Construct DataSource
        SQLServerConnectionPoolDataSource testDS = new SQLServerConnectionPoolDataSource();
        testDS.setServerName("sqlserveraddress");
        testDS.setPortNumber(1433);
        testDS.setDatabaseName("dbname");
        testDS.setUser("username");
        testDS.setPassword("password");

        ic.bind("java:/comp/env/jdbc/TestDS", testDS);

        DataWorker dw = DataWorker.getInstance();
    } catch (NamingException ex) {
        Logger.getLogger(TitleTest.class.getName()).log(Level.SEVERE, null, ex);
    }
}

Then the DataWorker class has a method with the following code, more or less

InitialContext ic = null;
DataSource ds = null;
Connection c = null;
PreparedStatement ps = null;
ResultSet rs = null;
String sql = "SELECT column FROM table";
try{
    ic = new InitialContext();
    ds = (DataSource) ic.lookup("jdbc/TestDS");
    c = ds.getConnection();
    ps = c.prepareStatement(sql);
    // Setup the Prepared Statement
    rs = ps.executeQuery();
    if(rs.next){
        //Process Results
    }
}catch(NamingException e){
    throw new RuntimeException(e);
}finally{
    //Close the ResultSet, PreparedStatement, Connection, InitialContext
}

If I change the
ic.createSubContext("java:/comp/env/jdbc");
ic.bind("java:/comp/env/jdbc/TestDS",testDS);
lines to
ic.createSubContext("jdbc");
ic.bind("jdbc/TestDS",testDS);
The worker class is able to find the DataSource, but fails giving an error saying that "username failed to login to the server".

If I pass the DataSource that I create in the JUnit method directly into the worker, it can connect and run queries.

So, I would like to know how to bind a DataSource that can be looked up by the worker class without being in the Web Container.

回答1:

When I last tried something like this a few years ago, I finally gave up and refactored: at that point, you could NOT create a DataSource outside of a container. Maybe you can, now, maybe someone's mocked something up.

Still, that smells... You shouldn't have ANY "business logic" code directly dependent on DataSources or JNDI lookups or such. That's all plumbing to be wired together outside your code.

How flexible is your design? If your code under test is directly dependent on a DataSource (or even obtains its own Connection), refactor it. Injecting a Connection will let you can test all you like with plain old JDBC, even using an in-memory implementation, and save you from having to prop up a lot of unnecessary (for the test, anyway) infrastructure to do it.



回答2:

I found that example to be wrong as well. This worked for me.

ic.createSubcontext("java:comp");
ic.createSubcontext("java:comp/env");
ic.createSubcontext("java:comp/env/jdbc");

final PGSimpleDataSource ds = new PGSimpleDataSource();
ds.setUrl("jdbc:postgresql://localhost:5432/mydb");
ds.setUser("postgres");
ds.setPassword("pg");

ic.bind("java:comp/env/jdbc/mydb", ds);

The difference you'll note is that the '/' after 'java:' in each of the contexts is wrong and shouldn't be there.