Saturday, December 20, 2014

JNDI Lookup in Spring Boot

I started using spring boot since last year and pleased with it so far. In this article I want to talk about one common issue that you may run into and that is configuring JDNI resource. Well configuring JNDI in spring boot application and running it in external tomcat is a piece of cake. But if you are wondering how to configure JNDI resource so that you could run your app in external tomcat, embedded tomcat and even integration tests against same configuration, then here is how.
External Tomcat
Let’s look at the easier one first and that is configuring your external tomcat.

If you are planning on sharing resources across many applications deployed into same tomcat then you would configure global naming resources in server.xml like below,


<Resource auth="Container" 
 driverClassName="Your DB driver class name" 
 global=" jdbc/DB_Name " 
 maxActive="20" maxIdle="0" maxWait="10000" 
 name="jdbc/DB_Name" 
 username="dbuser"
 password="password" 
 type="javax.sql.DataSource" 
 url="Your connection URL" />


Since this is a global resource you would require defining a resource link in the context.xml like below,


<ResourceLink name=" jdbc/DB_Name "
                global=" jdbc/DB_Name”
                auth="Container"
                type="javax.sql.DataSource" />



At this point if you restart your tomcat this resource should be registered and ready to look up through your application using the name in resource link configuration.

In your application’s application.properties add this line

spring.datasource.jndi-name=java:comp/env/jdbc/DB_Name



Spring boot will take care of looking up and loading the datasource automagically for you. Now you can define a bean JdbcTemplate or NamedJdbcTemplate in your application’s ServletIntializer class like below but again this is optional.


@Bean
JdbcTemplate jdbcTemplate(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(
                dataSource);

        return jdbcTemplate;
}

@Bean
NamedParameterJdbcTemplate namedParameterJdbcTemplate(DataSource dataSource) {
        NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(
                dataSource);

        return namedParameterJdbcTemplate;
}

Now in your Repository class simply autowire it,


@Autowired
private JdbcTemplate jdbcTemplate;

Technically you are done configuring jndi resource for your application.

 Embedded Tomcat
Now you want to have the same resource configured within the embedded tomcat so that you can run the app with in the embedded server.

In your application’s application.properties add these lines,


connection.url=jdbc://localhost:port/dbname
connection.username=user
connection.password=password
connection.driverClassName=Driver Class Name
connection.jndiName=jdbc/DB_Name


In your ServletIntialzer class you will have to define a bean TomcatEmbeddedServletContainerFactory and instantiate it. First thing to do is to enable the naming service in embedded server which is turned off by default. Second override postProcessContext method to create ContextResource, add it to the catalina context. The resource name will be added to java:comp/env context.


@Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
        return new TomcatEmbeddedServletContainerFactory() {

            @Override
            protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
                    Tomcat tomcat) {
                tomcat.enableNaming();
                return super.getTomcatEmbeddedServletContainer(tomcat);
            }

            @Override
            protected void postProcessContext(Context context) {
                ContextResource resource = new ContextResource();
                resource.setName(connectionJndiName);
                resource.setType(DataSource.class.getName());
                resource.setProperty("driverClassName",
                        connectionDriverClassName);
                resource.setProperty("url", connectionURL);
                resource.setProperty("password", connectionPassword);
                resource.setProperty("username", connectionUserName);

                context.getNamingResources().addResource(resource);
            }
        };
}


Now you can define one more bean to create DataSource by looking up the resource that you just registered using the Jndi name. The complete jndi name to look up “java:comp/env/jdbc/DB_Name”


@Bean(destroyMethod = "")
    public DataSource jndiDataSource() throws IllegalArgumentException,
            NamingException {
        JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
        bean.setJndiName(jndiLookupName);
        bean.setProxyInterface(DataSource.class);
        bean.setLookupOnStartup(false);
        bean.afterPropertiesSet();
        return (DataSource) bean.getObject();
    }



Now run your Application class, JdbcTemplate should be autowired in your Repository class and ready to execute queries.

Integration Tests for Repository
I do not believe in writing unit tests for Dao/Repository classes with or without mocking framework. It doesn’t make sense to write these tests just for the sake of writing test cases but what makes sense is to write integration tests. Again writing integration tests with some in memory databases doesn’t make whole lot of sense either as you will run into issues using some in built functionalities of your real database. It could simply be a nightmare if there are differences in functionalities supported in your real database vs in memory database. So I would write my integration tests against real database. As long as they are transactional and wipe out the data at the end.
Here’s how you could do that,
Spring provides a simple implementation of a JNDI naming context builder class SimpleNamingContextBuilder that helps to bind a JDBC DataSource to a JNDI location that can be exposed through initialContext().
Take a look at the class below,


public class JndiDatasourceCreator {
    private static final Logger LOGGER = Logger
     .getLogger(JndiDatasourceCreator.class);
    
    private static final String username = "userName";
    private static final String password = "password";
    private static final String jndiName = "DB_Name";
    private static final String driverClassName="Driver Name";
    private static final String url="jdbc://localhost:port/dbName";
    public static BasicDataSource dataSource;
    
    public static void create() throws Exception {
 try {
     final SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
     dataSource = new BasicDataSource();
     dataSource.setUsername(username);
     dataSource.setPassword(password);
     dataSource.setDriverClassName(driverClassName);
     dataSource.setUrl(url);
     builder.bind("java:comp/env/jdbc/" + jndiName, dataSource);
     builder.activate();
 } catch (NamingException ex) {
     LOGGER.info(ex.getMessage());
 }
    }
}

You could use Apache's Commons DBCP’s BasicDataSource to use a real pool and make sure it is static so that it’s setup one time.
Now you could write test case for your Dao like below,


@Autowired
private UserRepository userRepository;

@Before
public void setUp() throws Exception {
  JndiDatasourceCreator.create();
  insertTestUsers();//private test harness method
}

@Test
public void getUsers() {
  List<User> users = userRepository
  .getUsers();
 assertEquals(users.get(0).getName(), testUsers.get(0).getName());
}


I would recommend doing setup in the parent test class that every test could inherit.

That’s it for now, enjoy coding.





7 comments:

  1. Very useful post! Thank you!

    ReplyDelete
  2. Ditto ... very helpful. Thanks for posting.

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. How can we run Spring boot application in External tomcat at first place? I'm stuck in that.. Could you guide me??

    ReplyDelete
  5. I'm running the application with Undertow now, but need to run it in External tomcat for development mode with External JNDI values.

    ReplyDelete
  6. Me to run stuck with Spring boot application in External tomcat.Can u please guide me ?

    ReplyDelete
  7. Me to run stuck with Spring boot application in External tomcat.Can u please guide me ?

    ReplyDelete