Spring MVC and MongoDB: a match made in Platform.sh heaven

Otavio Santana
Developer Relations
19 Jun 2019

How difficult is it for a Java developer to create an application that needs to handle front-end technology? It’s a difficult question—and one that I hear every single day from back-end developers. So, how can we solve it in Spring world? With Spring MVC.

Spring MVC makes it easy to create stand-alone, production-grade, Spring-based applications that you can just run. In this post, we’ll show the perfect union between Spring MVC and MongoDB database, the NoSQL database most popular around the globe, running in the cloud on Platform.sh.

The initial web framework built on the Servlet API, Spring MVC has been included in the Spring Framework from day one. Spring Web MVC follows the Model-View-Controller design pattern and implements all the basic features of a core Spring framework, including Inversion of Control and Dependency Injection.

Show me the code

Platform.sh supports common Java tools, and so we’ll use the Spring Initializr to create a Spring Maven Project with Java 8, and append the Spring Data MongoDB. There’s also a Platform.sh configuration library that reads Platform.sh-specific details, like routes and environmental variables, and exposes them to the application. Here’s the pom.xml file:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>sh.platform.template</groupId>
	<artifactId>template-spring-mvc-maven-mongodb</artifactId>
	<version>0.0.1</version>

	<parent>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-parent</artifactId>
    	<version>2.1.5.RELEASE</version>
	</parent>

	<properties>
    	<java.version>1.8</java.version>
	</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-thymeleaf</artifactId>
    	</dependency>
    	<dependency>
        	<groupId>org.springframework.boot</groupId>
        	<artifactId>spring-boot-devtools</artifactId>
        	<optional>true</optional>
    	</dependency>
    	<dependency>
        	<groupId>org.springframework.boot</groupId>
        	<artifactId>spring-boot-starter-test</artifactId>
        	<scope>test</scope>
    	</dependency>
    	<dependency>
        	<groupId>org.springframework.data</groupId>
        	<artifactId>spring-data-mongodb</artifactId>
    	</dependency>
    	<dependency>
        	<groupId>sh.platform</groupId>
        	<artifactId>config</artifactId>
        	<version>2.2.0</version>
    	</dependency>
	</dependencies>

	<build>
    	<finalName>spring-mvc-maven-mongodb</finalName>
    	<plugins>
        	<plugin>
            	<groupId>org.springframework.boot</groupId>
            	<artifactId>spring-boot-maven-plugin</artifactId>
        	</plugin>
    	</plugins>
	</build>
</project>
</code:xml>

Spring Data MongoDB is part of the umbrella Spring Data project, which provides a familiar, consistent, Spring-based programming model for new datastores while retaining store-specific features and capabilities.

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;

@Document
public class User {

	@Id
	private String id;

	@NotBlank(message = "Name is mandatory")
	private String name;

	@NotBlank(message = "Email is mandatory")
	@Email(message = "Email is not valid")
	private String email;

            //getter and setter
}

We have a User class with three attributes: ID, name, and email. The identifier is primarily designated for MongoDB internal use. The validate fields of the User class in this post will use Bean Validation. Bean Validation—part of JavaEE and JavaSE —ensures that the properties of a Bean meet specific criteria by using annotations.

Spring Data MongoDB focuses on storing data in MongoDB. It also inherits functionality from the Spring Data Commons project, like the ability to derive queries. For the most part, you don’t have to learn the query language of MongoDB; you can write a handful of methods, and the Spring Boot MongoDB handles it for you.

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends CrudRepository<User, String> {

}

Once Spring uses the MVC approach to building a website, the HTTP requests are handled by a controller. To control these requests, we’ll create UserController that will have a @Controller annotation. Each HTTP verb has an annotation, such as GetMapping, thathandles GET HTTP requests, PostMapping that handles POST HTTP requests, and so on.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;

import javax.validation.Valid;


@Controller
public class UserController {

	@Autowired
	private UserRepository userRepository;

	@GetMapping("/")
	public String start(Model model) {
    	model.addAttribute("users", userRepository.findAll());
    	return "index";
	}

	@GetMapping("/signup")
	public String showSignUpForm(User user) {
    	return "add-user";
	}

	@PostMapping("/adduser")
	public String addUser(@Valid User user, BindingResult result, Model model) {
    	if (result.hasErrors()) {
        	return "add-user";
    	}

    	userRepository.save(user);
    	model.addAttribute("users", userRepository.findAll());
    	return "index";
	}

	@GetMapping("/edit/{id}")
	public String showUpdateForm(@PathVariable("id") String id, Model model) {
    	User user = userRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("Invalid user Id:" + id));
    	model.addAttribute("user", user);
    	return "update-user";
	}

	@PostMapping("/update/{id}")
	public String updateUser(@PathVariable("id") String id, @Valid User user, BindingResult result, Model model) {
    	if (result.hasErrors()) {
        	user.setId(id);
        	return "update-user";
    	}

    	userRepository.save(user);
    	model.addAttribute("users", userRepository.findAll());
    	return "index";
	}

	@GetMapping("/delete/{id}")
	public String deleteUser(@PathVariable("id") String id, Model model) {
    	User user = userRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("Invalid user Id:" + id));
    	userRepository.delete(user);
    	model.addAttribute("users", userRepository.findAll());
    	return "index";
	}
}

Thymeleaf parses the HTML templates and evaluates all expressions to render the value that was set in the controller.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
	<meta charset="utf-8">
	<meta http-equiv="x-ua-compatible" content="ie=edge">
	<title>Users</title>
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
	<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.4.1/css/all.css" integrity="sha384-5sAR7xN1Nv6T6+dT2mhtzEpVJvfS3NScPQTrOxhwjIuvcA67KV2R5Jz6kr4abQsz" crossorigin="anonymous">
	<link rel="stylesheet" href="../css/shards.min.css">
</head>
<body>
<div th:switch="${users}" class="container my-5">
	<div class="row">
    	<div class="col-md-6">
        	<h2 th:case="null">No users yet!</h2>
        	<div th:case="*">
            	<h2 class="my-5">Users</h2>
            	<table class="table table-striped table-responsive-md">
                	<thead>
                	<tr>
                    	<th>Name</th>
                    	<th>Email</th>
                    	<th>Edit</th>
                    	<th>Delete</th>
                	</tr>
                	</thead>
                	<tbody>
                	<tr th:each="user : ${users}">
                    	<td th:text="${user.name}"></td>
                    	<td th:text="${user.email}"></td>
                    	<td><a th:href="@{/edit/{id}(id=${user.id})}" class="btn btn-primary"><i class="fas fa-user-edit ml-2"></i></a></td>
                    	<td><a th:href="@{/delete/{id}(id=${user.id})}" class="btn btn-primary"><i class="fas fa-user-times ml-2"></i></a></td>
                	</tr>
                	</tbody>
            	</table>
        	</div>
        	<p class="my-5"><a href="/signup" class="btn btn-primary"><i class="fas fa-user-plus ml-2"></i></a></p>
    	</div>
	</div>
</div>
</body>
</html>

The final part of our application is a startup. It’s a standard method that follows the Java convention for an application entry point. SpringApplication bootstraps the app, starting Spring on a Tomcat web server.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication
@EnableTransactionManagement
public class Application {

	public static void main(String[] args) {
    	SpringApplication.run(Application.class, args);
	}

}

Platform.sh structure

The Java application is ready to go! The next step is to set the Platform.sh files required to manage and deploy the application. In our first Java post, we took a deep dive into each detail of these three files:

  • One Router (.platform/routes.yaml). Platform.sh allows you to define the routes.
  • Zero or more service containers (.platform/services.yaml). Platform.sh allows you to completely define and configure the topology and services you want to use on your project.
  • One or more application containers (.platform.app.yaml). You control your application and the way it will be built and deployed on Platform.sh via a single configuration file.

The file that will change on this post is the service file, t allowing you to define a database, search engine, cache, and so on. In this project, we’ll set MongoDB instead of MySQL.

database:
  type: mongodb:3.6
  disk: 1024

Platform.sh and Java

For the database, we need to define where the connection comes from. Platform.sh provides connection credentials to services using the PLATFORM_RELATIONSHIPS environmental variable.

import com.mongodb.MongoClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
import sh.platform.config.Config;
import sh.platform.config.MongoDB;

@Configuration
public class MongoConfig extends AbstractMongoConfiguration {

	private Config config = new Config();

	@Override
	@Bean
	public MongoClient mongoClient() {
    	MongoDB mongoDB = config.getCredential("database", MongoDB::new);
    	return mongoDB.get();
	}

	@Override
	protected String getDatabaseName() {
    	return config.getCredential("database", MongoDB::new).getDatabase();
	}
}

The Java Config library offers classes that will read the information from the variable and return an object that represents the services, like a DataSource for MySQL, PostgreSQL, MongoClient, Redis, or any other services you’ve defined in .platform/services.yaml!

The application is now ready, so it’s time to move it to the cloud with Platform.sh using the following steps:

  • Create a new free trial account.
  • Sign up with a new user and password, or login using a current GitHub, Bitbucket, or Google account. If you use a third-party login, you’ll be able to set a password for your Platform.sh account later.
  • Select the region of the world where your site should live.
  • Select the blank template.

After this wizard, Platform.sh will provision the whole infrastructure to you, and Platform.sh will offer your project a remote Git repository. The Platform.sh Git-driven infrastructure means it will automatically manage everything your application needs to push it to the master remote repository. After you set up your SSH keys, you only need to write your code—including a few YAML files that specify your desired infrastructure—then commit it to Git and push.

git remote add platform <platform.sh@gitrepository>
git commit -m "Initial project"
git push -u platform master

Code pushed will create both the Java application, a MongoDB instance, and, when it’s done, will return an IP address to the service. Let’s test the Spring MVC application.

You’ve now seen how a native integration with Spring MVC works, and how only a small change in configuration can easily bring your Maven, Gradle, and Ant managed Java applications to Platform.sh.

In my flagship Java post, you can read more about how you can quickly take your Java apps to the cloud on Platform. sh. And stay tuned for my next post about Elasticsearch versus Solr!