Reputation: 493
I'm trying to deploy a spring boot application as a WAR into Tomcat. When I have things hard-coded, it works just fine, and there are no problems. But after my initial testing, I tried to clean things up and go back to the properties. And I'm finding that my application.properties file is not being read by the application during deployment in Tomcat. And I'm just confused.
My initial testing with Tomcat, to get the JNDI configured, was hard-coded, and everything worked just fine then:
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
return (DataSource) new JndiTemplate().lookup("java:/comp/env/jdbc/myJndiLocal");
}
}
My context.xml is good and my tomcat's server.xml global resources are configured. The connection was made, and everything worked. Of course, hard-coded isn't what I want, so I switched to the properties.
Now, my problems are related to the org.springframework.boot.autoconfigure.jdbc.DataSourceProperties. I have my properties configured in Application.properties, which is in my resources directory:
spring.datasource.url=jdbc:postgresql://localhost:5432/MY_DB?currentSchema=public
spring.datasource.username=postgresql_username
spring.datasource.password=postgresql_password
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.jndiName=jdbc/myJndiLocal
And I created a datasource configuration that prepares my database connection:
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource(DataSourceProperties dataSourceProperties) {
System.out.printf(" !!!!! Datasource Config !!!!! %n" +
" url: %s%n" +
" username: %s%n" +
" password: %s%n" +
" driver: %s%n" +
" jndi: %s%n" +
" jndi full: %s%n",
dataSourceProperties.getUrl(), dataSourceProperties.getUsername(), dataSourceProperties.getPassword(),
dataSourceProperties.getDriverClassName(), dataSourceProperties.getJndiName(),
String.format("java:/comp/env/%s", dataSourceProperties.getJndiName())
);
if (dataSourceProperties.getJndiName() != null && !dataSourceProperties.getUrl().isBlank()) {
try {
return (DataSource) new JndiTemplate().lookup(String.format("java:/comp/env/%s", dataSourceProperties.getJndiName()));
}
catch (Exception e) {
System.out.println(" !!!!! JNDI Resource could not be found! Regular JDBC Configuration will be used instead. !!!!! ");
}
}
DataSourceBuilder<?> dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName(dataSourceProperties.getDriverClassName());
dataSourceBuilder.url(dataSourceProperties.getUrl());
dataSourceBuilder.username(dataSourceProperties.getUsername());
dataSourceBuilder.password(dataSourceProperties.getPassword());
return dataSourceBuilder.build();
}
}
The first printf statement is just for current debugging, and I'll remove it later. My goal is to default to JNDI if present, otherwise use standard JDBC configuration. When running as a JAR with the embedded tomcat via my IDE, there are no problems. Properties are read into place, and everything gets configured as I expect.
!!!!! Datasource Config !!!!!
url: jdbc:postgresql://localhost:5432/MY_DB?currentSchema=public
username: postgresql_username
password: postgresql_password
driver: org.postgresql.Driver
jndi: jdbc/myJndiLocal
jndi full: java:/comp/env/jdbc/myJndiLocal
!!!!! JNDI Resource could not be found! Regular JDBC Configuration will be used instead. !!!!!
The problem now, however, is that my properties are all completely null when deployed as a WAR file into an external Tomcat.
!!!!! Datasource Config !!!!!
url: null
username: null
password: null
driver: null
jndi: null
jndi full: java:/comp/env/null
I'm using the SpringBootServletInitializer for the WAR deployment, so I have to assume that is causing some kind of issue, but I'm at a loss presently as to what it is.
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EntityScan(basePackages = {"com.mycompany.myapp.model", "com.mycompany.model"})
@EnableJpaRepositories(basePackages = {"com.mycompany.dao.jpa"})
@ComponentScan(basePackages = {
"com.mycompany.myapp",
"com.mycompany.service",
"com.mycompany.service.impl",
"com.mycompany.myapp.webapp.pages",
"com.mycompany.webapp.util",
"com.mycompany.webapp.util.migration",
"com.mycompany.myapp.annotations.validation",
"com.mycompany.annotations"
})
public class MyAppApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(MyAppApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(MyAppApplication.class, args);
}
}
I'm on the latest Spring Boot, 3.3.4, and am using the latest Tomcat, 10.1.31. I'm also utilizing Maven for the dependencies and builds. I have already confirmed that the Application.properties is inside the proper location in the WAR file - /WEB-INF/classes/Application.properties. Why are my properties not being read into the application, and what can I do to address this? I've been doing some reading and found this question, which is similar, but I'm not doing anything inside of my main method. DataSourceProperties is a default Spring class, so I would have expected it would just populate. The use of it as a parameter to my configuration method I would have expected to force a jumpstart even if it wasn't prepared automatically.
Found a resolution, although I don't really understand why it is needed, nor why it's helping. I needed to add the @PropertySource annotation to my DataSourceConfig. Once I did that, all my properties were in place, and everything loaded up perfectly, connecting to my defined JNDI.
@Configuration
@PropertySource("classpath:Application.properties")
public class DataSourceConfig {
....
}
So, the Application.properties clearly must already have been on the classpath in order for that annotation to work properly. I'm now mainly confused as to why it wasn't being used by the application? Is there something about Tomcat deployment or my current configuration that is forcing Spring to utilize its own internal defaults and ignore my configuration files? The documentation is telling me that the file should be picked up automatically.
Upvotes: 0
Views: 255
Reputation: 3144
Is your Application.properties
a typo?
The default name should be lowercase application.properties
.
In My First Answer, My Project.
If the file name of the configuration file is changed to ZZZApplication.properties
,
DemoJspApplication.java
must be modified to:
ADD @PropertySource("classpath:ZZZApplication.properties") // Specifying a custom configuration file
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@PropertySource("classpath:ZZZApplication.properties") // Specifying a custom configuration file
@SpringBootApplication
public class DemoJspApplication {
public static void main(String[] args) {
System.out.println(">>> DemoJspApplication start");
SpringApplication.run(DemoJspApplication.class, args);
System.out.println(">>> DemoJspApplication after run");
}
}
I've verified and tested it.
Upvotes: 1
Reputation: 3144
There's so much you can do, just using the default settings should be enough.
demo-war-jsp-tag-db-Tomcat
├── pom.xml
├── Docker_MySQL
│ └── init.sql
├── Docker_PostgreSQL
│ └── init.sql
└── src
└── main
├── java
│ └── com
│ └── example
│ ├── DemoJspApplication.java
│ ├── ServletInitializer.java
│ ├── HelloJDBCJSPController.java
│ ├── HelloJSPController.java
│ └── HelloController.java
├── resources
│ └── application.properties
└── webapp
├── index.jsp
├── index-tag-001.jsp
├── index-tag-002.jsp
├── index-tag-003.jsp
└── WEB-INF
└── jsp
├── show-hello-jdbc.jsp
└── hello.jsp
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo-jsp</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>demo-jsp</name>
<description>Demo project for Spring Boot</description>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</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-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>jakarta.servlet.jsp.jstl</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>jakarta.servlet.jsp.jstl</groupId>
<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
<build>
<finalName>hello</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
CREATE TABLE STUDENTS (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
age INT,
grade VARCHAR(10),
email VARCHAR(100),
enrollment_date DATE
);
INSERT INTO STUDENTS (name, age, grade, email, enrollment_date)
VALUES
('MySQL Alice', 20, 'A', '[email protected]', '2023-01-15'),
('MySQL Bob', 22, 'B', '[email protected]', '2023-02-10'),
('MySQL Charlie', 21, 'A', '[email protected]', '2023-03-12'),
('MySQL David', 23, 'C', '[email protected]', '2023-04-08'),
('MySQL Eva', 20, 'B', '[email protected]', '2023-05-05');
CREATE TABLE IF NOT EXISTS STUDENTS (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
age INT,
grade VARCHAR(10),
email VARCHAR(100),
enrollment_date DATE
);
-- Insert sample data
INSERT INTO STUDENTS (name, age, grade, email, enrollment_date)
VALUES
('PostgreSQL Alice', 20, 'A', '[email protected]', '2023-01-15'),
('PostgreSQL Bob', 22, 'B', '[email protected]', '2023-02-10'),
('PostgreSQL Charlie', 21, 'A', '[email protected]', '2023-03-12'),
('PostgreSQL David', 23, 'C', '[email protected]', '2023-04-08'),
('PostgreSQL Eva', 20, 'B', '[email protected]', '2023-05-05');
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoJspApplication {
public static void main(String[] args) {
System.out.println(">>> DemoJspApplication start");
SpringApplication.run(DemoJspApplication.class, args);
System.out.println(">>> DemoJspApplication after run");
}
}
package com.example;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
public class ServletInitializer extends SpringBootServletInitializer {
public ServletInitializer() {
System.out.println(">>> ServletInitializer() init");
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
System.out.println(">>> SpringApplicationBuilder configure()");
return application.sources(DemoJspApplication.class);
}
}
Just use @Autowired private DataSource dataSource;
package com.example;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.sql.DataSource;
import java.util.Map;
@Controller
public class HelloJDBCJSPController {
@Autowired
private DataSource dataSource;
@RequestMapping(value = "/helloDS", method = RequestMethod.GET)
public ModelAndView getDataSource(Map<String, Object> model) {
System.out.println("HelloJDBCJSPController >>>> dataSource = " + dataSource);
//Put the DataSource in the model
model.put("dataSource", dataSource);
// /WEB-INF/jsp/show-hello-jdbc.jsp
return new ModelAndView("show-hello-jdbc");
}
@GetMapping("/loadDataSource")
public String loadDataSource(HttpServletRequest request) {
// Put the DataSource in the request
request.setAttribute("dataSource", dataSource);
// Forward to index-tag-003.jsp in the root directory
return "forward:/index-tag-003.jsp";
}
}
spring.application.name=hello-jsp
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
# mysql
spring.datasource.url=jdbc:mysql://localhost:3306/demodb
spring.datasource.username=demouser
spring.datasource.password=Passw0rd!
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# postgres
# spring.datasource.url=jdbc:postgresql://localhost:5432/demodb
# spring.datasource.username=demouser
# spring.datasource.password=Passw0rd!
# spring.datasource.driver-class-name=org.postgresql.Driver
# jndi datasource
spring.datasource.jndi-name=java:comp/env/jdbc/demoPostgresDataSource
# spring.datasource.jndi-name=java:comp/env/jdbc/demoMysqlDataSource
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import="javax.sql.DataSource" %>
<%@ taglib uri="jakarta.tags.sql" prefix="sql" %>
<%@ taglib uri="jakarta.tags.core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Taglib 003 Example of sql tag</title>
</head>
<body>
<sql:query dataSource="${requestScope.dataSource}" var="result">
SELECT * FROM STUDENTS;
</sql:query>
<table border="1">
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
</tr>
<c:forEach var="row" items="${result.rows}">
<tr>
<td><c:out value="${row.id}"/></td>
<td><c:out value="${row.name}"/></td>
<td><c:out value="${row.email}"/></td>
</tr>
</c:forEach>
</table>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import="javax.sql.DataSource" %>
<%@ taglib uri="jakarta.tags.sql" prefix="sql" %>
<%@ taglib uri="jakarta.tags.core" prefix="c" %>
<%-- Check if requestScope.dataSource is null --%>
<c:if test="${requestScope.dataSource == null}">
<jsp:forward page="/loadDataSource" />
</c:if>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Taglib 003 Example of sql tag</title>
</head>
<body>
<sql:query dataSource="${requestScope.dataSource}" var="result">
SELECT * FROM STUDENTS;
</sql:query>
<table border="1">
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
</tr>
<c:forEach var="row" items="${result.rows}">
<tr>
<td><c:out value="${row.id}"/></td>
<td><c:out value="${row.name}"/></td>
<td><c:out value="${row.email}"/></td>
</tr>
</c:forEach>
</table>
</body>
</html>
Please note that the content of this index-tag-003.jsp
is very similar to show-hello-jdbc.jsp
, except for a small section of content.
It contains the following paragraph:
<c:if test="${requestScope.dataSource == null}">
<jsp:forward page="/loadDataSource" />
</c:if>
/loadDataSource
=> HelloJDBCJSPController.java
@Autowired
private DataSource dataSource; // read it from application.properties
public String loadDataSource(HttpServletRequest request) {
// Put the DataSource in the request
request.setAttribute("dataSource", dataSource);
....
mvn clean package
copy target/hello.war
to Tomcat/webapps/
cd Tomcat/conf/
mkdir -p Catalina/localhost
create hello.xml
<!-- hello Context -->
<Context docBase="${catalina.base}/webapps/examples" reloadable="true">
<!-- JNDI resource configuration -->
<!-- JNDI DataSource Configuration for PostgreSQL -->
<!-- spring.datasource.jndi-name=java:comp/env/jdbc/demoPostgresDataSource -->
<Resource
name="jdbc/demoPostgresDataSource"
auth="Container"
type="javax.sql.DataSource"
maxTotal="100"
maxIdle="30"
maxWaitMillis="10000"
username="demouser"
password="Passw0rd!"
driverClassName="org.postgresql.Driver"
url="jdbc:postgresql://localhost:5432/demodb"
validationQuery="SELECT 1"/>
<!-- JNDI DataSource Configuration for MySQL -->
<!-- spring.datasource.jndi-name=java:comp/env/jdbc/demoMysqlDataSource -->
<Resource
name="jdbc/demoMysqlDataSource"
auth="Container"
type="javax.sql.DataSource"
maxTotal="100"
maxIdle="30"
maxWaitMillis="10000"
username="demouser"
password="Passw0rd!"
driverClassName="com.mysql.cj.jdbc.Driver"
url="jdbc:mysql://localhost:3306/demodb"
validationQuery="SELECT 1"/>
</Context>
org.postgresql:postgresql:jar:42.7.4
cd Docker_PostgreSQL
docker run \
--name my-postgres \
-e POSTGRES_USER=demouser \
-e POSTGRES_PASSWORD=Passw0rd! \
-e POSTGRES_DB=demodb \
-v $(pwd)/init.sql:/docker-entrypoint-initdb.d/init.sql \
-p 5432:5432 \
-d \
postgres
cd Docker_MySQL
docker run \
--name my-mysql \
-e MYSQL_ROOT_PASSWORD=Passw0rd! \
-e MYSQL_DATABASE=demodb \
-e MYSQL_USER=demouser \
-e MYSQL_PASSWORD=Passw0rd! \
-v `pwd`/init.sql:/docker-entrypoint-initdb.d/init.sql \
-p 3306:3306 \
-d \
mysql
cd Tomcat/bin
./startup.sh
Note that it now displays the data content of PostgreSQL Database data.
check Tomcat/webapps/hello/WEB-INF/classes/application.properties
It use spring.datasource.jndi-name=java:comp/env/jdbc/demoPostgresDataSource
# mysql
spring.datasource.url=jdbc:mysql://localhost:3306/demodb
spring.datasource.username=demouser
spring.datasource.password=Passw0rd!
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# jndi datasource
spring.datasource.jndi-name=java:comp/env/jdbc/demoPostgresDataSource
It should be noted that when spring.datasource.jndi-name is set, Spring Boot will use the jndi setting first, and then ignore spring.datasource.url, spring.datasource.username, spring.datasource.password, spring.datasource.driver-class-name settings.
cd Tomcat/bin
./shutdown.sh
cd Tomcat/webapps
rm hello.war
Keep the Tomcat/webapps/hello directory.
Modify the contents of the Tomcat/webapps/hello/WEB-INF/classes/application.properties file:
Set
spring.datasource.jndi-name=java:comp/env/jdbc/demoPostgresDataSource
to comment and change it to
# spring.datasource.jndi-name=java:comp/env/jdbc/demoPostgresDataSource
spring.application.name=hello-jsp
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
# mysql
spring.datasource.url=jdbc:mysql://localhost:3306/demodb
spring.datasource.username=demouser
spring.datasource.password=Passw0rd!
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# postgres
# spring.datasource.url=jdbc:postgresql://localhost:5432/demodb
# spring.datasource.username=demouser
# spring.datasource.password=Passw0rd!
# spring.datasource.driver-class-name=org.postgresql.Driver
# jndi datasource
# spring.datasource.jndi-name=java:comp/env/jdbc/demoPostgresDataSource
# spring.datasource.jndi-name=java:comp/env/jdbc/demoMysqlDataSource
cd Tomcat/bin
./startup.sh
Note that it now displays the data content of MySQL Database data.
Note that spring.datasource.jndi-name
(Postgres) is now used instead of spring.datasource.url
(MySQL).
Tomcat/webapps/hello/WEB-INF/classes/application.properties
# mysql
spring.datasource.url=jdbc:mysql://localhost:3306/demodb
spring.datasource.username=demouser
spring.datasource.password=Passw0rd!
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# spring.datasource.jndi-name=java:comp/env/jdbc/demoPostgresDataSource
Upvotes: 1