Introduction

In this post we will see how to configure JNDI datasource with Spring Boot. JNDI data source is very similar to JDBC data source. 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 we do with JDBC data sources, we only need to specify the JNDI resource name in our application server.

Why do we use 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.

You may also like to read Spring Data JPA CRUD Example and Spring Data JPA Entity Graphs

Prerequisites

Java 1.8, Spring Boot, Gradle 4, Eclipse

Example

In this example we will create Spring Boot application. We will use Spring Data JPA for quering database.

Creating Project

Create Gradle based Spring Boot project called SpringBootJndiDataSource in Eclipse using below build.gradle script.

Notice we have excluded the default loggers from all modules and added log4j API for the logging purpose.

As it is a REST based application, so we have added starter-web. We include starter-data-jpa to perform to perform database operations.

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'
jar {
    baseName = 'SpringBootJndiDataSource'
    version = '0.0.1-SNAPSHOT'
    manifest {
        attributes("Main-Class": "com.roytuts.main.Application")
    }
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
configurations {
    all*.exclude module : 'spring-boot-starter-logging'
}
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')
    compile("org.slf4j:slf4j-api")
    compile("org.slf4j:slf4j-log4j12")
    compile("commons-logging:commons-logging:1.2")
}

Building Project

Build the blank project, if you get any Exception related to main class then create a class called Application under package com.roytuts.main with main method and try to build.

Application Configuration

Create below application.properties file under src/main/resources directory.

In this file we override embedded Tomcat server’s default port.

We specify Oracle Database connection details and hibernate dialect.

#start server at below port
server.port=9999
#DB connection parameters
connection.url=jdbc:Oracle:thin:@//:/
connection.username=
connection.password=
connection.driverClassName=oracle.jdbc.driver.OracleDriver
connection.jndiName=jdbc/oracleDataSource
#DB dialect - override default one
spring.jpa.database-platform=org.hibernate.dialect.Oracle12cDialect

Property Config

Create below properties class DbConnectionProps to load key/value pairs for database connection parameters from application.properties file.

In the above property file we have all properties declared with a prefix – connection. Therefore using Spring Boot it is very easy to load properties in Java class attributes. Simply specify the prefix [email protected] annotation and add the same property names as class attributes.

package com.roytuts.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "connection")
public class DbConnectionProps {
	String url;
	String username;
	String password;
	String driverClassName;
	String jndiName;
	//getters and setters
}

Property Config as a Bean

Create ApplicationConfig class in order to create Spring Beans such as we need for DbConnectionProps.

We have loaded properties in the above configuration class but we won’t be able to inject as a Bean until we declare as a Bean.

So declare as a Bean to access the property config class throughout the application.

package com.roytuts.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.roytuts.properties.DbConnectionProps;
@Configuration
public class ApplicationConfig {
	@Bean
	public DbConnectionProps dbConnectionProps() {
		return new DbConnectionProps();
	}
}

Datasource Config

Now create the DataSourceConfig class in order to configure DataSource and JPA Repository with Spring’s Transaction support.

package com.roytuts.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;
import com.roytuts.properties.DbConnectionProps;
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "com.roytuts.jparepository")
public class DataSourceConfig {
	@Autowired
	DbConnectionProps props;
	@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(props.getJndiName());
				resource.setType(DataSource.class.getName());
				resource.setProperty("factory", "org.apache.tomcat.jdbc.pool.DataSourceFactory");
				resource.setProperty("driverClassName", props.getDriverClassName());
				resource.setProperty("url", props.getUrl());
				resource.setProperty("password", props.getPassword());
				resource.setProperty("username", props.getUsername());
				context.getNamingResources().addResource(resource);
			}
		};
	}
	@Bean(destroyMethod = "")
	public DataSource jndiDataSource() throws IllegalArgumentException, NamingException {
		JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
		bean.setJndiName("java:comp/env/" + props.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.model");
		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;
	}
}

Entity Class

Create below entity class LogDetails to define mapping to database table. Please create the table with below columns as found in the below class.

package com.roytuts.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "LOG_DETAILS")
public class LogDetails implements Serializable {
	private static final long serialVersionUID = -2711129826239216746L;
	@Id
	@Column(name = "ID")
	Long id;
	@Column(name = "LOG_NAME")
	String logName;
	@Column(name = "DESCRIPTION")
	String logDetails;
	//getters and setters
}

Spring Repository

Create below JPA Repository interface to perform database activities.

package com.roytuts.jparepository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.roytuts.model.LogDetails;
public interface LogDetailsRepository extends JpaRepository<LogDetails, Long> {
}

Spring Service

We need below Service class to fetch data from JPA Repository DAO layer.

package com.roytuts.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.roytuts.jparepository.LogDetailsRepository;
import com.roytuts.model.LogDetails;
@Service
public class LogDetailsService {
	@Autowired
	LogDetailsRepository repository;
	public List<LogDetails> getLogDetails() {
					return repository.findAll();
	}
}

Spring REST Controller

Need to send data to client through below Rest Controller class.

package com.roytuts.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.model.LogDetails;
import com.roytuts.service.LogDetailsService;
@RestController
public class LogDetailsRestController {
	@Autowired
	LogDetailsService service;
	@GetMapping("/logs")
	public ResponseEntity<List<LogDetails>> getlogDetails() {
		return new ResponseEntity<>(service.getLogDetails(), HttpStatus.OK);
	}
}

Spring Main Class

Create main class to start up the application

package com.roytuts.main;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "com.roytuts")
public class Application {
	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

Thanks for reading.

Leave a Reply

Your email address will not be published. Required fields are marked *