Wednesday

How to debug an external Maven build from Eclipse

There are times when you need to debug a remote Maven build running outside of Eclipse, so that you would be able to step through the source code in Eclipse.

Assume our sample remote application is as below:


package com.ron.hr;

public class HelloWorld {

 public static void main(String[] args) {
  System.out.println("line 1");
  System.out.println("line 2");
 }

}

You would need to do these two steps to debug remotely:
  1. Start Maven build outside of Eclipse with mvnDebug command located in Maven installation bin folder
  2. Start debugger in Eclipse to attach to the remote app to debug.
Below example is based on Windows.

Start Maven Build


Run Maven process as below (following goals are for illustration purpose, you may include other goals.)

mvnDebug clean compile exec:java -Dexec.mainClass="com.ron.hr.HelloWorld"

The mvnDebug command set up port number for the remote app so that it waits at the port number for debugger to connect. Once the command is run, it will display following message and wait for debugger to connect to port 8000:

Listening for transport dt_socket at address: 8000


Start Debugger


You should have the HelloWorld source code for your remote app in Eclipse already, also set up breakpoints as you wish. Go to "Run" - "Debug Configuration" - "Remote Configuration", then create new configuration as shown below:





Note that the port number much match the one for remote app (mvnDebug uses port 8000 by default). Click "Debug" and watch the console where the remote app runs in, remote app will run and pause at your first breakpoint, as shown below:



This is the console output from Maven build:

Listening for transport dt_socket at address: 8000
[INFO] Scanning for projects...
...
[INFO] Building hr 0.0.1-SNAPSHOT
...
[INFO] Copying 1 resource
...
[INFO] Changes detected - recompiling the module!
...

[INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ hr ---

Note that Maven build is not running inside Eclipse! The debugger links the source code in Eclipse to the remote Maven build process. How cool this is!!!

Tips for Debugging Remote Java Applications in Eclipse

There are times when you need to debug a remote Java application running outside of Eclipse, for instance, if you run server code in remote machine (or local machine) outside of Eclipse and you want to be able to step through the source code reside in Eclipse.

Assume our sample remote application is as below:


package com.ron.hr;

public class HelloWorld {

 public static void main(String[] args) {
  System.out.println("line 1");
  System.out.println("line 2");
 }

}

And a jar file has been created:
hr-0.0.1-SNAPSHOT.jar

You would need to do these two steps to debug remotely:
  1. Start the remote Java app (even if running in local machine outside of Eclipse) with special JVM arguments
  2. Start debugger in Eclipse to attach to the remote app to debug.
Below example is based on Windows.

Start remote Java Application

Run Java application in command prompt with two steps:

set java_opts=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000

java %java_opts% -classpath target\hr-0.0.1-SNAPSHOT.jar com.ron.hr.HelloWorld

The first command set up port number for the remote app so that it waits at the port number for debugger to connect. The second command is starts the app using the JVM argumets from first command. In real world, your Java application is typically a server process, e.g. Tomcat process. Above example is only for illustration. Once the command is run, it will display:

Listening for transport dt_socket at address: 8000

Start Debugger

You should have the HelloWorld source code for your remote app in Eclipse already, also set up breakpoints as you wish. Go to "Run" - "Debug Configuration" - "Remote Configuration", then create new configuration as shown below:




Note that the port number much match the one for remote app. Click "Debug" and watch the console where the remote app runs in, remote app will run and pause at your first breakpoint, as shown below:

Note that the application is not running inside Eclipse! The debugger links the source code in Eclipse to the remote application. How cool this is!!!

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.

Wednesday

Spring Boot - Part 2: REST API with JPA join query and wildcard search

This is part 2, continuation of Spring Boot tutorial - part 1. Complete code of part 2 can be downloaded from my GitHub. It implements a REST API that achieves following goals:
  1. Given two tables in the database, Department and Employee, and that one department contains many employees and each employee belongs to only one department, We need to design and develop an API to retrieve all employees underneath a given department.
  2. Wildcard search: find all employees whose name contains a given string.
Here is the Pom.xml:

The API will retrieve data from two tables, Department and Employee. We need two entities:
The relationship from Employee to Department is Many to One. The controller method all() returns all employees. Method one() returns the employee with the given id. Note that the associated department is also part of the json. The department object is directly embedded within the employee when returning the json to API client. This is different than using links. The association to all employees is represented by links within json object, which is constructed by HATEOAS. In real world you wouldn't normally want to implement a mixed solution. I did this to demonstrate the different of using HATEOAS and direct object association. In the above controller, notice how it calls EmployeeRepository's method. Note there is no java implementation at all, Spring Boot finds the repository interface and automagically creates implementation based on the query: Since we are using an in-memory DB, we have to populate data when Spring Boot application starts. Spring Boot calls CommandLineRunner that it finds (@Bean will register the returned object with factory in application context), within which we use the previously created data access object to pre-populate associate data. Create LoadDatabase to populate data as following:
Ready...Set....Go! Open command prompt and go to the folder where pom.xml is located and run:
mvn clean spring-boot:run

The above command will first clean the target folder and run all phases/goals up to 'run'. Watch the console, which shows the sequence of application startup process: Spring Boot....Tomcat....JPA....preload data etc. This will start up the embedded server to serve RESTful API!!!

Test in browser by going to

  1.  http://localhost:8080/employees/3, which retrieves employee with id=3
  2. http://localhost:8080/employees, which retrieves all employees.
  3. http://localhost:8080/employees?nameLike=Jen, which retrieves the names containing 'Jen'
  4. http://localhost:8080/employees?deptName=ni, which retrieves employee under department 'ni'.

Notice how the department and employee associate is represented in the json and their difference from links generated via HATEOAS.
Congratulations!!!

See more on
  1. Part 1 - Basics about developing REST API
  2. Part 3 - How to configure it to run with an external server