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.
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; }
@Autowired private JdbcTemplate jdbcTemplate;
Technically you are done configuring jndi
resource for your application.
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.
Very useful post! Thank you!
ReplyDeleteDitto ... very helpful. Thanks for posting.
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteHow can we run Spring boot application in External tomcat at first place? I'm stuck in that.. Could you guide me??
ReplyDeleteI'm running the application with Undertow now, but need to run it in External tomcat for development mode with External JNDI values.
ReplyDeleteMe to run stuck with Spring boot application in External tomcat.Can u please guide me ?
ReplyDeleteMe to run stuck with Spring boot application in External tomcat.Can u please guide me ?
ReplyDeleteHow did you initialized the following variables?
ReplyDeleteresource.setName(connectionJndiName);
resource.setType(DataSource.class.getName());
resource.setProperty("driverClassName",
connectionDriverClassName);
resource.setProperty("url", connectionURL);
resource.setProperty("password", connectionPassword);
resource.setProperty("username", connectionUserName);
It doesn't seems that this implementation can be mixed with the Spring Auto-configuration of a Datasource with automatic reading of application.properties
I want to get String values from jndi
ReplyDeleteCan you please provide the source code
ReplyDeleteCan you please help me to configure JNDI in spring boot which should deploy in websphere 9.0.0.7. I want to inject the datasource to jparepository.
ReplyDeletehow do we get it configured with JNDI created on weblogic server
ReplyDeleteHi,
ReplyDeleteFirst of all thanks for writing this nice article.I have similar requirement for using xa datsource with embedded tomcat.Can you please help me in this.I already have configure atmomikos.What changes should I do to support db2