Introduction
In this post I will show you how to configure JNDI datasource with Spring Boot applications. JNDI (Java Naming Directory Interface) data source is very similar to JDBC (Java Database Connectivity) data source. I will show examples on Oracle as well as MySQL database servers. The MySQL version example is downloadable at the end of this tutorial.
I will also show you how to work with Spring Boot 1.5.9 and Spring Boot 2.2.1 to 2.4.2 versions. The TomcatEmbeddedServletContainerFactory
has been removed from Spring Boot 2 and I will show you how to use TomcatServletWebServerFactory
in Spring Boot 2.
The JNDI data source accesses a database connection that is pre-defined and configured in the application server and published as a JNDI resource or service. Instead of specifying a driver and database as you do with JDBC data sources, you only need to specify the JNDI resource name in our application server.
Why do you need JNDI DataSource?
JNDI comes in rescue when you have to move an application between environments: development -> integration -> test -> production.
If you configure each application server to use the same JNDI name, you can have different databases in each environment but you need not to change your code. You just need to drop the deployable WAR file in the new environment.
Related Posts:
Prerequisites
Java at least 1.8, Spring Boot 1.5.9 or Spring Boot 2.2.1 to 2.4.2, Gradle 4.10.2/5.6/6.7.1, MySQL 8.0.x
Project Setup
Create gradle or maven based Spring Boot project called spring-boot-jndi-datasource in your favorite tool or IDE.
Build Script
The default generated build.gradle script does not include the required dependencies. Therefore you need to update the build script to include the required dependencies.
You will see how to include the required dependencies in build scripts for Spring Boot 1.5.9 and Spring Boot 2.2.1 to 2.4.2.
As it is a REST based application, so I have added starter-web. I included starter-data-jpa to perform to perform database operations.
Spring Boot 1.5.9
buildscript {
ext {
springBootVersion = '1.5.9.RELEASE'
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
compile("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
compile('org.springframework.boot:spring-boot-starter-data-jpa')
runtime('com.oracle.jdbc:ojdbc7:12.1.0.2')
}
Spring Boot 2.2.1 to 2.4.2
buildscript {
ext {
springBootVersion = '2.2.1.RELEASE' to '2.4.2'
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
sourceCompatibility = 12
targetCompatibility = 12
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
implementation("org.springframework.boot:spring-boot-starter-data-jpa:${springBootVersion}")
implementation('mysql:mysql-connector-java:8.0.17/22')
implementation('org.apache.tomcat:tomcat-jdbc:9.0.22/41')
runtimeOnly('javax.xml.bind:jaxb-api:2.4.0-b180830.0359')
}
Notice in the above build script I have added jaxb-api
. This API is required by the Java application. Up to Java 8 version you don’t need to add this API explicitly in your build script because this API is available by default up to Java 8. After Java 9 you have to add it in the build script or build file manually to avoid JAXB related exceptions.
Pom.xml
For maven based project you can use the following pom.xml file. According to your Spring Boot version, you can update the dependency version.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.roytuts</groupId>
<artifactId>spring-boot-jndi-datasource</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>12</maven.compiler.source>
<maven.compiler.target>12</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Building Project
Build the blank project, if you get any Exception related to main class then create a class called JndiDatasourceApp
under package com.roytuts.spring.boot.jndi.datasource with main method and try to build.
Application Configuration
Create below application.properties file under src/main/resources directory.
Oracle Database
I have specified Oracle Database connection details and hibernate dialect.
#datasource
spring.datasource.driverClassName=oracle.jdbc.driver.OracleDriver
spring.datasource.url=jdbc:Oracle:thin:@//:/
spring.datasource.username=scott
spring.datasource.password=tiger
spring.datasource.jndiName=jdbc/myDataSource
#disable schema generation from Hibernate
spring.jpa.hibernate.ddl-auto=none
#DB dialect - override default one
spring.jpa.database-platform=org.hibernate.dialect.Oracle12cDialect
MySQL Database
I have specified MySQL Database connection details.
#datasource
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost/roytuts
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.jndiName=jdbc/myDataSource
#disable schema generation from Hibernate
spring.jpa.hibernate.ddl-auto=none
Remember to update the configuration values according to your own.
Property Config
Create below properties class DatabaseProperties
to load key/value pairs for database connection parameters from application.properties file.
You can also load the properties in different way, such as load the property file using class level annotation @PropertySource("classpath:application.properties")
or as it is the default properties file so it will be loaded by default when application starts up. The you need to @Autowire
Environment
class to get the value for a corresponding key from properties file. For example,
@PropertySource("classpath:application.properties") //optional
public class AppConfig {
@Autowired
private Environment env;
@Bean
public DataSource datasource() {
String jndiName = env.getProperty("jndiName")
//... more code
}
}
In the property file you have all properties declared with a prefix – spring.datasource
. Therefore using Spring Boot it is very easy to load properties in Java class attributes. Simply specify the prefix using @ConfigurationProperties
annotation and add the same property names as class attributes.
package com.roytuts.spring.boot.jndi.datasource.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "spring.datasource")
public class DatabaseProperties {
String url;
String username;
String password;
String driverClassName;
String jndiName;
// getters and setters
}
I have loaded properties in the above configuration class but I won’t be able to inject as a Bean until I declare as a Bean.
So I will declare as a Bean to access the property config class throughout the application wherever required.
Application Config
Now create the AppConfig
class in order to configure DataSource and JPA Repository with Spring’s Transaction support. I will also expose the property config as a bean in this class.
Spring Boot 1.5.9
For Spring Boot version 1.5.9 use below configuration:
package com.roytuts.spring.boot.jndi.datasource.config;
import java.sql.SQLException;
import javax.naming.NamingException;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.apache.catalina.Context;
import org.apache.catalina.startup.Tomcat;
import org.apache.tomcat.util.descriptor.web.ContextResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jndi.JndiObjectFactoryBean;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "com.roytuts.spring.boot.jndi.datasource.repository")
public class AppConfig {
@Bean
public DatabaseProperties databaseProperties() {
return new DatabaseProperties();
}
@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(databaseProperties().getJndiName());
resource.setType(DataSource.class.getName());
resource.setProperty("factory", "org.apache.tomcat.jdbc.pool.DataSourceFactory");
resource.setProperty("driverClassName", databaseProperties().getDriverClassName());
resource.setProperty("url", databaseProperties().getUrl());
resource.setProperty("password", databaseProperties().getPassword());
resource.setProperty("username", databaseProperties().getUsername());
context.getNamingResources().addResource(resource);
}
};
}
@Bean(destroyMethod = "")
public DataSource jndiDataSource() throws IllegalArgumentException, NamingException {
JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
bean.setJndiName("java:comp/env/" + databaseProperties().getJndiName());
bean.setProxyInterface(DataSource.class);
bean.setLookupOnStartup(false);
bean.afterPropertiesSet();
return (DataSource) bean.getObject();
}
@Bean
public EntityManagerFactory entityManagerFactory() throws SQLException, IllegalArgumentException, NamingException {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.ORACLE);
vendorAdapter.setShowSql(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.roytuts.spring.boot.jndi.datasource.entity");
factory.setDataSource(jndiDataSource());
factory.afterPropertiesSet();
return factory.getObject();
}
@Bean
public PlatformTransactionManager transactionManager()
throws SQLException, IllegalArgumentException, NamingException {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory());
return txManager;
}
}
Spring Boot 2.2.1 to 2.4.2
For Spring Boot 2.2.1 to 2.4.2, use below configuration file:
package com.roytuts.spring.boot.jndi.datasource.config;
import java.sql.SQLException;
import javax.naming.NamingException;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.apache.catalina.Context;
import org.apache.catalina.startup.Tomcat;
import org.apache.tomcat.util.descriptor.web.ContextResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jndi.JndiObjectFactoryBean;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "com.roytuts.spring.boot.jndi.datasource.repository")
public class AppConfig {
@Bean
public DatabaseProperties databaseProperties() {
return new DatabaseProperties();
}
@Bean
public TomcatServletWebServerFactory tomcatFactory() {
return new TomcatServletWebServerFactory() {
@Override
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
tomcat.enableNaming();
return super.getTomcatWebServer(tomcat);
}
@Override
protected void postProcessContext(Context context) {
ContextResource resource = new ContextResource();
resource.setType("org.apache.tomcat.jdbc.pool.DataSource");
resource.setName(databaseProperties().getJndiName());
resource.setProperty("factory", "org.apache.tomcat.jdbc.pool.DataSourceFactory");
resource.setProperty("driverClassName", databaseProperties().getDriverClassName());
resource.setProperty("url", databaseProperties().getUrl());
resource.setProperty("username", databaseProperties().getUsername());
resource.setProperty("password", databaseProperties().getPassword());
context.getNamingResources().addResource(resource);
}
};
}
@Bean(destroyMethod = "")
public DataSource jndiDataSource() throws IllegalArgumentException, NamingException {
JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
bean.setJndiName("java:comp/env/" + databaseProperties().getJndiName());
bean.setProxyInterface(DataSource.class);
bean.setLookupOnStartup(false);
bean.afterPropertiesSet();
return (DataSource) bean.getObject();
}
@Bean
public EntityManagerFactory entityManagerFactory() throws SQLException, IllegalArgumentException, NamingException {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.MYSQL);
vendorAdapter.setShowSql(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.roytuts.spring.boot.jndi.datasource.entity");
factory.setDataSource(jndiDataSource());
factory.afterPropertiesSet();
return factory.getObject();
}
@Bean
public PlatformTransactionManager transactionManager()
throws SQLException, IllegalArgumentException, NamingException {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory());
return txManager;
}
}
Of course according to your database type and version you may need to work on the above configuration class.
Entity Class
Create below entity class Company
to define mapping to database table. Please create the table with below columns as found in the below class.
package com.roytuts.spring.boot.jndi.datasource.entity;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "company")
public class Company implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(name = "name")
private String name;
// getters and setters
}
MySQL Table
The corresponding MySQL table structure is given below:
CREATE TABLE `company` (
`id` int unsigned COLLATE utf8mb4_unicode_ci NOT NULL AUTO_INCREMENT,
`name` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Dump Some Data
As you need to test your application, so dump some data:
insert into `company`(`id`,`name`) values
(1,'Tom & Jerry'),
(2,'Order All'),
(3,'Akash Food'),
(4,'Chinese Food'),
(5,'Roy Food');
Spring Repository
Create below JPA Repository interface to perform database activities. Spring provides built-in API through JpaRepository
to perform basic CRUD operations.
package com.roytuts.spring.boot.jndi.datasource.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.roytuts.spring.boot.jndi.datasource.entity.Company;
public interface CompanyRepository extends JpaRepository<Company, Long> {
}
Spring Service
You need below Service class to fetch data from JPA Repository DAO layer.
package com.roytuts.spring.boot.jndi.datasource.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.roytuts.spring.boot.jndi.datasource.entity.Company;
import com.roytuts.spring.boot.jndi.datasource.repository.CompanyRepository;
@Service
public class CompanyService {
@Autowired
private CompanyRepository companyRepository;
public List<Company> getCompanyList() {
return companyRepository.findAll();
}
}
Spring REST Controller
Need to send data to client through below Rest Controller class.
package com.roytuts.spring.boot.jndi.datasource.rest.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.roytuts.spring.boot.jndi.datasource.entity.Company;
import com.roytuts.spring.boot.jndi.datasource.service.CompanyService;
@RestController
public class CompanyRestController {
@Autowired
private CompanyService companyService;
@GetMapping("/company")
public ResponseEntity<List<Company>> getCompanyList() {
return new ResponseEntity<List<Company>>(companyService.getCompanyList(), HttpStatus.OK);
}
}
Spring Main Class
Create main class to start up the application. main class with @SpringBootApplication
annotation is enough to deploy the application into Tomcat server.
package com.roytuts.spring.boot.jndi.datasource;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "com.roytuts.spring.boot.jndi.datasource")
public class JndiDatasourceApp {
public static void main(String[] args) {
SpringApplication.run(JndiDatasourceApp.class, args);
}
}
Testing the Application
Running the above main class will deploy the application into embedded Tomcat server.
Now hit the URL http://localhost:8080/company from browser or REST client you will get the below output:
[{"id":1,"name":"Tom & Jerry"},{"id":2,"name":"Order All"},{"id":3,"name":"Akash Food"},{"id":4,"name":"Chinese Food"},{"id":5,"name":"Roy Food"}]
That’s all. Hope you got an idea how to work with JNDI datasource in Spring Boot 1.5.9 and Spring Boot 2.2.1 to 2.4.2.