Tuesday

Spring Boot - Part 3: How to configure it to run with an external server

Part 1 and Part 2 are about running the application locally, using the embedded server in Spring Boot. In real world, we typically need to deploy the application to an external container or server (for example, Tomcat).

There are difference how Spring Boot should create database connections using property files. In local environment, you can just put id/password in the application.properties file, or use other methods.

While the application is running in the server, instead of using DB connection property (id/password/url etc) in application.properties file, it is better to have Spring Boot use JNDI data source, which is configured in server and the connection is also managed by the server container. This is different from the local configuration when running embedded server in Spring Boot.

How do we configure JNDI? You probably don't need JNDI when testing locally, then the question becomes: what is the best practice to manage the different environment configurations? There are four parts of changes: Code changes, POM file changes, Property file changes and Server configuration.

Code changes:

In order to run Spring Boot within Tomcat servlet context, main app needs to extend the initializer: 

@SpringBootApplication 
public class SpringApp extends SpringBootServletInitializer {...} 

Property file changes:

Spring Boot uses active profile to manage environment specific configurations. Create one application.properties, and also create one application-{env}.properties for each environment where {env} is your environment name. When a profile is activated via JVM arguments (corresponding to an environment), Spring Boot will load the related property file. Note that application.properties is shared among all environment and values can be overridden by application-{env}.properties! 

 In you local property file, you may include DB connection id/password. In the test or production environment property files, do not include id/password, you would instead use JNDI name in application-{env}.properties, also you must specify type of data source. Make sure to enable Hikari connection pool logging to make sure Spring doesn't accidentally use other connection pooling framework:

spring.datasource.jndi-name=jdbc/EmployeeDB
spring.datasource.type=com.zaxxer.hikari.HikariDataSource


# JPA specific configs
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql=true
spring.jpa.properties.hibernate.id.new_generator_mappings=false
spring.jpa.properties.hibernate.default_schema=dbschema
spring.jpa.properties.hibernate.search.autoregister_listeners=false
spring.jpa.properties.hibernate.bytecode.use_reflection_optimizer=false

# Enable logging to verify that HikariCP is used, the second entry is specific to HikariCP
logging.level.org.hibernate.SQL=DEBUG
logging.level.com.zaxxer.hikari.HikariConfig=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE 


Tomcat configuration 


The data source is configured in the external Tomcat server. 

Add DB connection JNDI data source in context.xml: 
<Resource name="jdbc/EmployeeDB"                  global="jdbc/EmployeeDB"                  auth="Container"
                  factory="com.zaxxer.hikari.HikariJNDIFactory"
                  type="javax.sql.DataSource"
                  dataSource.user="xxxxx"
                  dataSource.password="xxxxxx"

In /conf/web.xml, set up resource ref for JNDI (recommended):
<resource-ref>
  <description>
    Resource reference to a factory for java.sql.Connection
    instances that may be used for talking to a particular
    database that is configured in the <Context>
    configuration for the web application.
  </description>
  <res-ref-name>
    jdbc/EmployeeDB
  </res-ref-name>
  <res-type>
    javax.sql.DataSource
  </res-type>
  <res-auth>
    Container
  </res-auth>
</resource-ref>

POM Changes:

Since we need to deploy a WAR file to Tomcat, we change packaging method in POM:


<packaging>war</packaging>


Add spring-boot-starter-tomcat dependency into POM file:

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-tomcat -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <version>2.1.3.RELEASE</version>
</dependency>

Spring Boot 2.x uses HikariCP connection pooling as it is better than Tomcat's connection pooling. We need to exclude tomcat-jdbc from dependency in POM to make sure Spring uses Hikari:
<dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
                
                <exclusions>
                    <exclusion>
                        <groupId>org.apache.tomcat</groupId>
                        <artifactId>tomcat-jdbc</artifactId>
                    </exclusion>
                </exclusions>
</dependency>

Set up Spring Boot Profile in POM using spring-boot-maven-plugin:

<project>
    <...>
    <profiles>
        <profile>
            <id>development</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                        <configuration>
                            <profiles>
                                <profile>local</profile>
                            </profiles>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    <profiles>
    </...>
</project>
 
You run this command in local environment when you build for local:
mvn spring-boot:run -Plocal 

When you are ready to package for actual test environment, you would need to run:
mvn package

Note that there is no profile name appended, since Spring Active Profile will be managed by JVM arguments set up in server. For example, add this to Tomcat JVM option:
-Dspring.profiles.active="test"

Finally, with all these effort, you are ready to run maven command to package WAR file and deploy into Tomcat's webapps folder.

No comments: