First Spring Boot Project
--
I found many great guides for my first web app with Django and was surprised to find so few for Spring Boot. So here we go:
Setup
- we will be working with Maven (so it should be installed). Maven is a build automation tool, which will allow us to add any dependency, i.e. functionality we want to our Spring app. We will be working with the POM.xml file for that (Project Object Model). Note that there are alternatives to Maven, e.g. Gradle.
- using the Spring Initializer generate your project ‘starter set’ which mainly gives you the proper file structure for Spring to work, as well as the POM.xml file. This is not a necessary step, but makes it easier. Note that you should choose Maven and add the Spring Boot Web dependency.
- create a new project from the downloaded files in your IDE. I’ll be using IntellJ.
- run “mvn spring-boot:run” from the command line to test if your setup is working properly, if successful you should see this.
- and our file structure looks like so. The src.main.java structure matters, so does the resource folder within which we will create our templates. Also note the root location of the POM file.
Create the first view (“Hello world”)
- Dependencies
- we will be using Thymeleaf, a template engine which will allow us to e.g. display values in our templates that were passed to it by the controller. As with Maven there are alternatives to Thymeleaf of course. In order to use it, we require the respective dependency in our POM file:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
- note that every time we add a new dependency to POM, we have to reload the maven project. Also note that errors might occur here, e.g. I had to roll back to Spring Boot version 2.4.0 and Java 8 (“1.8”) after adding the Thymeleaf dependency.
2. HTML
- create a plain HTML file inside the Templates folder and let’s call it “home”. The “th” flag is for Thymeleaf and inserts the passed values from the controller, which is shown just a tad below.
<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head><title>Home Page</title></head>
<body>
<h1>Hello !</h1>
<p>Welcome to <span th:text="${appName}">Our App</span></p>
</body>
</html>
3. Controller
- now we need to add the said controller, which will connect us to the right view, i.e. template which is “home.html”. Note the necessary annotations “Controller” and “GetMapping(‘someUrl’)”.
package com.shcherbakova.app.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class controllerService {
@GetMapping("/")
public String homePage(Model model) {
model.addAttribute("appName", "SomeCoolName");
return "home";
}}
- after adding the controller, my project structure looks like so:
4. Run
- running “mvn spring-boot:run” once again, you should now see the following if you visit your localhost. Also note the inserted attribute “appName” with the value “SomeCoolName”.
Database I (In-Memory)
We will start with H2, an in-memory database and later on move to MySQL.
- Dependencies
- we’ll have to add H2 and JPA (Java Persistance API) dependencies to our POM:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
2. Model
- our model is a Book. A model in Spring requires the “@Entity” annotation. Book has an Id which is generated automatically and it has two columns for a Title and an Author. The usual getters and setters, constructors and overridden toString method are included. Spring will now build the model for you.
package com.shcherbakova.app.persistance.model;
import javax.persistence.*;
@Entity
public class Book{
@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private long id;
@Column(nullable = false, unique = true)
private String title;
@Column(nullable = false)
private String author;
public Book() {}
public Book(String title, String author) {
this.title = title;
this.author = author;
}
@Override
public String toString() {
return String.format(
"ID[id=%d, title='%s', author='%s']",
id, title, author);
}
public Long getId() {return id;}
public String getTitle(){return title;}
public String getAuthor() {return author;}
public void setTitle(String title){this.title = title;};
public void setAuthor(String author){this.author = author;}}
3. CRUD Interface
- you now need to extend the CRUD interface, so that your BookController (which we will create in a second) is able to work with it. CrudRepository requires the type of the model (Book) and the id (Long).
package com.shcherbakova.app.persistance.repository;
import com.shcherbakova.app.persistance.model.Book;
import org.springframework.data.repository.CrudRepository;
import java.util.List;
public interface BookRepository extends CrudRepository<Book,Long> {}
4. BookController
- create a new controller for the Book model (single-responsibility principle). The “@Autowired” annotation injects the bookRepository instance (with dependency injection provided by Spring Boot only one instance of a dependency is created and injected wherever we use the autowired annotation). Also note that we use a different class to create our view here: ModelAndView(template), but you could just as well use Model like in ControllerService. We pass our for now not yet existing book collection to the also not yet existing “books” view using the findAll method which our bookRepository gets from the CrudInterface it extends.
package com.shcherbakova.app.controller;
import com.shcherbakova.app.persistance.repository.BookRepository;
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.servlet.ModelAndView;
@Controller
@RequestMapping("/books")
public class BookController {
@Autowired
private BookRepository bookRepository;
@GetMapping
public ModelAndView books() {
ModelAndView mv = new ModelAndView("books");
mv.addObject("allBooks", bookRepository.findAll());
return mv;
}}
5. HTML
<table class="table table-striped">
<tr th:each="book : ${allBooks}">
<td th:text="${book.title}"></td>
<td th:text="${book.author}"></td>
</tr>
</table>
6. Run
- lets create some books and re-run the app. First modify your main Application class by adding “EntityScan” to load our created Book model and by adding “EnableJpaRepositories” to load our CRUD operations bookRepository. Add a CommandLineRunner which will create some data for our model.
@EnableJpaRepositories("com.shcherbakova.app.persistance.repository")
@EntityScan("com.shcherbakova.app.persistance.model")
@SpringBootApplication
public class AppApplication {
@RequestMapping("/")
String home() {
return "Hello World!";
}
public static void main(String[] args) {
SpringApplication.run(AppApplication.class, args);
}
@Bean
public CommandLineRunner demo(BookRepository bookRepository) {
return (args) -> {
bookRepository.save(new Book("Pam", "B"));
bookRepository.save(new Book("Jim", "H"));
bookRepository.save(new Book("Dwight", "S"));
};
}}
- Re-running the command and visiting /books on your localhost gives us:
Forms
let’s now add some user generated data to the database through a form.
- add a new path to BookController where users can create new books:
@GetMapping("/new")
public String newBook(Model model){
model.addAttribute("book", new Book());
return "newBook";}
- create the referenced template “newBook”, including:
<form action="#" th:action="@{submitted}" th:object="${book}" method="post">
<table>
<tr>
<td>Title</td>
<td><input type="text" th:field="*{title}" /></td>
</tr>
<tr>
<td>Author</td>
<td><input type="text" th:field="*{author}" /></td>
</tr>
<tr>
<td><input type="submit" value="Submit" /></td>
</tr>
</table>
</form>
- our action in the template references the url “submitted” which we have to create inside the BookController:
@PostMapping("/submitted")
public String submit(@ModelAttribute Book book) {
bookRepository.save(book);
return "redirect:/books";}
- running our app now gives us the following at books/new and books after form submission respectively:
Database II (MySQL)
- Dependencies
- at first we need to add a new dependency to our POM file (you also don’t need the H2 dependency anymore and can remove it):
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
2. New Database
- create a new database either through the MySQL Workbench or running the following commands:
mysql --user=root --password=yourSuperSecretPassword
CREATE DATABASE spring;
\q
3. Application configuration
- modify the application.properties file inside the resources folder:
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/spring?useLegacyDatetimeCode=false&serverTimezone=Europe/Berlin&useSSL=false
spring.datasource.username=root
spring.datasource.password=yourSuperSecretPassword
spring.datasource.driver-class-name =com.mysql.jdbc.Driver
- and now you’re all set to use your database. Try it by removing the demo function inside the Main class (AppApplication here) and adding the data through form input.
User Authentication
- Dependencies
- you know the drill by now, add new dependencies which give us access to Spring user authentication services
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
2. Configuration
- create a new config directory inside your app with a new securityConfig file, which allows us to configure urls that are not restricted (“/” here) and link to a login view, which we have to create.
@Configuration
@EnableWebSecurity
public class securityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
@Bean
@Override
public UserDetailsService userDetailsService() {
UserDetails user =
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}}
3. HTML
<body>
<div th:if="${param.error}">
Invalid username and password.
</div>
<div th:if="${param.logout}">
You have been logged out.
</div>
<form th:action="@{/login}" method="post">
<div><label> User Name : <input type="text" name="username"/> </label></div>
<div><label> Password: <input type="password" name="password"/> </label></div>
<div><input type="submit" value="Sign In"/></div>
</form>
</body>
4. Controller
- note that we haven’t created a controller anywhere above on how to reach the login page which we must pass through to access /books now.
- hence, add the view inside your controllerService:
@GetMapping("/login")
public String login() {
return "login"; }
- for logout simply add :
@GetMapping("/logout")
public String logout(HttpServletRequest request){
request.getSession(true).invalidate();
return "redirect:/";}
Et voilà. You can also find the code here: https://github.com/elijahveto/first-spring-app
Other helpful ressources: