Tutorials Spring Boot

Elena Vance
-
tutorials spring boot

Spring Boot has a lot of magic going for it. Developing applications with Spring Boot is both cool and fun. It makes it easy to create stand-alone, production-grade Spring applications that you can just run. Most Spring Boot applications require minimal configuration, letting you focus on business logic rather than boilerplate setup. You will explore the key concepts of Spring Boot and discover how Starter projects simplify application development.

What you will learn You will build: - A web application to manage your to-dos - A basic REST service to manage survey questions This course is a perfect first step into Spring Boot. You will learn Spring Boot step by step — in more than 90 hands-on steps.

You will work with: - REST services - Spring (dependency management) - Spring MVC - Spring Boot and Spring Boot Starter projects - Spring Security (authentication & authorization) - Bootstrap (UI styling) - Maven (dependency management) - Eclipse (IDE) and embedded Tomcat (web server) We’ll help you set up each of these.

Topics covered - Basics of Spring Boot - Auto-configuration and Spring Boot “magic” - Spring Boot Starter projects and Spring Initializr - DispatcherServlet and request flow - Todo management application with login/logout - Models, Controllers, ViewResolver, and Filters - Forms: data binding and validation - Annotation-based handling: @RequestParam ,@ModelAttribute ,@SessionAttributes , etc.

Using Bootstrap to style pages - Building REST APIs with Spring Boot Starter Web - Content negotiation (JSON and XML) - Embedded servlet containers: Tomcat, Jetty, Undertow - Unit and integration testing with Spring Boot Starter Test - Profiles and dynamic configuration - Spring Data JPA - Spring Boot Actuator - Spring Security essentials and configuration - Developer tools and LiveReload Getting Started Installing Tools - Installation Resources - 🎥 Video Playlist - 📄 PDF Guide - 📚 More Details - Troubleshooting 🛠️ Troubleshooting Guide (50 pages, 200+ errors and FAQs answered) Running Examples - Download the ZIP file or clone the Git repository.

Unzip the ZIP file (if you downloaded one). - Open Command Prompt/Terminal and navigate ( cd ) to the folder containingpom.xml . - Open Eclipse: - Go to File → Import → Existing Maven Project - Navigate to the folder where you unzipped the project - Select the correct project - Locate the Spring Boot Application file (look for the class annotated with @SpringBootApplication ). - Right-click the file → Run As → Java Application. - ✅ You’re all set!

For help: Installation Guide (Video Playlist) Spring Level 1 to Level 6 — Section Overview Here’s a quick overview of the different sections of the course: - Level 1: Introduction to Spring in 10 Steps - Level 2: Develop a Todo Management Web Application in 30 Steps - Level 3: Introduction to Unit Testing with JUnit in 5 Steps - Level 4: Introduction to Mocking with Mockito in 5 Steps - Level 5: Advanced Features of Spring Boot in 25 Steps - Build a simple API for managing a survey questionnaire - Level 6: Introduction to JPA in 10 Steps - Level 7: Connect the Todo Management Web Application to JPA 5 Bonus Sections - Introduction to Tools and Frameworks Step-by-Step Details Introductory Modules Note: These introductory modules are organized across the course to enable JIT (Just-In-Time) learning.

If you are new to Spring, don’t forget to check the Introduction to Spring module in the appendix.

Introduction to JUnit — 5 steps - Introduction to Mockito — 5 steps - Introduction to JPA — 10 steps - Introduction to Spring — 10 steps Web Application with Spring Boot - Step 01 — Part 1: Basic Spring Boot web application setup - Step 01 — Part 2: pom.xml , Spring Boot application, andapplication.properties - Step 02 — Part 1: First Spring MVC controller — @ResponseBody vs@Controller - Step 02 — Part 2: Understanding HTTP request flow - Step 03: Demystifying some of the Spring Boot “magic” - Step 04: Redirect to Login JSP — @ResponseBody and view resolver - Step 05: Show user id and password on welcome page — ModelMap and@RequestParam - Step 06: DispatcherServlet and Spring MVC flow - Step 07: Your first HTML form - Step 08: Add hard-coded validation of user id and password - Step 09: More Spring “magic” - Step 10: Create TodoController andlist-todos view.

MakeTodoService a@Service - Step 11: Architecture of web applications - Step 12: Session vs Model vs Request — @SessionAttributes - Step 13: Add new to-do - Step 14: Display to-dos in a table using JSTL tags - Step 15: Bootstrap for page formatting using webjars - Step 16: Delete a to-do - Step 17: Format “Add Todo” page and add basic HTML5 form validation - Step 18 — Part 1: Validations with Hibernate Validator — using command bean - Step 18 — Part 2: Using JSR 349 (Bean Validation) - Step 19: Updating a to-do - Step 20: Add a target date for to-do — use initBinder to handle date fields - Step 21: JSP fragments and navigation bar - Step 22: Preparing for Spring Security - Step 23: Initial Spring Security setup - Step 24: Refactor and add logout functionality using Spring Security - Step 25: Exception handling Connecting Web Application with JPA Note: Ensure you complete the Introduction to JPA module before starting this section.

Step 26: Add dependencies for JPA and H2 - Step 27: Configure H2 console - Step 28: Create Todo entity and JPA repository - Step 29: Insert Todo using JPA repository - Step 30: Update, delete, and retrieve to-dos using JPA repository - Step 31: Data initialization with data.sql - Step 32: Connecting JPA to other databases - Step 33: Upgrading to Spring Boot 2 and Spring 5 Spring Boot Deep Dive — Simple API - Step 01: Setup and launch Spring Boot application with Maven and Eclipse - Step 02: Create your first @RestController - Step 03: Understanding Spring Boot magic: spring-boot-starter-web - Step 04: Understanding Spring Boot magic: spring-boot-starter-parent - Step 05: Spring Boot vs Spring framework - Step 06: Create services for survey and questions - Step 07: What is REST?

Create REST service with @GetMapping and@PathVariable - Step 08: Second REST service to retrieve a specific question - Step 09: Spring Boot Developer Tools and LiveReload — develop faster!

Step 10: Create a REST service to add a new question: @PostMapping , Postman - Step 11: Content negotiation — deliver XML responses from REST services - Step 12: Spring Initializr — create Spring Boot projects on the fly - Step 13: Spring Boot Actuator — monitor your application - Step 14: Embedded servlet containers — switch to Jetty or Undertow - Step 15: Add dynamic configuration: YAML & more - Step 16: Basics of profiles - Step 17: Advanced configuration with type-safe @ConfigurationProperties - Step 18: Spring Data JPA with CommandLineRunner - Step 19: H2 console and add a custom JPA repository method - Step 20: Introduction to Spring Data REST - Step 21: Spring Boot integration tests - Step 22: Add integration test for POST requests - Step 23: Small refactor to organize code - Step 24: Unit tests with Spring Boot and Mockito - Step 25: Unit test for createTodo - Step 26: Secure services with Basic Authentication using Spring Security - Step 27: Configure Spring Security roles for survey and other services - Step 28: Deep dive into Spring Boot auto-configuration Step-by-Step Notes and Code Examples 02.

Spring-Boot-Web-Application Step01.md What you will learn in this step - Let’s create a simple web application using Spring Boot. - How to run the Spring Boot application. - An overview of the “magic” that Spring Boot provides (deep dive in Step 03). Files list - (Add list of files for this step — e.g., pom.xml ,src/main/java/...

,src/main/resources/application.properties ,src/main/resources/templates/* ) pom.xml <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.in28minutes.springboot.web</groupId> <artifactId>spring-boot-first-web-application</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>spring-boot-first-web-application</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>4.0.0-M2</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>21</java.version> <maven-jar-plugin.version>3.1.1</maven-jar-plugin.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-devtools</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java package com.in28minutes.springboot.web; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringBootFirstWebApplication { public static void main(String[] args) { SpringApplication.run(SpringBootFirstWebApplication.class, args); } } src/main/resources/application.properties src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java package com.in28minutes.springboot.web; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest public class SpringBootFirstWebApplicationTests { @Test public void contextLoads() { } } Step02.md What You Will Learn during this Step: - Using @RequestMapping(value = "/login", method = RequestMethod.GET) - Accessing endpoint: http://localhost:8080/login - Why use @ResponseBody ?

Importance of the @RequestMapping method - How web applications work — Request and Response cycle: - Browser sends HTTP Request to the web server - Code in the web server executes - Input: HttpRequest - Output: HttpResponse - Input: - Web server responds with an HTTP Response src/main/java/com/in28minutes/springboot/web/controller/LoginController.java New package com.in28minutes.springboot.web.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class LoginController { @RequestMapping("/login") public String loginMessage() { return "Hello World"; } } src/main/resources/application.properties Modified New Lines logging.level.org.springframework.web: DEBUG Step03.md What You Will Learn during this Step: - Demystifying some of the Spring Boot magic: - Spring Boot Starter Parent - Spring Boot Starter Web - Embedded Tomcat - Spring Boot DevTools Step04.md What You Will Learn during this Step: - Creating your first JSP - (Note: A bit of setup is required before this step!) - Introduction to ViewResolver Useful Snippets and References - First Snippet: /src/main/webapp/WEB-INF/jsp/login.jsp <html> <head> <title>Yahoo!!</title> </head> <body> My First JSP!!!

</body> </html> Second Snippet - /src/main/resources/application.properties spring.mvc.view.prefix: /WEB-INF/jsp/ spring.mvc.view.suffix: .jsp logging.level.: DEBUG Third Snippet : To enable jsp support in embedded tomcat server! <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope>//default for IntelliJ IDEA </dependency> Exercises - Create a new jsp and a new controller method to redirect to it! - Play around!

Files List pom.xml Modified New Lines <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope> //default for IntelliJ IDEA </dependency> src/main/java/com/in28minutes/springboot/web/controller/LoginController.java Modified package com.in28minutes.springboot.web.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class LoginController { @RequestMapping("/login") public String loginMessage() { return "login"; } } src/main/resources/application.properties Modified New Lines spring.mvc.view.prefix=/WEB-INF/jsp/ spring.mvc.view.suffix=.jsp logging.level.org.springframework.web=DEBUG src/main/webapp/WEB-INF/jsp/login.jsp New <html> <head> <title>First Web Application</title> </head> <body> My First JSP!!

</body> </html> Step05.md What You Will Learn during this Step: - You first GET Parameter - Problem with using GET src/main/java/com/in28minutes/springboot/web/controller/LoginController.java Modified @Controller public class LoginController { @RequestMapping("/login") public String loginMessage(@RequestParam String name, ModelMap model) { model.put("name", name); return "login"; } } src/main/webapp/WEB-INF/jsp/login.jsp Modified New Lines My First JSP!! Welcome ${name}! Step06.md What You Will Learn during this Step: - Importance of the DispatcherServlet in Spring MVC. Spring MVC Request Flow - DispatcherServlet receives the HTTP Request. - DispatcherServlet identifies the correct Controller based on the URL.

The Controller executes business logic. - The Controller returns: a) Model b) View Name back to DispatcherServlet. - DispatcherServlet uses a ViewResolver to identify the correct view. - DispatcherServlet makes the Model available to the View and executes it. - DispatcherServlet sends the final HTTP Response back to the client.

📌 Reference Flow Diagram: Spring MVC Request Flow Step07.md What You Will Learn during this Step: - Lets get the name from the user in a form Useful Snippets and References First Snippet @RequestMapping(value = "/login", method = RequestMethod.GET) public String showLoginPage(ModelMap model, @RequestParam String name) { return "login"; } @RequestMapping(value = "/login", method = RequestMethod.POST) public String handleLogin(ModelMap model, @RequestParam String name) { model.put("name", name); return "welcome"; } Second Snippet <form method="POST"> Name : <input name="name" type="text"/> <input type="submit"/> </form> Files List src/main/java/com/in28minutes/springboot/web/controller/LoginController.java Modified package com.in28minutes.springboot.web.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; @Controller public class LoginController { @RequestMapping(value = "/login", method = RequestMethod.GET) public String showLoginPage(ModelMap model) { return "login"; } @RequestMapping(value = "/login", method = RequestMethod.POST) public String showWelcomePage(ModelMap model, @RequestParam String name) { model.put("name", name); return "welcome"; } } src/main/webapp/WEB-INF/jsp/login.jsp Modified New Lines <form method="post"> Name : <input type="text" name="name" /> Password : <input type="password" name="password" /> <input type="submit" /> </form> src/main/webapp/WEB-INF/jsp/welcome.jsp New <html> <head> <title>First Web Application</title> </head> <body> Welcome ${name}!!

</body> </html> Step08.md What You Will Learn during this Step: - Add validation for userid and password. - Understand the concept of hard-coded validation (checking credentials against fixed values). - Show appropriate error messages when invalid credentials are entered. - Learn the flow of form submission: - User submits login form. - Application checks credentials against hard-coded values. - On success → Redirect to welcome page. - On failure → Stay on login page and display error message.

✅ This step introduces basic login validation, preparing the ground for more flexible and secure authentication approaches later in the course.

Files List src/main/java/com/in28minutes/springboot/web/controller/LoginController.java Modified @Controller public class LoginController { @Autowired LoginService service; @RequestMapping(value = "/login", method = RequestMethod.GET) public String showLoginPage(ModelMap model) { return "login"; } @RequestMapping(value = "/login", method = RequestMethod.POST) public String showWelcomePage(ModelMap model, @RequestParam String name, @RequestParam String password) { boolean isValidUser = service.validateUser(name, password); if (!isValidUser) { model.put("errorMessage", "Invalid Credentials"); return "login"; } model.put("name", name); model.put("password", password); return "welcome"; } } src/main/java/com/in28minutes/springboot/web/service/LoginService.java New package com.in28minutes.springboot.web.service; import org.springframework.stereotype.Component; @Component public class LoginService { public boolean validateUser(String userid, String password) { // in28minutes, dummy return userid.equalsIgnoreCase("in28minutes") && password.equalsIgnoreCase("dummy"); } } src/main/webapp/WEB-INF/jsp/login.jsp Modified New Lines <font color="red">${errorMessage}</font> Step09.md What You Will Learn during this Step: - Understand the magic of Spring.

Learn about Spring Auto-wiring and Dependency Injection. - Key concepts: @Autowired ,@Component . - How Spring manages dependencies automatically to reduce boilerplate code. Step10.md What You Will Do: - Create TodoController and thelist-todos.jsp page. - Implement TodoService as a Spring@Service and inject it into the controller. Pending for Next Step: - The variable ${name} is not yet available inlist-todos.jsp . - The username “in28Minutes” is still hardcoded in TodoController . Notes: - In this step, you will see controller-service interaction in action.

package com.in28minutes.springboot.web.model; import java.util.Date; public class Todo { private int id; private String user; private String desc; private Date targetDate; private boolean isDone; public Todo(int id, String user, String desc, Date targetDate, boolean isDone) { super(); this.id = id; this.user = user; this.desc = desc; this.targetDate = targetDate; this.isDone = isDone; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUser() { return user; } public void setUser(String user) { this.user = user; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } public Date getTargetDate() { return targetDate; } public void setTargetDate(Date targetDate) { this.targetDate = targetDate; } public boolean isDone() { return isDone; } public void setDone(boolean isDone) { this.isDone = isDone; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + id; return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Todo other = (Todo) obj; if (id != other.id) { return false; } return true; } @Override public String toString() { return String.format( "Todo [id=%s, user=%s, desc=%s, targetDate=%s, isDone=%s]", id, user, desc, targetDate, isDone); } } Snippet - /src/main/java/com/in28minutes/springboot/web/service/TodoService.java package com.in28minutes.springboot.web.service; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.List; import org.springframework.stereotype.Service; import com.in28minutes.springboot.web.model.Todo; @Service public class TodoService { private static List<Todo> todos = new ArrayList<>(); private static int todoCount = 3; static { todos.add(new Todo(1, "in28Minutes", "Learn Spring MVC", new Date(), false)); todos.add(new Todo(2, "in28Minutes", "Learn Struts", new Date(), false)); todos.add(new Todo(3, "in28Minutes", "Learn Hibernate", new Date(), false)); } public List<Todo> retrieveTodos(String user) { List<Todo> filteredTodos = new ArrayList<>(); for (Todo todo : todos) { if (todo.getUser().equals(user)) { filteredTodos.add(todo); } } return filteredTodos; } public void addTodo(String name, String desc, Date targetDate, boolean isDone) { todos.add(new Todo(++todoCount, name, desc, targetDate, isDone)); } public void deleteTodo(int id) { todos.removeIf(todo -> todo.getId() == id); } } Step11.md What You Will Do: - Discuss the architecture of web applications.

Understand how different layers (Controller, Service, View) interact. - Learn about the responsibilities of each layer and how they contribute to separation of concerns. Step12.md What You Will Learn during this Step: - Differences between Session, Model, and Request scopes. - Best practices for using Session data — avoid storing unnecessary information. - How @SessionAttributes("name") works to store model attributes in the session. - Why Model is preferred over adding elements directly to HttpServletRequest :- Ensures view-agnostic design.

Allows the use of different view technologies not tied to HttpServletRequest . - Understanding Spring documentation on @SessionAttributes :- Lists model attribute names to be transparently stored in session or conversational storage. Notes: - This step is crucial for handling user-specific data across multiple requests. - Helps prepare for form handling, login sessions, and dynamic content rendering.

First Snippet @SessionAttributes("name") Files List src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java Modified New Lines import org.springframework.context.annotation.ComponentScan; @ComponentScan("com.in28minutes.springboot.web") src/main/java/com/in28minutes/springboot/web/controller/LoginController.java Modified @Controller @SessionAttributes("name") public class LoginController { src/main/java/com/in28minutes/springboot/web/controller/TodoController.java New package com.in28minutes.springboot.web.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.SessionAttributes; import com.in28minutes.springboot.web.service.LoginService; import com.in28minutes.springboot.web.service.TodoService; @Controller @SessionAttributes("name") public class TodoController { @Autowired TodoService service; @RequestMapping(value = "/list-todos", method = RequestMethod.GET) public String showTodos(ModelMap model) { String name = (String) model.get("name"); model.put("todos", service.retrieveTodos(name)); return "list-todos"; } } src/main/java/com/in28minutes/springboot/web/model/Todo.java New package com.in28minutes.springboot.web.model; import java.util.Date; public class Todo { private int id; private String user; private String desc; private Date targetDate; private boolean isDone; public Todo(int id, String user, String desc, Date targetDate, boolean isDone) { super(); this.id = id; this.user = user; this.desc = desc; this.targetDate = targetDate; this.isDone = isDone; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUser() { return user; } public void setUser(String user) { this.user = user; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } public Date getTargetDate() { return targetDate; } public void setTargetDate(Date targetDate) { this.targetDate = targetDate; } public boolean isDone() { return isDone; } public void setDone(boolean isDone) { this.isDone = isDone; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + id; return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Todo other = (Todo) obj; return id == other.id; } @Override public String toString() { return String.format( "Todo [id=%s, user=%s, desc=%s, targetDate=%s, isDone=%s]", id, user, desc, targetDate, isDone); } } src/main/java/com/in28minutes/springboot/web/service/LoginService.java Modified @Service public class LoginService { src/main/java/com/in28minutes/springboot/web/service/TodoService.java New package com.in28minutes.springboot.web.service; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.List; import org.springframework.stereotype.Service; import com.in28minutes.springboot.web.model.Todo; @Service public class TodoService { private static List<Todo> todos = new ArrayList<>(); private static int todoCount = 3; static { todos.add(new Todo(1, "in28Minutes", "Learn Spring MVC", new Date(), false)); todos.add(new Todo(2, "in28Minutes", "Learn Struts", new Date(), false)); todos.add(new Todo(3, "in28Minutes", "Learn Hibernate", new Date(), false)); } public List<Todo> retrieveTodos(String user) { List<Todo> filteredTodos = new ArrayList<>(); for (Todo todo : todos) { if (todo.getUser().equals(user)) { filteredTodos.add(todo); } } return filteredTodos; } public void addTodo(String name, String desc, Date targetDate, boolean isDone) { todos.add(new Todo(++todoCount, name, desc, targetDate, isDone)); } public void deleteTodo(int id) { todos.removeIf(todo -> todo.getId() == id); } } src/main/webapp/WEB-INF/jsp/list-todos.jsp New <html> <head> <title>First Web Application</title> </head> <body> Here are the list of your todos: ${todos} <BR/> Your Name is : ${name} </body> </html> src/main/webapp/WEB-INF/jsp/welcome.jsp Modified New Lines Welcome ${name}!!

<a href="/list-todos">Click here</a> to manage your todo's. Step13.md What You Will Do: - Add functionality to create a new Todo. - Work with the todo.jsp page for adding todos. - Learn the importance of using redirect:/list-todos after form submission: - Prevents duplicate form submissions on page refresh. - Updates the list view to show the newly added todo. Notes: - Controller handles the form submission and redirects to the list view. - Model and view interaction ensures the new todo is displayed correctly.

Files Modified: src/main/java/com/in28minutes/springboot/web/controller/TodoController.java @RequestMapping(value = "/add-todo", method = RequestMethod.GET) public String showAddTodoPage(ModelMap model) { return "todo"; } @RequestMapping(value = "/add-todo", method = RequestMethod.POST) public String addTodo(ModelMap model, @RequestParam String desc) { service.addTodo((String) model.get("name"), desc, new Date(), false); return "redirect:/list-todos"; } src/main/webapp/WEB-INF/jsp/list-todos.jsp Modified New Lines Here are the list of ${name}'s todos: ${todos}.

<a href="/add-todo">Add a Todo</a> src/main/webapp/WEB-INF/jsp/todo.jsp New <html> <head> <title>First Web Application</title> </head> <body> ADD Todo Page for ${name} <form method="post"> Description : <input name="desc" type="text"/> <input type="submit"/> </form> </body> </html> Step14.md What You Will Do: - Display the list of Todos in a table using JSTL tags. - Use the JSTL core tag library: <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> - Add the necessary JSTL dependency to your project. Notes: - JSTL tags make it easier to iterate over collections, conditionally display content, and reduce scriptlets in JSP.

This step focuses on dynamic content rendering using standard JSP techniques. Files Modified: list-todos.jsp (to use JSTL tags for displaying todos) <dependency> <groupId>jakarta.servlet.jsp.jstl</groupId> <artifactId>jakarta.servlet.jsp.jstl-api</artifactId> </dependency> Step15.md What You Will Do: - Add Bootstrap to give basic styling and formatting to pages. - Use Bootstrap classes like container ,table , andtable-striped . - Use Bootstrap classes like - Use WebJars to manage Bootstrap dependencies. - Leverage Spring Boot auto-configuration for WebJars: SimpleUrlHandlerMapping automatically maps/webjars/** URLs to the appropriate resource handler.

Notes: - WebJars allow you to include client-side libraries (like Bootstrap) via Maven. - This step improves UI presentation without manual setup of static resources.

Useful Snippets <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>3.3.6</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>1.9.1</version> </dependency> <script src="webjars/jquery/1.9.1/jquery.min.js"></script> <script src="webjars/bootstrap/3.3.6/js/bootstrap.min.js"></script> <link href="webjars/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet"> Files List pom.xml Modified New Lines <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>5.1.3</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.6.0</version> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope> <!--default for IntelliJ --> </dependency> <dependency> <groupId>jakarta.servlet.jsp.jstl</groupId> <artifactId>jakarta.servlet.jsp.jstl-api</artifactId> </dependency> src/main/resources/application.properties Modified New Lines logging.level.org.springframework.web=INFO src/main/webapp/WEB-INF/jsp/list-todos.jsp Modified New Lines <%@ taglib prefix="c" uri="jakarta.tags.core" %> <title>Todo's for ${name}</title> <link href="webjars/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet"> <div class="container"> <table class="table table-striped"> <caption>Your todos are</caption> <thead> <tr> <th>Description</th> <th>Target Date</th> <th>Is it Done?</th> </tr> </thead> <tbody> <c:forEach items="${todos}" var="todo"> <tr> <td>${todo.desc}</td> <td>${todo.targetDate}</td> <td>${todo.done}</td> </tr> </c:forEach> </tbody> </table> <div><a class="button" href="/add-todo">Add a Todo</a></div> <script src="webjars/jquery/1.9.1/jquery.min.js"></script> <script src="webjars/bootstrap/3.3.6/js/bootstrap.min.js"></script> </div> Step16.md What we will do: - Add functionality to delete a todo Useful Snippets <a type="button" class="btn btn-warning" href="/delete-todo?id=${todo.id}">Delete</a> Files List src/main/java/com/in28minutes/springboot/web/controller/TodoController.java Modified New Lines @RequestMapping(value = "/delete-todo", method = RequestMethod.GET) public String deleteTodo(@RequestParam int id) { service.deleteTodo(id); } src/main/webapp/WEB-INF/jsp/list-todos.jsp Modified New Lines <td><a type="button" class="btn btn-warning" href="/delete-todo?id=${todo.id}">Delete</a></td> Step17.md Step17.md What You Will Do: - Format the Add Todo page for better presentation.

Add HTML5 form validations to ensure proper input from the user. - Examples: required fields, date format checks, and text length constraints. Notes: - HTML5 validations provide basic client-side checks before the form is submitted. - Improves user experience by preventing invalid data entry. Useful Snippets: - Form fields with HTML5 validation attributes ( required ,type="date" , etc.).

<fieldset class="form-group"> <label>Description</label> <input name="desc" type="text" class="form-control" required="required"/> </fieldset> Files List src/main/webapp/WEB-INF/jsp/list-todos.jsp Modified Snippets <td><a type="button" class="btn btn-warning" href="/delete-todo?id=${todo.id}">Delete</a></td> <div> <a class="button" href="/add-todo">Add a Todo</a> </div> <script src="webjars/bootstrap/5.1.3/js/bootstrap.min.js"></script> <script src="webjars/jquery/3.6.0/jquery.min.js"></script> src/main/webapp/WEB-INF/jsp/todo.jsp Modified New Lines <link href="webjars/bootstrap/5.1.3/css/bootstrap.min.css" rel="stylesheet"> <div class="container"> <form method="post"> <fieldset class="form-group"> <label>Description</label> <input name="desc" type="text" class="form-control" required="required"/> </fieldset> <button type="submit" class="btn btn-success">Add</button> </form> </div> <script src="webjars/bootstrap/5.1.3/js/bootstrap.min.js"></script> <script src="webjars/jquery/3.6.0/jquery.min.js"></script> Step18.md What we will do: - Lets use a command bean for Todo - Add Validations - The JSR 303 and JSR 349 defines specification for the Bean Validation API (version 1.0 and 1.1, respectively), and Hibernate Validator is the reference implementation.

org.hibernate:hibernate-validator Useful Snippets <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <form:form method="post" commandName="todo"> <!-- use modelAttribute instead of commandName for Spring Boot Versions > 2.* <form:form method="post" modelAttribute="todo"> --> <fieldset class="form-group"> <form:label path="desc">Description</form:label> <form:input path="desc" type="text" class="form-control" required="required"/> </fieldset> </form:form> @Size(min = 10, message = "Enter atleast 10 Characters.") @Valid Todo todo, BindingResult result if (result.hasErrors()) return "todo"; <form:errors path="desc" cssClass="text-warning"/> Files List src/main/java/com/in28minutes/springboot/web/controller/TodoController.java Modified @Controller @SessionAttributes("name") public class TodoController { @Autowired TodoService service; @RequestMapping(value = "/list-todos", method = RequestMethod.GET) public String showTodos(ModelMap model) { String name = (String) model.get("name"); model.put("todos", service.retrieveTodos(name)); return "list-todos"; } @RequestMapping(value = "/add-todo", method = RequestMethod.GET) public String showAddTodoPage(ModelMap model) { model.addAttribute("todo", new Todo(0, (String) model.get("name"), "Default Desc", new Date(), false)); return "todo"; } @RequestMapping(value = "/delete-todo", method = RequestMethod.GET) public String deleteTodo(@RequestParam int id) { service.deleteTodo(id); return "redirect:/list-todos"; } @RequestMapping(value = "/add-todo", method = RequestMethod.POST) public String addTodo(ModelMap model, @Valid Todo todo, BindingResult result) { if (result.hasErrors()) { return "todo"; } service.addTodo((String) model.get("name"), todo.getDesc(), new Date(), false); return "redirect:/list-todos"; } } src/main/java/com/in28minutes/springboot/web/model/Todo.java Modified New Lines public Todo() { super(); } @Size(min = 10, message = "Enter at least 10 Characters...") private String desc; src/main/webapp/WEB-INF/jsp/todo.jsp Modified New Lines <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <form:form method="post" commandName="todo"> <!-- use modelAttribute instead of commandName for Spring Boot Versions > 2.* <form:form method="post" modelAttribute="todo"> --> <form:label path="desc">Description</form:label> <form:input path="desc" type="text" <form:errors path="desc" cssClass="text-warning"/> </form:form> Step19.md What You Will Do: - Add update functionality for existing todos.

Reuse the same JSP page used for adding todos to handle updates. Notes: - Updating a todo involves pre-populating the form with existing data. - This step demonstrates reusing views and keeping the UI consistent. - Controller logic will differentiate between add and update operations based on the presence of an existing todo. Useful Snippets public Todo retrieveTodo(int id) { for (Todo todo : todos) { if (todo.getId() == id) return todo; } return null; } public void updateTodo(Todo todo) { todos.remove(todo); todos.add(todo); } todo.

updateTodo(todo); <form: hidden path = "id" / > Files List src/main/java/com/in28minutes/springboot/web/controller/TodoController.java Modified @RequestMapping(value = "/update-todo", method = RequestMethod.GET) public String showUpdateTodoPage(@RequestParam int id, ModelMap model) { Todo todo = service.retrieveTodo(id); model.put("todo", todo); return "todo"; } @RequestMapping(value = "/update-todo", method = RequestMethod.POST) public String updateTodo(ModelMap model, @Valid Todo todo, BindingResult result) { if (result.hasErrors()) { return "todo"; } todo.setUser((String) model.get("name")); service.updateTodo(todo); return "redirect:/list-todos"; } src/main/java/com/in28minutes/springboot/web/service/TodoService.java Modified public List<Todo> retrieveTodos(String user) { List<Todo> filteredTodos = new ArrayList<>(); for (Todo todo : todos) { if (todo.getUser().equalsIgnoreCase(user)) { filteredTodos.add(todo); } } return filteredTodos; } public Todo retrieveTodo(int id) { for (Todo todo : todos) { if (todo.getId() == id) { return todo; } } return null; } public void updateTodo(Todo todo) { todos.remove(todo); todos.add(todo); } src/main/webapp/WEB-INF/jsp/list-todos.jsp Modified New Lines <td><a type="button" class="btn btn-success" href="/update-todo?id=${todo.id}">Update</a></td> src/main/webapp/WEB-INF/jsp/todo.jsp Modified New Lines <form:hidden path="id"/> Step20.md What You Will Do: - Utilize the Target Date field in the Todo form.

Learn about the initBinder method to handle date formatting and binding. Notes: initBinder allows customization of how form input is converted to Java objects.- Ensures that date fields are properly parsed and bound to the command bean. - Prepares the application for handling other complex data types in forms.

Useful Snippets <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> <fmt:formatDate pattern="dd/MM/yyyy" value="${todo.targetDate}"/> @InitBinder protected void initBinder(WebDataBinder binder) { SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy"); binder.registerCustomEditor(Date.class, new CustomDateEditor( dateFormat, false)); } <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap-datepicker</artifactId> <version>1.9.0</version> </dependency> <script src="webjars/bootstrap-datepicker/1.0.1/js/bootstrap-datepicker.js"></script> <script> $('#targetDate').datepicker({ format: 'dd/mm/yyyy' }); </script> Files List pom.xml Modified New Lines <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap-datepicker</artifactId> <version>1.9.0</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>5.1.3</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.6.0</version> </dependency> src/main/java/com/in28minutes/springboot/web/controller/TodoController.java Modified @RequestMapping(value = "update-todo", method = RequestMethod.POST) public String updateTodo(ModelMap model, @Valid Todo todo, BindingResult result) { if (result.hasErrors()) { return "todo"; } String username = getLoggedInUsername(model); todo.setUsername(username); todoService.updateTodo(todo); return "redirect:list-todos"; } @RequestMapping(value = "add-todo", method = RequestMethod.POST) public String addNewTodo(ModelMap model, @Valid Todo todo, BindingResult result) { if (result.hasErrors()) { return "todo"; } String username = getLoggedInUsername(model); todoService.addTodo(username, todo.getDescription(), LocalDate.now().plusYears(1), false); return "redirect:list-todos"; } src/main/webapp/WEB-INF/jsp/todo.jsp Modified New Snippets <form:input type="hidden" path="id"/> <form:input type="hidden" path="done"/> <form:label path="desc">Description</form:label> <form:input path="desc" type="text" class="form-control" required="required" /> <form:errors path="desc" cssClass="text-warning" /> <form:label path="targetDate">Target Date</form:label> <form:input path="targetDate" type="text" class="form-control" required="required" /> <form:errors path="targetDate" cssClass="text-warning" /> <script src="webjars/bootstrap-datepicker/1.9.0/js/bootstrap-datepicker.js"></script> <script type="text/javascript"> $('#targetDate').datepicker({ format: 'yyyy-mm-dd' }); </script> Step21.md What we will do: - Add a navigation bar - Use JSP Fragments - Exercise : Align the login & welcome pages.

Useful Snippets <nav class="navbar navbar-expand-md navbar-light bg-light mb-3 p-1"> <a class="navbar-brand m-1" href="https://courses.in28minutes.com">in28minutes</a> <div class="collapse navbar-collapse"> <ul class="navbar-nav"> <li class="nav-item"><a class="nav-link" href="/">Home</a></li> <li class="nav-item"><a class="nav-link" href="/list-todos">Todos</a></li> </ul> </div> <ul class="navbar-nav"> <li class="nav-item"><a class="nav-link" href="/logout">Logout</a></li> </ul> </nav> Files List src/main/webapp/WEB-INF/jsp/common/footer.jspf New <script src="webjars/bootstrap/5.1.3/js/bootstrap.min.js"></script> <script src="webjars/jquery/3.6.0/jquery.min.js"></script> <script src="webjars/bootstrap-datepicker/1.9.0/js/bootstrap-datepicker.min.js"></script> </body> </html> src/main/webapp/WEB-INF/jsp/common/header.jspf New <%@ taglib prefix="c" uri="jakarta.tags.core" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <html> <head> <link href="webjars/bootstrap/5.1.3/css/bootstrap.min.css" rel="stylesheet" > <link href="webjars/bootstrap-datepicker/1.9.0/css/bootstrap-datepicker.standalone.min.css" rel="stylesheet" > <title>Manage Your Todos</title> </head> <body> src/main/webapp/WEB-INF/jsp/common/navigation.jspf New <nav class="navbar navbar-expand-md navbar-light bg-light mb-3 p-1"> <a class="navbar-brand m-1" href="https://courses.in28minutes.com">in28minutes</a> <div class="collapse navbar-collapse"> <ul class="navbar-nav"> <li class="nav-item"><a class="nav-link" href="/">Home</a></li> <li class="nav-item"><a class="nav-link" href="/list-todos">Todos</a></li> </ul> </div> <ul class="navbar-nav"> <li class="nav-item"><a class="nav-link" href="/logout">Logout</a></li> </ul> </nav> src/main/webapp/WEB-INF/jsp/list-todos.jsp Modified New Lines <%@ include file="common/header.jspf" %> <%@ include file="common/navigation.jspf" %> <%@ include file="common/footer.jspf" %> src/main/webapp/WEB-INF/jsp/login.jsp Modified New Lines <%@ include file="common/header.jspf" %> <%@ include file="common/navigation.jspf" %> <div class="container"> <!--OLD CODE--> </div> <%@ include file="common/footer.jspf" %> src/main/webapp/WEB-INF/jsp/todo.jsp Modified New Lines <%@ include file="common/header.jspf" %> <%@ include file="common/navigation.jspf" %> <div class="container"> <form:form method="post" commandName="todo"> <!-- use modelAttribute instead of commandName for Spring Boot Versions > 2.* <form:form method="post" modelAttribute="todo"> --> <form:hidden path="id" /> <fieldset class="form-group"> <form:label path="desc">Description</form:label> <form:input path="desc" type="text" class="form-control" required="required" /> <form:errors path="desc" cssClass="text-warning" /> </fieldset> <fieldset class="form-group"> <form:label path="targetDate">Target Date</form:label> <form:input path="targetDate" type="text" class="form-control" required="required" /> <form:errors path="targetDate" cssClass="text-warning" /> </fieldset> <button type="submit" class="btn btn-success">Add</button> </form:form> </div> <%@ include file="common/footer.jspf" %> src/main/webapp/WEB-INF/jsp/welcome.jsp Modified New Lines <%@ include file="common/header.jspf"%> <%@ include file="common/navigation.jspf"%> <div class="container"> Welcome ${name}!!

</div> <%@ include file="common/footer.jspf"%> Step22.md What we will do: - Set up the application for Spring Security - Remove all custom login-related functionality - Make the Welcome page the default (with initial hardcoding) - Refactor the getLoggedInUserName method - Update the Home Page link in the navigation Files List src/main/java/com/in28minutes/springboot/web/controller/LoginController.java Modified New Lines @RequestMapping(value = "/", method = RequestMethod.GET) public String showLoginPage(ModelMap model) { model.put("name", "in28Minutes"); } src/main/java/com/in28minutes/springboot/web/controller/TodoController.java Modified @RequestMapping(value = "/list-todos", method = RequestMethod.GET) public String showTodos(ModelMap model) { String name = getLoggedInUserName(model); model.put("todos", service.retrieveTodos(name)); return "list-todos"; } private String getLoggedInUserName(ModelMap model) { return (String) model.get("name"); } src/main/webapp/WEB-INF/jsp/common/navigation.jspf Modified New Lines <li class="active"><a href="/">Home</a></li> src/main/webapp/WEB-INF/jsp/login.jsp Deleted Step23.md What we will do: - Get Setup for Spring Security Useful Snippets <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> package com.in28minutes.springboot.myfirstwebapp.security; import static org.springframework.security.config.Customizer.withDefaults; import java.util.function.Function; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; @Configuration public class SpringSecurityConfiguration { //LDAP or Database //In Memory //InMemoryUserDetailsManager //InMemoryUserDetailsManager(UserDetails...

users) @Bean public InMemoryUserDetailsManager createUserDetailsManager() { UserDetails userDetails1 = createNewUser("in28minutes", "dummy"); UserDetails userDetails2 = createNewUser("ranga", "dummydummy"); return new InMemoryUserDetailsManager(userDetails1, userDetails2); } private UserDetails createNewUser(String username, String password) { Function<String, String> passwordEncoder = input -> passwordEncoder().encode(input); UserDetails userDetails = User.builder() .passwordEncoder(passwordEncoder) .username(username) .password(password) .roles("USER", "ADMIN") .build(); return userDetails; } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } //All URLs are protected //A login form is shown for unauthorized requests //CSRF disable //Frames @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests( auth -> auth.anyRequest().authenticated()); http.formLogin(withDefaults()); http.csrf(AbstractHttpConfigurer::disable); // OR // http.csrf(AbstractHttpConfigurer::disable); http.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)); // Starting from SB 3.1.x return http.build(); } } Not Needed anymore with Spring Boot Auto Configuration <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> Files List pom.xml Modified New Lines <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> src/main/java/com/in28minutes/springboot/web/security/SecurityConfiguration.java New package com.in28minutes.springboot.myfirstwebapp.security; import static org.springframework.security.config.Customizer.withDefaults; import java.util.function.Function; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; @Configuration public class SpringSecurityConfiguration { //LDAP or Database //In Memory //InMemoryUserDetailsManager //InMemoryUserDetailsManager(UserDetails...

users) @Bean public InMemoryUserDetailsManager createUserDetailsManager() { UserDetails userDetails1 = createNewUser("in28minutes", "dummy"); UserDetails userDetails2 = createNewUser("ranga", "dummydummy"); return new InMemoryUserDetailsManager(userDetails1, userDetails2); } private UserDetails createNewUser(String username, String password) { Function<String, String> passwordEncoder = input -> passwordEncoder().encode(input); UserDetails userDetails = User.builder() .passwordEncoder(passwordEncoder) .username(username) .password(password) .roles("USER", "ADMIN") .build(); return userDetails; } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } //All URLs are protected //A login form is shown for unauthorized requests //CSRF disable //Frames @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests( auth -> auth.anyRequest().authenticated()); http.formLogin(withDefaults()); http.csrf(AbstractHttpConfigurer::disable); // OR // http.csrf(AbstractHttpConfigurer::disable); http.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)); // Starting from SB 3.1.x return http.build(); } } Step24.md What we will do: - Remove hardcoded user name - Delete the LoginService - Rename LoginController toWelcomeController - Implement logout functionality Useful Snippets private String getLoggedInUserName(ModelMap model) { Object principal = SecurityContextHolder.getContext() .getAuthentication().getPrincipal(); if (principal instanceof UserDetails) return ((UserDetails) principal).getUsername(); return principal.toString(); } <ul class="nav navbar-nav navbar-right"> <li><a href="/logout">Logout</a></li> </ul> @RequestMapping(value = "/logout", method = RequestMethod.GET) public String logout(HttpServletRequest request, HttpServletResponse response) { Authentication auth = SecurityContextHolder.getContext() .getAuthentication(); if (auth != null) { new SecurityContextLogoutHandler().logout(request, response, auth); } return "redirect:/"; } Step25.md What we will do: - Introduce basic exception handling - Understand that exception handling is a cross-cutting concern - Avoid handling exceptions in Controllers or Services unless additional value is added - Refactor controllers for cleaner error management - Explore the default Spring Boot Whitelabel Error Page - Review the details shown on error pages - Learn how to customize error pages if needed - Use @ControllerAdvice for global and controller-specific exception handling - Handle errors thrown from views Useful Snippets @Controller("error") public class ExceptionController { private Log logger = LogFactory.getLog(ExceptionController.class); @ExceptionHandler(Exception.class) public ModelAndView handleError(HttpServletRequest req, Exception ex) { logger.error("Request: " + req.getRequestURL() + " raised " + ex); ModelAndView mav = new ModelAndView(); mav.addObject("exception", ex); mav.addObject("url", req.getRequestURL()); mav.setViewName("error"); return mav; } } Files List src/main/java/com/in28minutes/springboot/web/controller/ErrorController.java New package com.in28minutes.springboot.web.controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.ModelAndView; @Controller("error") public class ErrorController { @ExceptionHandler(Exception.class) public ModelAndView handleException (HttpServletRequest request, Exception ex) { ModelAndView mv = new ModelAndView(); mv.addObject("exception", ex.getLocalizedMessage()); mv.addObject("url", request.getRequestURL()); mv.setViewName("error"); return mv; } } src/main/java/com/in28minutes/springboot/web/controller/LoginController.java Deleted src/main/java/com/in28minutes/springboot/web/controller/LogoutController.java New package com.in28minutes.springboot.web.controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class LogoutController { @RequestMapping(value = "/logout", method = RequestMethod.GET) public String logout(HttpServletRequest request, HttpServletResponse response) { Authentication authentication = SecurityContextHolder.getContext() .getAuthentication(); if (authentication != null) { new SecurityContextLogoutHandler().logout(request, response, authentication); } return "redirect:/"; } } src/main/java/com/in28minutes/springboot/web/controller/TodoController.java Modified private String getLoggedInUserName(ModelMap model) { Object principal = SecurityContextHolder.getContext() .getAuthentication().getPrincipal(); if (principal instanceof UserDetails) { return ((UserDetails) principal).getUsername(); } return principal.toString(); } src/main/java/com/in28minutes/springboot/web/controller/WelcomeController.java New package com.in28minutes.springboot.web.controller; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class WelcomeController { @RequestMapping(value = "/", method = RequestMethod.GET) public String showWelcomePage(ModelMap model) { model.put("name", getLoggedinUserName()); return "welcome"; } private String getLoggedinUserName() { Object principal = SecurityContextHolder.getContext() .getAuthentication().getPrincipal(); if (principal instanceof UserDetails) { return ((UserDetails) principal).getUsername(); } return principal.toString(); } } src/main/java/com/in28minutes/springboot/web/service/LoginService.java Deleted src/main/webapp/WEB-INF/jsp/common/navigation.jspf Modified New Lines <nav role="navigation" class="navbar navbar-default"> <div class=""> <a href="http://www.in28minutes.com" class="navbar-brand">in28Minutes</a> </div> <div class="navbar-collapse"> <ul class="nav navbar-nav"> <li class="active"><a href="/">Home</a></li> <li><a href="/list-todos">Todos</a></li> </ul> <ul class="nav navbar-nav navbar-right"> <li><a href="/logout">Logout</a></li> </ul> </div> </nav> src/main/webapp/WEB-INF/jsp/error.jsp New <%@ include file="common/header.jspf"%> <%@ include file="common/navigation.jspf"%> <div class="container"> An exception occurred!

Please contact Support! </div> <%@ include file="common/footer.jspf"%> Connecting Web Application with JPA Note: Complete the “Introduction to JPA” module before starting this section.

Step 26: Add dependencies for JPA and H2 - Step 27: Configure the H2 Console - Step 28: Create the Todo entity and JPA repository - Step 29: Insert a Todo using the JPA repository - Step 30: Update, delete, and retrieve Todos using the JPA repository - Step 31: Initialize data with data.sql - Step 32: Connect JPA to other databases - Step 33: Upgrade to Spring Boot 2 and Spring 5 Step 26 to 32 /pom.xml New <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.in28minutes.springboot.web</groupId> <artifactId>spring-boot-first-web-application-git</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>spring-boot-first-web-application</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.5.5</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>21</java.version> <maven-jar-plugin.version>3.1.1</maven-jar-plugin.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-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope> <!--default for IntelliJ --> </dependency> <dependency> <groupId>jakarta.servlet.jsp.jstl</groupId> <artifactId>jakarta.servlet.jsp.jstl-api</artifactId> </dependency> <dependency> <groupId>org.glassfish.web</groupId> <artifactId>jakarta.servlet.jsp.jstl</artifactId> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>5.1.3</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.6.0</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap-datepicker</artifactId> <version>1.9.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> /src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java New package com.in28minutes.springboot.web; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; @SpringBootApplication @ComponentScan("com.in28minutes.springboot.web") public class SpringBootFirstWebApplication { public static void main(String[] args) { SpringApplication.run(SpringBootFirstWebApplication.class, args); } } /src/main/java/com/in28minutes/springboot/web/controller/ErrorController.java New package com.in28minutes.springboot.web.controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.ModelAndView; @Controller("error") public class ErrorController { @ExceptionHandler(Exception.class) public ModelAndView handleException (HttpServletRequest request, Exception ex) { ModelAndView mv = new ModelAndView(); mv.addObject("exception", ex.getLocalizedMessage()); mv.addObject("url", request.getRequestURL()); mv.setViewName("error"); return mv; } } /src/main/java/com/in28minutes/springboot/web/controller/LogoutController.java New package com.in28minutes.springboot.web.controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class LogoutController { @RequestMapping(value = "/logout", method = RequestMethod.GET) public String logout(HttpServletRequest request, HttpServletResponse response) { Authentication authentication = SecurityContextHolder.getContext() .getAuthentication(); if (authentication != null) { new SecurityContextLogoutHandler().logout(request, response, authentication); } return "redirect:/"; } } /src/main/java/com/in28minutes/springboot/web/controller/TodoController.java New package com.in28minutes.springboot.web.controller; import java.text.SimpleDateFormat; import java.util.Date; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.propertyeditors.CustomDateEditor; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.validation.BindingResult; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import com.in28minutes.springboot.web.model.Todo; import com.in28minutes.springboot.web.service.TodoRepository; @Controller public class TodoController { @Autowired TodoRepository repository; @InitBinder public void initBinder(WebDataBinder binder) { // Date - dd/MM/yyyy SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy"); binder.registerCustomEditor(Date.class, new CustomDateEditor( dateFormat, false)); } @RequestMapping(value = "/list-todos", method = RequestMethod.GET) public String showTodos(ModelMap model) { String name = getLoggedInUserName(model); model.put("todos", repository.findByUser(name)); //model.put("todos", service.retrieveTodos(name)); return "list-todos"; } private String getLoggedInUserName(ModelMap model) { Object principal = SecurityContextHolder.getContext() .getAuthentication().getPrincipal(); if (principal instanceof UserDetails) { return ((UserDetails) principal).getUsername(); } return principal.toString(); } @RequestMapping(value = "/add-todo", method = RequestMethod.GET) public String showAddTodoPage(ModelMap model) { model.addAttribute("todo", new Todo(0, getLoggedInUserName(model), "Default Desc", new Date(), false)); return "todo"; } @RequestMapping(value = "/delete-todo", method = RequestMethod.GET) public String deleteTodo(@RequestParam int id) { //if(id==1) //throw new RuntimeException("Something went wrong"); repository.delete(id); //service.deleteTodo(id); return "redirect:/list-todos"; } @RequestMapping(value = "/update-todo", method = RequestMethod.GET) public String showUpdateTodoPage(@RequestParam int id, ModelMap model) { Todo todo = repository.findOne(id); //Todo todo = service.retrieveTodo(id); model.put("todo", todo); return "todo"; } @RequestMapping(value = "/update-todo", method = RequestMethod.POST) public String updateTodo(ModelMap model, @Valid Todo todo, BindingResult result) { if (result.hasErrors()) { return "todo"; } todo.setUser(getLoggedInUserName(model)); repository.save(todo); //service.updateTodo(todo); return "redirect:/list-todos"; } @RequestMapping(value = "/add-todo", method = RequestMethod.POST) public String addTodo(ModelMap model, @Valid Todo todo, BindingResult result) { if (result.hasErrors()) { return "todo"; } todo.setUser(getLoggedInUserName(model)); repository.save(todo); /*service.addTodo(getLoggedInUserName(model), todo.getDesc(), todo.getTargetDate(), false);*/ return "redirect:/list-todos"; } } /src/main/java/com/in28minutes/springboot/web/controller/WelcomeController.java New package com.in28minutes.springboot.web.controller; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class WelcomeController { @RequestMapping(value = "/", method = RequestMethod.GET) public String showWelcomePage(ModelMap model) { model.put("name", getLoggedinUserName()); return "welcome"; } private String getLoggedinUserName() { Object principal = SecurityContextHolder.getContext() .getAuthentication().getPrincipal(); if (principal instanceof UserDetails) { return ((UserDetails) principal).getUsername(); } return principal.toString(); } } /src/main/java/com/in28minutes/springboot/web/model/Todo.java New package com.in28minutes.springboot.web.model; import java.util.Date; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.validation.constraints.Size; @Entity public class Todo { @Id @GeneratedValue private int id; private String user; @Size(min = 10, message = "Enter at least 10 Characters...") private String desc; private Date targetDate; private boolean isDone; public Todo() { super(); } public Todo(int id, String user, String desc, Date targetDate, boolean isDone) { super(); this.id = id; this.user = user; this.desc = desc; this.targetDate = targetDate; this.isDone = isDone; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUser() { return user; } public void setUser(String user) { this.user = user; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } public Date getTargetDate() { return targetDate; } public void setTargetDate(Date targetDate) { this.targetDate = targetDate; } public boolean isDone() { return isDone; } public void setDone(boolean isDone) { this.isDone = isDone; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + id; return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Todo other = (Todo) obj; if (id != other.id) { return false; } return true; } @Override public String toString() { return String.format( "Todo [id=%s, user=%s, desc=%s, targetDate=%s, isDone=%s]", id, user, desc, targetDate, isDone); } } /src/main/java/com/in28minutes/springboot/web/security/SecurityConfiguration.java New package com.in28minutes.springboot.web.security; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { //Create User - in28Minutes/dummy @Autowired public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().passwordEncoder(org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance()).withUser("in28Minutes").password("dummy") .roles("USER", "ADMIN"); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/login", "/h2-console/**").permitAll() .antMatchers("/", "/*todo*/**").access("hasRole('USER')").and() .formLogin(); http.csrf().disable(); http.headers().frameOptions().disable(); } } /src/main/java/com/in28minutes/springboot/web/service/TodoRepository.java New package com.in28minutes.springboot.web.service; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import com.in28minutes.springboot.web.model.Todo; public interface TodoRepository extends JpaRepository<Todo, Integer> { List<Todo> findByUser(String user); //service.retrieveTodos(name) //service.deleteTodo(id); //service.retrieveTodo(id) //service.updateTodo(todo) //service.addTodo(getLoggedInUserName(model), todo.getDesc(), todo.getTargetDate(),false); } /src/main/java/com/in28minutes/springboot/web/service/TodoService.java New package com.in28minutes.springboot.web.service; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.List; import org.springframework.stereotype.Service; import com.in28minutes.springboot.web.model.Todo; @Service public class TodoService { private static List<Todo> todos = new ArrayList<Todo>(); private static int todoCount = 3; static { todos.add(new Todo(1, "in28Minutes", "Learn Spring MVC", new Date(), false)); todos.add(new Todo(2, "in28Minutes", "Learn Struts", new Date(), false)); todos.add(new Todo(3, "in28Minutes", "Learn Hibernate", new Date(), false)); } public List<Todo> retrieveTodos(String user) { List<Todo> filteredTodos = new ArrayList<Todo>(); for (Todo todo : todos) { if (todo.getUser().equalsIgnoreCase(user)) { filteredTodos.add(todo); } } return filteredTodos; } public Todo retrieveTodo(int id) { for (Todo todo : todos) { if (todo.getId() == id) { return todo; } } return null; } public void updateTodo(Todo todo) { todos.remove(todo); todos.add(todo); } public void addTodo(String name, String desc, Date targetDate, boolean isDone) { todos.add(new Todo(++todoCount, name, desc, targetDate, isDone)); } public void deleteTodo(int id) { Iterator<Todo> iterator = todos.iterator(); while (iterator.hasNext()) { Todo todo = iterator.next(); if (todo.getId() == id) { iterator.remove(); } } } } /src/main/resources/application.properties New spring.mvc.view.prefix=/WEB-INF/jsp/ spring.mvc.view.suffix=.jsp logging.level.org.springframework.web=INFO spring.jpa.show-sql=true spring.datasource.url=jdbc:h2:mem:testdb spring.data.jpa.repositories.bootstrap-mode=default spring.h2.console.enabled=true /src/main/resources/data.sql New insert into TODO values(10001, 'Learn Spring Boot', false, sysdate(), 'in28Minutes'); insert into TODO values(10002, 'Learn Angular JS', false, sysdate(), 'in28Minutes'); insert into TODO values(10003, 'Learn to Dance', false, sysdate(), 'in28Minutes'); /src/main/webapp/WEB-INF/jsp/common/footer.jspf New <script src="webjars/jquery/1.9.1/jquery.min.js"></script> <script src="webjars/bootstrap/3.3.6/js/bootstrap.min.js"></script> <script src="webjars/bootstrap-datepicker/1.0.1/js/bootstrap-datepicker.js"></script> <script> $('#targetDate').datepicker({ format : 'dd/mm/yyyy' }); </script> </body> </html> /src/main/webapp/WEB-INF/jsp/common/header.jspf New <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> <%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> <html> <head> <title>First Web Application</title> <link href="webjars/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet"> </head> <body> /src/main/webapp/WEB-INF/jsp/common/navigation.jspf New <nav role="navigation" class="navbar navbar-default"> <div class=""> <a href="http://www.in28minutes.com" class="navbar-brand">in28Minutes</a> </div> <div class="navbar-collapse"> <ul class="nav navbar-nav"> <li class="active"><a href="/">Home</a></li> <li><a href="/list-todos">Todos</a></li> </ul> <ul class="nav navbar-nav navbar-right"> <li><a href="/logout">Logout</a></li> </ul> </div> </nav> /src/main/webapp/WEB-INF/jsp/error.jsp New <%@ include file="common/header.jspf"%> <%@ include file="common/navigation.jspf"%> <div class="container"> An exception occurred!

</div> <%@ include file="common/footer.jspf"%> /src/main/webapp/WEB-INF/jsp/list-todos.jsp New <%@ include file="common/header.jspf" %> <%@ include file="common/navigation.jspf" %> <div class="container"> <table class="table table-striped"> <caption>Your todos are</caption> <thead> <tr> <th>Description</th> <th>Target Date</th> <th>Is it Done?</th> <th></th> <th></th> </tr> </thead> <tbody> <c:forEach items="${todos}" var="todo"> <tr> <td>${todo.desc}</td> <td><fmt:formatDate value="${todo.targetDate}" pattern="dd/MM/yyyy"/></td> <td>${todo.done}</td> <td><a type="button" class="btn btn-success" href="/update-todo?id=${todo.id}">Update</a></td> <td><a type="button" class="btn btn-warning" href="/delete-todo?id=${todo.id}">Delete</a></td> </tr> </c:forEach> </tbody> </table> <div> <a class="button" href="/add-todo">Add a Todo</a> </div> </div> <%@ include file="common/footer.jspf" %> /src/main/webapp/WEB-INF/jsp/todo.jsp New <%@ include file="common/header.jspf" %> <%@ include file="common/navigation.jspf" %> <div class="container"> <form:form method="post" commandName="todo"> <!-- use modelAttribute instead of commandName for Spring Boot Versions > 2.* <form:form method="post" modelAttribute="todo"> --> <form:hidden path="id" /> <fieldset class="form-group"> <form:label path="desc">Description</form:label> <form:input path="desc" type="text" class="form-control" required="required" /> <form:errors path="desc" cssClass="text-warning" /> </fieldset> <fieldset class="form-group"> <form:label path="targetDate">Target Date</form:label> <form:input path="targetDate" type="text" class="form-control" required="required" /> <form:errors path="targetDate" cssClass="text-warning" /> </fieldset> <button type="submit" class="btn btn-success">Add</button> </form:form> </div> <%@ include file="common/footer.jspf" %> /src/main/webapp/WEB-INF/jsp/welcome.jsp New <%@ include file="common/header.jspf"%> <%@ include file="common/navigation.jspf"%> <div class="container"> Welcome ${name}!!

</div> <%@ include file="common/footer.jspf"%> /src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java New package com.in28minutes.springboot.web; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class SpringBootFirstWebApplicationTests { @Test public void contextLoads() { } } step33-Upgrade-To-Sprint-Boot-2-M3.md /pom.xml Modified New Lines <version>2.3.1.RELEASE</version> /src/main/java/com/in28minutes/springboot/web/controller/TodoController.java Modified New Lines repository.deleteById(id); Todo todo = repository.findById(id).get(); /src/main/webapp/WEB-INF/jsp/todo.jsp Modified New Lines <form:form method="post" modelAttribute="todo"> Files List 02.Spring-Boot-Web-Application/notes.md Schema create table todo ( id integer not null, desc varchar(255), is_done boolean not null, target_date timestamp, user varchar(255), primary key (id) ) Data INSERT INTO TODO VALUES (10001, 'Learn Spring Boot', false, sysdate(), 'in28Minutes') INSERT INTO TODO VALUES (10002, 'Learn RESTful Web Services', false, sysdate(), 'in28Minutes') INSERT INTO TODO VALUES (10003, 'Learn SOAP Web Services', false, sysdate(), 'in28Minutes') 02.Spring-Boot-Web-Application/README.md Developing your first Spring Boot Web Application is fun.

Installing and Setting Up MySQL - Install MySQL https://dev.mysql.com/doc/en/installing.html - More details : http://www.mysqltutorial.org/install-mysql/ - Trouble Shooting - https://dev.mysql.com/doc/refman/en/problems.html - Startup the Server (as a service) - Go to command prompt (or terminal) - Execute following commands to create a database and a user mysql --user=user_name --password db_name create database todo_example; create user 'todouser'@'localhost' identified by 'YOUR_PASSWORD'; grant all on todo_example.* to 'todouser'@'localhost'; - Execute following sql queries to create the table and insert the data Table create table todo ( id integer not null, desc varchar(255), is_done boolean not null, target_date timestamp, user varchar(255), primary key (id) ); Data INSERT INTO TODO VALUES (10001, 'Learn Spring Boot', false, sysdate(), 'in28Minutes'); INSERT INTO TODO VALUES (10002, 'Learn RESTful Web Services', false, sysdate(), 'in28Minutes'); INSERT INTO TODO VALUES (10003, 'Learn SOAP Web Services', false, sysdate(), 'in28Minutes'); Code changes to connect to MySQL - Add dependency to pom.xml (and remove H2 dependency) <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> - Configure application.properties spring.jpa.hibernate.ddl-auto=none spring.datasource.url=jdbc:mysql://localhost:3306/todo_example spring.datasource.username=todouser spring.datasource.password=YOUR_PASSWORD - Restart and You are ready!

You will learn about - Basics of Auto Configuration and Spring Boot Magic - Spring Boot Starter Projects - Spring Initializr - REST Service Content Negotiation with JSON and XML - Embedded servlet containers : Tomcat, Jetty and Undertow - Writing Unit and Integration tests using Spring Boot Starter Test - Profiles and Dynamic Configuration with Spring Boot - Spring Boot Data JPA - Spring Boot Actuator - Spring Boot Developer Tools and LiveReload Step01.md What You Will Learn during this Step: - Set up an Maven Project with Eclipse.

Intellij Link : https://www.jetbrains.com/help/idea/2016.2/getting-started-with-maven.html#create_maven_project - Copy Two Files pom.xml and Application.java - Launch Your First Spring Boot Application. - You will be introduced to Maven - Dependency Management Cool thing to note! - Without a lot of configuration, we are up and running with a web application - Refer https://github.com/in28minutes/SpringMvcStepByStep/blob/master/Step15.md to understand the sort of stuff - web.xml, dispatcher servlet configuration, maven dependency management and plugins - that are need to launch a typical web application without Spring Boot!

What You Will NOT Learn during this Step: - Spring Boot does a lot of magic. This magic is called Auto Configuration. We will discuss about different terms related to Spring Boot - Starter Parent, Starter projects, Auto configuration - in depth during our first 10 steps. - As far as this step is concerned, we will focus on getting up and running with Spring Boot. We will understand all the magic a little later.

We will copy a lot of code in this step - just to avoid typos Exercises - If you are comfortable with Spring, try to create a few dependencies and see if are automatically auto-wired!

Files List pom.xml <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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.in28minutes.springboot</groupId> <artifactId>first-springboot-project</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.0.RELEASE</version> </parent> <properties> <java.version>1.8</java.version> <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> src/main/java/com/in28minutes/springboot/Application.java package com.in28minutes.springboot; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; @SpringBootApplication public class Application { public static void main(String[] args) { ApplicationContext ctx = SpringApplication.run(Application.class, args); } } Step02.md What You Will Learn during this Step: - Lets add a RestController with a dependency and see Spring Boot Magic live Theory Break : Quick Spring and Spring MVC Primer - What is dependency?

@Component - @Autowired - @RestController Useful Snippets and References package com.in28minutes.springboot; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class WelcomeController { //Auto wiring @Autowired private WelcomeService service; @RequestMapping("/welcome") public String welcome() { return service.retrieveWelcomeMessage(); } } @Component class WelcomeService { public String retrieveWelcomeMessage() { //Complex Method return "Good Morning updated"; } } Files List src/main/java/com/in28minutes/service/WelcomeService.java New package com.in28minutes.service; import org.springframework.stereotype.Component; @Component public class WelcomeService { public String retrieveWelcomeMessage() { //Complex Method return "Good Morning updated"; } } src/main/java/com/in28minutes/springboot/Application.java Modified New Lines import org.springframework.context.annotation.ComponentScan; @ComponentScan("com.in28minutes") package com.in28minutes.springboot; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.ComponentScan; @SpringBootApplication @ComponentScan("com.in28minutes") public class Application { public static void main(String[] args) { ApplicationContext ctx = SpringApplication.run(Application.class, args); } } src/main/java/com/in28minutes/springboot/WelcomeController.java New package com.in28minutes.springboot; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.in28minutes.service.WelcomeService; @RestController public class WelcomeController { //Auto wiring @Autowired private WelcomeService service; @RequestMapping("/welcome") public String welcome() { return service.retrieveWelcomeMessage(); } } Step03.md What You Will Learn during this Step: - First installment of revealing how magic happens with Spring Boot.

As a Spring Boot developer, you need to understand what’s happening beneath the hood of Spring Boot! - spring-boot-starter-web : starter for building applications with Spring MVC. Tomcat is default embedded container. - We already added this starter in the first step! Now we will explore the features it provides - We will enable logging in DEBUG mode to understand further spring-boot-starter-web - Spring Boot Starter Web brings all dependencies needed to build normal and RESTful web applications. Look at the dependency tree.

All the dependencies are added in because of spring-boot-starter-web - Also look at /META-INF/spring.provides inside the spring-boot-starter-web.jar - Spring Boot Starter Web auto configures things needed to startup a web application.

Look at the log - Mapping servlet: ‘dispatcherServlet’ to [/] - Mapped “{[/error]}” onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object» org.springframework.boot.autoconfigure.web.BasicErrorController.error( javax.servlet.http.HttpServletRequest) - Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] - Look at package org.springframework.boot.autoconfigure.web in spring-boot-autoconfigure-*.jar - Go to url http://localhost:8080/some-non-existing-url Useful Snippets /src/main/resources/application.properties logging.level.org.springframework: DEBUG Files List src/main/java/com/in28minutes/service/WelcomeService.java Deleted src/main/java/com/in28minutes/springboot/Application.java Modified New Lines package com.in28minutes.springboot; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; @SpringBootApplication public class Application { public static void main(String[] args) { ApplicationContext ctx = SpringApplication.run(Application.class, args); } } src/main/java/com/in28minutes/springboot/WelcomeController.java Modified New Lines package com.in28minutes.springboot; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class WelcomeController { //Auto wiring @Autowired private WelcomeService service; @RequestMapping("/welcome") public String welcome() { return service.retrieveWelcomeMessage(); } } src/main/java/com/in28minutes/springboot/WelcomeService.java New package com.in28minutes.springboot; import org.springframework.stereotype.Component; @Component public class WelcomeService { public String retrieveWelcomeMessage() { //Complex Method return "Good Morning updated"; } } src/main/resources/application.properties New logging.level.org.springframework: DEBUG Step04.md What You Will Learn during this Step: - Understand Starter Parent - How to override things defined in Starter Parent - Other starter projects Starter Parent - Dependency Versions - Java Versions - Default Plugins Other Starter Projects - spring-boot-starter-web-services - spring-boot-starter-test - spring-boot-starter-jdbc - spring-boot-starter-security - spring-boot-starter-data-jpa - spring-boot-starter-data-rest - More at https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#using-boot-starter Useful Snippets and References First Snippet <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.0.RELEASE</version> </parent> Step05.md What You Will Learn during this Step: - Spring Boot vs Spring - What Spring Boot is Not!

Spring Boot vs Spring Applications with Spring Framework New - Over the next few years, a number of applications were developed with Spring Framework - Testable but - Lot of configuration (XML and Java) - Developing Spring Based application need configuration of a lot of beans! - Integration with other frameworks need configuration as well! - In the last few years, focus is moving from monolith applications to microservices. We need to be able to start project quickly.

Minimum or Zero start up time - Framework Setup - Deployment - Configurability - Logging, Transaction Management - Monitoring - Web Server Configuration Spring New - Spring is just a dependency injection framework. Spring focuses on the “plumbing” of enterprise applications so that teams can focus on application-level business logic, without unnecessary ties to specific deployment environments. - First half of the 2000 decade! EJBs - EJBs were NOT easy to develop.

Write a lot of xml and plumbing code to get EJBs running - Impossible to Unit Test - Alternative - Writing simple JDBC Code involved a lot of plumbing - Spring framework started with aim of making Java EE development simpler. - Goals - Make applications testable. i.e. easier to write unit tests - Reduce plumbing code of JDBC and JMS - Simple architecture. Minus EJB. - Integrates well with other popular frameworks.

Spring Boot New - Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”. - We take an opinionated view of the Spring platform and third-party libraries so you can get started with minimum fuss. - Example Problem Statements - You want to add Hibernate to your project. You dont worry about configuring a data source and a session factory. I will do if for you! - Goals - Provide quick start for projects with Spring. - Be opinionated but provide options.

Provide a range of non-functional features that are common to large classes of projects (e.g. embedded servers, security, metrics, health checks, externalized configuration). What Spring Boot is NOT?

It’s not an app or a web server - Does not implement any specific framework - for example, JPA or JMS - Does not generate code pom.xml Deleted src/main/java/com/in28minutes/springboot/Application.java Deleted src/main/java/com/in28minutes/springboot/WelcomeController.java Deleted src/main/java/com/in28minutes/springboot/WelcomeService.java Deleted src/main/resources/application.properties Deleted Step06.md What You Will Learn during this Step: - We want to prepare for creating a Rest Service - Survey - Question - SurveyService - We use hard-coded data to get started Files List Applications with Spring Framework Deleted Spring Deleted Spring Boot Deleted pom.xml New <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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.in28minutes.springboot</groupId> <artifactId>first-springboot-project</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.0.RELEASE</version> </parent> <properties> <java.version>1.8</java.version> <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> src/main/java/com/in28minutes/springboot/Application.java New package com.in28minutes.springboot; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; @SpringBootApplication public class Application { public static void main(String[] args) { ApplicationContext ctx = SpringApplication.run(Application.class, args); } } src/main/java/com/in28minutes/springboot/WelcomeController.java New package com.in28minutes.springboot; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class WelcomeController { //Auto wiring @Autowired private WelcomeService service; @RequestMapping("/welcome") public String welcome() { return service.retrieveWelcomeMessage(); } } src/main/java/com/in28minutes/springboot/WelcomeService.java New package com.in28minutes.springboot; import org.springframework.stereotype.Component; @Component public class WelcomeService { public String retrieveWelcomeMessage() { //Complex Method return "Good Morning updated"; } } src/main/java/com/in28minutes/springboot/model/Question.java New package com.in28minutes.springboot.model; import java.util.List; public class Question { private String id; private String description; private String correctAnswer; private List<String> options; // Needed by Caused by: com.fasterxml.jackson.databind.JsonMappingException: // Can not construct instance of com.in28minutes.springboot.model.Question: // no suitable constructor found, can not deserialize from Object value // (missing default constructor or creator, or perhaps need to add/enable // type information?) public Question() { } public Question(String id, String description, String correctAnswer, List<String> options) { super(); this.id = id; this.description = description; this.correctAnswer = correctAnswer; this.options = options; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getDescription() { return description; } public String getCorrectAnswer() { return correctAnswer; } public List<String> getOptions() { return options; } @Override public String toString() { return String .format("Question [id=%s, description=%s, correctAnswer=%s, options=%s]", id, description, correctAnswer, options); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((id == null) ?

0 : id.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Question other = (Question) obj; if (id == null) { if (other.id != null) return false; } else if (!id.equals(other.id)) return false; return true; } } src/main/java/com/in28minutes/springboot/model/Survey.java New package com.in28minutes.springboot.model; import java.util.List; public class Survey { private String id; private String title; private String description; private List<Question> questions; public Survey(String id, String title, String description, List<Question> questions) { super(); this.id = id; this.title = title; this.description = description; this.questions = questions; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public List<Question> getQuestions() { return questions; } public void setQuestions(List<Question> questions) { this.questions = questions; } @Override public String toString() { return "Survey [id=" + id + ", title=" + title + ", description=" + description + ", questions=" + questions + "]"; } } src/main/java/com/in28minutes/springboot/service/SurveyService.java New package com.in28minutes.springboot.service; import java.math.BigInteger; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.springframework.stereotype.Component; import com.in28minutes.springboot.model.Question; import com.in28minutes.springboot.model.Survey; @Component public class SurveyService { private static List<Survey> surveys = new ArrayList<>(); static { Question question1 = new Question("Question1", "Largest Country in the World", "Russia", Arrays.asList( "India", "Russia", "United States", "China")); Question question2 = new Question("Question2", "Most Populus Country in the World", "China", Arrays.asList( "India", "Russia", "United States", "China")); Question question3 = new Question("Question3", "Highest GDP in the World", "United States", Arrays.asList( "India", "Russia", "United States", "China")); Question question4 = new Question("Question4", "Second largest english speaking country", "India", Arrays .asList("India", "Russia", "United States", "China")); List<Question> questions = new ArrayList<>(Arrays.asList(question1, question2, question3, question4)); Survey survey = new Survey("Survey1", "My Favorite Survey", "Description of the Survey", questions); surveys.add(survey); } public List<Survey> retrieveAllSurveys() { return surveys; } public Survey retrieveSurvey(String surveyId) { for (Survey survey : surveys) { if (survey.getId().equals(surveyId)) { return survey; } } return null; } public List<Question> retrieveQuestions(String surveyId) { Survey survey = retrieveSurvey(surveyId); if (survey == null) { return null; } return survey.getQuestions(); } public Question retrieveQuestion(String surveyId, String questionId) { Survey survey = retrieveSurvey(surveyId); if (survey == null) { return null; } for (Question question : survey.getQuestions()) { if (question.getId().equals(questionId)) { return question; } } return null; } private SecureRandom random = new SecureRandom(); public Question addQuestion(String surveyId, Question question) { Survey survey = retrieveSurvey(surveyId); if (survey == null) { return null; } String randomId = new BigInteger(130, random).toString(32); question.setId(randomId); survey.getQuestions().add(question); return question; } } src/main/resources/application.properties New logging.level.org.springframework: DEBUG Step07.md What You Will Learn during this Step: - Create a REST Service for Retrieving all questions for a survey - Autowire SurveyService - Create @GetMapping(“/surveys/{surveyId}/questions”) - Use @PathVariable String surveyId - http://localhost:8080/surveys/Survey1/questions/ - How does the Bean get converted to a JSON?

Auto Configuration : If Jackson jar is on the class path, message converters are auto created! (Search in log : Creating shared instance of singleton bean ‘mappingJackson2HttpMessageConverter’) Some Theory - What is REST? - Architectural style for the web. REST specifies a set of constraints. - Client - Server : Server (service provider) should be different from a client (service consumer). - Enables loose coupling and independent evolution of server and client as new technologies emerge. - Each service should be stateless. - Each Resource has a resource identifier.

It should be possible to cache response. - Consumer of the service may not have a direct connection to the Service Provider. Response might be sent from a middle layer cache. - A resource can have multiple representations. Resource can modified through a message in any of the these representations. - Client - Server : Server (service provider) should be different from a client (service consumer).

Useful Snippets and References - JSON View : https://jsonview.com/ First Snippet @RestController class SurveyController { @Autowired private SurveyService surveyService; @GetMapping("/surveys/{surveyId}/questions") public List<Question> retrieveQuestions(@PathVariable String surveyId) { return surveyService.retrieveQuestions(surveyId); } } Exercise - Try to think about how the URI for retrieving the details of a specific question should be!

Files List pom.xml Modified New Lines <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> src/main/java/com/in28minutes/springboot/controller/SurveyController.java New package com.in28minutes.springboot.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import com.in28minutes.springboot.model.Question; import com.in28minutes.springboot.service.SurveyService; @RestController class SurveyController { @Autowired private SurveyService surveyService; @GetMapping("/surveys/{surveyId}/questions") public List<Question> retrieveQuestions(@PathVariable String surveyId) { return surveyService.retrieveQuestions(surveyId); } } Step08.md What You Will Learn during this Step: - Adding the second method to rest service to retrieve a specific question - This will be a very short step - http://localhost:8080/surveys/Survey1/questions/Question1 - Different Request Methods - GET - Retrieve details of a resource - POST - Create a new resource - PUT - Update an existing resource - PATCH - Update part of a resource - DELETE - Delete a resource Useful Snippets and References First Snippet @GetMapping(path = "/surveys/{surveyId}/questions/{questionId}") public Question retrieveQuestion(@PathVariable String surveyId, @PathVariable String questionId) { return surveyService.retrieveQuestion(surveyId, questionId); } Exercises - Write the method to retrieve all surveys!

Files List src/main/java/com/in28minutes/springboot/controller/SurveyController.java Modified New Lines // GET "/surveys/{surveyId}/questions/{questionId}" @GetMapping("/surveys/{surveyId}/questions/{questionId}") public Question retrieveDetailsForQuestion(@PathVariable String surveyId, @PathVariable String questionId) { return surveyService.retrieveQuestion(surveyId, questionId); package com.in28minutes.springboot.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import com.in28minutes.springboot.model.Question; import com.in28minutes.springboot.service.SurveyService; @RestController class SurveyController { @Autowired private SurveyService surveyService; @GetMapping("/surveys/{surveyId}/questions") public List<Question> retrieveQuestions(@PathVariable String surveyId) { return surveyService.retrieveQuestions(surveyId); } // GET "/surveys/{surveyId}/questions/{questionId}" @GetMapping("/surveys/{surveyId}/questions/{questionId}") public Question retrieveDetailsForQuestion(@PathVariable String surveyId, @PathVariable String questionId) { return surveyService.retrieveQuestion(surveyId, questionId); } } Step09.md What You Will Learn during this Step: - I hate the fact that I’ve to stop and start the server each time.

Can somebody save me? - Yeah. Spring Boot Developer Tools - By default, any entry on the classpath that points to a folder will be monitored for changes. - These will not trigger restart - /META-INF/maven, /META-INF/resources ,/resources ,/static ,/public or /templates - Folders can be configured : spring.devtools.restart.exclude=static/,public/ - Additional Paths : spring.devtools.restart.additional-paths - LiveReload http://livereload.com/extensions/ - Technology in progress!! So, expect a few problems!

Programming Tip - Become an expert at your IDE - https://www.youtube.com/watch?v=dN9GYsG1v_c Useful Snippets and References First Snippet <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> Exercises - Make changes and see if they reflect immediately Step10.md What You Will Learn during this Step: - Create a REST Service to add a new question to survey - @PostMapping(“/surveys/{surveyId}/questions”) - @RequestBody Question question - What should be Response Status for create?

ResponseEntity.created(location).build() - ResponseEntity.noContent().build() - Using Postman : https://www.getpostman.com - URL to POST to - http://localhost:8080/surveys/Survey1/questions Useful Snippets and References Sample Body for POST Request {"description":"Second Most Populous Country in the World","correctAnswer":"India","options":["India","Russia","United States","China"]} First Snippet @PostMapping("/surveys/{surveyId}/questions") ResponseEntity<?> add(@PathVariable String surveyId, @RequestBody Question question) { Question createdTodo = surveyService.addQuestion(surveyId, question); if (createdTodo == null) { return ResponseEntity.noContent().build(); } URI location = ServletUriComponentsBuilder.fromCurrentRequest() .path("/{id}").buildAndExpand(createdTodo.getId()).toUri(); return ResponseEntity.created(location).build(); } Exercises - Create more REST services of your choice Files List src/main/java/com/in28minutes/springboot/controller/SurveyController.java Modified New Lines import java.net.URI; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; // /surveys/{surveyId}/questions @PostMapping("/surveys/{surveyId}/questions") public ResponseEntity<Void> addQuestionToSurvey( @PathVariable String surveyId, @RequestBody Question newQuestion) { Question question = surveyService.addQuestion(surveyId, newQuestion); if (question == null) return ResponseEntity.noContent().build(); // Success - URI of the new resource in Response Header // Status - created // URI -> /surveys/{surveyId}/questions/{questionId} // question.getQuestionId() URI location = ServletUriComponentsBuilder.fromCurrentRequest().path( "/{id}").buildAndExpand(question.getId()).toUri(); // Status return ResponseEntity.created(location).build(); package com.in28minutes.springboot.controller; import java.net.URI; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import com.in28minutes.springboot.model.Question; import com.in28minutes.springboot.service.SurveyService; @RestController class SurveyController { @Autowired private SurveyService surveyService; @GetMapping("/surveys/{surveyId}/questions") public List<Question> retrieveQuestions(@PathVariable String surveyId) { return surveyService.retrieveQuestions(surveyId); } // GET "/surveys/{surveyId}/questions/{questionId}" @GetMapping("/surveys/{surveyId}/questions/{questionId}") public Question retrieveDetailsForQuestion(@PathVariable String surveyId, @PathVariable String questionId) { return surveyService.retrieveQuestion(surveyId, questionId); } // /surveys/{surveyId}/questions @PostMapping("/surveys/{surveyId}/questions") public ResponseEntity<Void> addQuestionToSurvey( @PathVariable String surveyId, @RequestBody Question newQuestion) { Question question = surveyService.addQuestion(surveyId, newQuestion); if (question == null) return ResponseEntity.noContent().build(); // Success - URI of the new resource in Response Header // Status - created // URI -> /surveys/{surveyId}/questions/{questionId} // question.getQuestionId() URI location = ServletUriComponentsBuilder.fromCurrentRequest().path( "/{id}").buildAndExpand(question.getId()).toUri(); // Status return ResponseEntity.created(location).build(); } } Step11.md What You Will Learn during this Step: - Understand Content Negotiation - Accept:application/xml - Deliver XML Responses from the REST Services - http://localhost:8080/surveys/Survey1/questions/ Useful Snippets and References First Snippet <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> </dependency> Second Snippet <List> <item> <id>Question1</id> <description>Largest Country in the World</description> <correctAnswer>Russia</correctAnswer> <options> <options>India</options> <options>Russia</options> <options>United States</options> <options>China</options> </options> </item> <item> <id>Question2</id> <description>Most Populus Country in the World</description> <correctAnswer>China</correctAnswer> <options> <options>India</options> <options>Russia</options> <options>United States</options> <options>China</options> </options> </item> <item> <id>Question3</id> <description>Highest GDP in the World</description> <correctAnswer>United States</correctAnswer> <options> <options>India</options> <options>Russia</options> <options>United States</options> <options>China</options> </options> </item> <item> <id>Question4</id> <description>Second largest english speaking country</description> <correctAnswer>India</correctAnswer> <options> <options>India</options> <options>Russia</options> <options>United States</options> <options>China</options> </options> </item> </List> Exercises - Execute other services using xml and see what happens!

Step12.md What You Will Learn during this Step: - Spring Initializr - https://start.spring.io - Create a few projects! Exercises - Create more projects with Spring Initializr and play around with it!

Step13.md What You Will Learn during this Step: - Spring Boot Actuator - /env, /metrics, /trace, /dump, /shutdown, /beans, / autoconfig, /configprops, /mappings - HAL Browser - http://localhost:8080/actuator/ - Execute individual REST Services for each of above - Programming Tip - Use static code analysis - https://www.youtube.com/watch?v=rB_BaftN3nE Useful Snippets and References First Snippet <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-rest-hal-browser</artifactId> </dependency> Files List pom.xml Modified New Lines <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> <artifactId>spring-boot-starter-actuator</artifactId> <groupId>org.springframework.data</groupId> <artifactId>spring-data-rest-hal-browser</artifactId> Step14.md What You Will Learn during this Step: - Embedded servlet containers - Default Tomcat - We did not install Tomcat.

Did we? Magic is done by Spring Boot! - Switching to Jetty or Undertow - Configuration - server.port - Programming Tip - Always review code : https://www.youtube.com/watch?v=hVJGu0xdXII Useful Snippets and References First Snippet <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency> Exercises - Find out from documentation (https://docs.spring.io/spring-boot/docs/current/reference/html/howto-embedded-servlet-containers.html) how to switch to undertow!

Files List pom.xml Modified New Lines <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> <artifactId>spring-boot-starter-jetty</artifactId> Step15.md What You Will Learn during this Step: - Using Dynamic Configuration in your application - Customize Welcome Message - Different ways of configuration - –welcome.message=”SomethingElse” in Program Arguments - –spring.config.location=classpath:/default.properties - We will learn about profiles in next step - Using Placeholders - YAML Snippets First Snippet logging: level: org.springframework: DEBUG app: name: In28Minutes description: ${app.name} is your first Spring Boot application welcome: message: Welcome to your first Spring Boot app!

Second Snippet @Value("${welcome.message}") Files List src/main/java/com/in28minutes/springboot/WelcomeService.java Modified New Lines import org.springframework.beans.factory.annotation.Value; //Spring to manage this bean and create an instance of this @Value("${welcome.message}") private String welcomeMessage; return welcomeMessage; package com.in28minutes.springboot; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; //Spring to manage this bean and create an instance of this @Component public class WelcomeService { @Value("${welcome.message}") private String welcomeMessage; public String retrieveWelcomeMessage() { //Complex Method return welcomeMessage; } } src/main/resources/application.properties Modified New Lines logging.level.org.springframework.web.servlet: DEBUG app.name=in28Minutes welcome.message=Welcome message from property file!

Welcome to ${app.name} src/main/resources/application.yaml New logging: level: org.springframework: INFO org.springframework.web.servlet: DEBUG Step16.md What You Will Learn during this Step: - Understand Basics of Profiles - Setting a profile - Using -Dspring.profiles.active=prod in VM Arguments - spring.profiles.active=prod - Using a profile - application-{profile-name}.properties - @Profile(“dev”) on a bean - Usage - Configure Resources - Databases, Queues, External Services Files List src/main/java/com/in28minutes/springboot/Application.java Modified New Lines import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Profile; @Profile("prod") @Bean public String dummy() { return "something"; package com.in28minutes.springboot; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Profile; @SpringBootApplication public class Application { public static void main(String[] args) { ApplicationContext ctx = SpringApplication.run(Application.class, args); } @Profile("prod") @Bean public String dummy() { return "something"; } } src/main/resources/application-dev.properties New logging.level.org.springframework: TRACE src/main/resources/application-prod.properties New logging.level.org.springframework: INFO src/main/resources/application.properties Modified New Lines spring.profiles.active=prod src/main/resources/application.yaml Deleted Step17.md What You Will Learn during this Step: - Even better configuration management than @Value - Type-safe Configuration Properties - http://localhost:8080/dynamic-configuration - Also look at http://localhost:8080/actuator/#http://localhost:8080/configprops Useful Snippets and References First Snippet package com.in28minutes.springboot.configuration; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties("basic") public class BasicConfiguration { private boolean value; private String message; private int number; public boolean isValue() { return value; } public void setValue(boolean value) { this.value = value; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } } Second Snippet @Autowired private BasicConfiguration configuration; @RequestMapping("/dynamic-configuration") public Map dynamicConfiguration() { // Not the best practice to use a map to store differnt types!

Map map = new HashMap(); map.put("message", configuration.getMessage()); map.put("number", configuration.getNumber()); map.put("key", configuration.isValue()); return map; } Third Snippet basic.value: true basic.message: Dynamic Message basic.number: 100 Fourth Snippet basic: value: true message: Dynamic Message YAML number: 100 Exercises Understand Type Safety *************************** APPLICATION FAILED TO START *************************** Description: Binding to target com.in28minutes.springboot.configuration.BasicConfiguration@391b8545 failed: Property: basic.number Value: ABC Reason: Failed to convert property value of type [java.lang.String] to required type [int] for property 'number'; nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [int] Action: Update your application's configuration Files List src/main/java/com/in28minutes/springboot/WelcomeController.java Modified New Lines import java.util.HashMap; import java.util.Map; import com.in28minutes.springboot.configuration.BasicConfiguration; private BasicConfiguration configuration; @RequestMapping("/dynamic-configuration") public Map dynamicConfiguration() { Map map = new HashMap(); map.put("message", configuration.getMessage()); map.put("number", configuration.getNumber()); map.put("value", configuration.isValue()); return map; package com.in28minutes.springboot; import java.util.HashMap; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.in28minutes.springboot.configuration.BasicConfiguration; @RestController public class WelcomeController { //Auto wiring @Autowired private WelcomeService service; @Autowired private BasicConfiguration configuration; @RequestMapping("/welcome") public String welcome() { return service.retrieveWelcomeMessage(); } @RequestMapping("/dynamic-configuration") public Map dynamicConfiguration() { Map map = new HashMap(); map.put("message", configuration.getMessage()); map.put("number", configuration.getNumber()); map.put("value", configuration.isValue()); return map; } } src/main/java/com/in28minutes/springboot/configuration/BasicConfiguration.java New package com.in28minutes.springboot.configuration; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties("basic") public class BasicConfiguration { private boolean value; private String message; private int number; public boolean isValue() { return value; } public void setValue(boolean value) { this.value = value; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } } src/main/resources/application.properties Modified New Lines basic.value=true basic.message=Welcome to in28minutes basic.number=200 Step18.md What You Will Learn during this Step: - Let’s switch back to tomcat first!

Get introduced to Spring Data JPA - Create a very simple example with Spring Data JPA - Use CommandLineRunner!

Some Notes - Useful Properties - spring.datasource.driver-class-name=com.mysql.jdbc.Driver - spring.datasource.url=jdbc:mysql://localhost:3306/test - spring.datasource.username=root - spring.datasource.password=admin - spring.datasource.initialize=true - spring.jpa.hibernate.ddl-auto=update - spring.jpa.show-sql=true spring.datasource.url=jdbc:h2:mem:testdb spring.data.jpa.repositories.bootstrap-mode=default Useful Snippets and References First Snippet - Add H2 Later after showing the error <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> Second Snippet package com.in28minutes.springboot.jpa; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name;// Not perfect!! Should be a proper object! private String role;// Not perfect!! An enum should be a better choice!

protected User() { } public User(String name, String role) { super(); this.name = name; this.role = role; } public Long getId() { return id; } public String getName() { return name; } public String getRole() { return role; } @Override public String toString() { return String.format("User [id=%s, name=%s, role=%s]", id, name, role); } } Third Snippet package com.in28minutes.springboot.jpa; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component public class UserCommandLineRunner implements CommandLineRunner { private static final Logger log = LoggerFactory .getLogger(UserCommandLineRunner.class); @Autowired private UserRepository repository; @Override public void run(String...

args) { // save a couple of customers repository.save(new User("Ranga", "Admin")); repository.save(new User("Ravi", "User")); repository.save(new User("Satish", "Admin")); repository.save(new User("Raghu", "User")); log.info("-------------------------------"); log.info("Finding all users"); log.info("-------------------------------"); for (User user : repository.findAll()) { log.info(user.toString()); } } } Fourth Snippet package com.in28minutes.springboot.jpa; import java.util.List; import org.springframework.data.repository.CrudRepository; public interface UserRepository extends CrudRepository<User, Long> { } Exercises - Look at other methods provided by the UserRepository Files List pom.xml Modified New Lines <artifactId>spring-boot-starter-data-jpa</artifactId> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> src/main/java/com/in28minutes/springboot/jpa/User.java New package com.in28minutes.springboot.jpa; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; private String role; protected User() { } public User(String name, String role) { super(); this.name = name; this.role = role; } public Long getId() { return id; } public String getName() { return name; } public String getRole() { return role; } @Override public String toString() { return "User [id=" + id + ", name=" + name + ", role=" + role + "]"; } } src/main/java/com/in28minutes/springboot/jpa/UserCommandLineRunner.java New package com.in28minutes.springboot.jpa; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component public class UserCommandLineRunner implements CommandLineRunner { private static final Logger log = LoggerFactory .getLogger(UserCommandLineRunner.class); @Autowired private UserRepository repository; @Override public void run(String...

args) throws Exception { repository.save(new User("Ranga", "Admin")); repository.save(new User("Ravi", "User")); repository.save(new User("Satish", "Admin")); repository.save(new User("Raghu", "User")); for (User user : repository.findAll()) { log.info(user.toString()); } log.info("Admin users are....."); log.info("____________________"); for (User user : repository.findByRole("Admin")) { log.info(user.toString()); } } } src/main/java/com/in28minutes/springboot/jpa/UserRepository.java New package com.in28minutes.springboot.jpa; import java.util.List; import org.springframework.data.repository.CrudRepository; public interface UserRepository extends CrudRepository<User, Long> { } Step19.md What You Will Learn during this Step: - Look at H2 Console : http://localhost:8080/h2-console - Use db url jdbc:h2:mem:testdb - Add findByRole method Some Notes - Useful Properties - spring.datasource.driver-class-name=com.mysql.jdbc.Driver - spring.datasource.url=jdbc:mysql://localhost:3306/test - spring.datasource.username=root - spring.datasource.password=admin - spring.datasource.initialize=true - spring.jpa.hibernate.ddl-auto=update - spring.jpa.show-sql=true spring.datasource.url=jdbc:h2:mem:testdb spring.data.jpa.repositories.bootstrap-mode=default Useful Snippets and References First Snippet log.info("-------------------------------"); log.info("Finding user with id 1"); log.info("-------------------------------"); User user = repository.findOne(1L); log.info(user.toString()); log.info("-------------------------------"); log.info("Finding all Admins"); log.info("-------------------------------"); for (User admin : repository.findByRole("Admin")) { log.info(admin.toString()); // Do something...

} Second Snippet package com.in28minutes.springboot.jpa; import java.util.List; import org.springframework.data.repository.CrudRepository; public interface UserRepository extends CrudRepository<User, Long> { List<User> findByRole(String description); } Files List src/main/java/com/in28minutes/springboot/jpa/UserRepository.java Modified New Lines List<User> findByRole(String role); package com.in28minutes.springboot.jpa; import java.util.List; import org.springframework.data.repository.CrudRepository; public interface UserRepository extends CrudRepository<User, Long> { List<User> findByRole(String role); } Step20.md What You Will Learn during this Step: - Introduction to Spring Data Rest - Hit http://localhost:8080/users in POSTMAN - http://localhost:8080/users/1 - http://localhost:8080/users/?size=4 - http://localhost:8080/users/?sort=name,desc - @Param(“role”) - http://localhost:8080/users/search/findByRole?role=Admin - Good for quick prototype! Be cautious about using this in Big applications!

Useful Snippets and References First Snippet <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-rest</artifactId> </dependency> Second Snippet package com.in28minutes.springboot.jpa; import java.util.List; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.springframework.data.rest.core.annotation.RepositoryRestResource; @RepositoryRestResource(collectionResourceRel = "users", path = "users") public interface UserRestRepository extends PagingAndSortingRepository<User, Long> { List<User> findByRole(@Param("role") String role); } Third Snippet : POST to http://localhost:8080/users HEADER Content-Type:application/json BODY { "name": "Raja", "role": "Admin" } Fourth Snippet : POST to http://localhost:8080/users/5 HEADER Content-Type:application/json BODY { "name": "Raja-Updated", "role": "Admin" } Files List pom.xml Modified New Lines <artifactId>spring-boot-starter-data-rest</artifactId> src/main/java/com/in28minutes/springboot/jpa/UserRestRepository.java New package com.in28minutes.springboot.jpa; import java.util.List; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.springframework.data.rest.core.annotation.RepositoryRestResource; @RepositoryRestResource(path = "users", collectionResourceRel = "users") public interface UserRestRepository extends PagingAndSortingRepository<User, Long> { List<User> findByRole(@Param("role") String role); } Step21.md /pom.xml New <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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.in28minutes</groupId> <artifactId>springboot-for-beginners-example</artifactId> <version>0.0.1-SNAPSHOT</version> <name>Your First Spring Boot Example</name> <packaging>jar</packaging> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.0.RELEASE</version> </parent> <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-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-rest</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-rest-hal-browser</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> </dependencies> <properties> <java.version>1.8</java.version> <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version> </properties> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> /src/main/java/com/in28minutes/springboot/Application.java New package com.in28minutes.springboot; import java.util.HashMap; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.in28minutes.springboot.configuration.BasicConfiguration; @SpringBootApplication public class Application { public static void main(String[] args) { ApplicationContext ctx = SpringApplication.run(Application.class, args); } @RestController class SomeBean { @Autowired private SomeDependency someDependency; @Autowired private BasicConfiguration configuration; @RequestMapping("/") public String index() { return someDependency.getSomething(); } @RequestMapping("/dynamic-configuration") public Map dynamicConfiguration() { // Not the best practice to use a map to store differnt types!

Map map = new HashMap(); map.put("message", configuration.getMessage()); map.put("number", configuration.getNumber()); map.put("key", configuration.isValue()); return map; } } @Component class SomeDependency { @Value("${welcome.message}") private String welcomeMessage; public String getSomething() { return welcomeMessage; } } } /src/main/java/com/in28minutes/springboot/configuration/BasicConfiguration.java New package com.in28minutes.springboot.configuration; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties("basic") public class BasicConfiguration { private boolean value; private String message; private int number; public boolean isValue() { return value; } public void setValue(boolean value) { this.value = value; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } } /src/main/java/com/in28minutes/springboot/controller/SurveyController.java New package com.in28minutes.springboot.controller; import java.net.URI; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import com.in28minutes.springboot.model.Question; import com.in28minutes.springboot.service.SurveyService; @RestController class SurveyController { @Autowired private SurveyService surveyService; @GetMapping("/surveys/{surveyId}/questions") public List<Question> retrieveQuestions(@PathVariable String surveyId) { return surveyService.retrieveQuestions(surveyId); } @GetMapping(path = "/surveys/{surveyId}/questions/{questionId}") public Question retrieveQuestion(@PathVariable String surveyId, @PathVariable String questionId) { return surveyService.retrieveQuestion(surveyId, questionId); } @PostMapping("/surveys/{surveyId}/questions") ResponseEntity<?> add(@PathVariable String surveyId, @RequestBody Question question) { Question createdTodo = surveyService.addQuestion(surveyId, question); if (createdTodo == null) { return ResponseEntity.noContent().build(); } URI location = ServletUriComponentsBuilder.fromCurrentRequest() .path("/{id}").buildAndExpand(createdTodo.getId()).toUri(); return ResponseEntity.created(location).build(); } } /src/main/java/com/in28minutes/springboot/jpa/User.java New package com.in28minutes.springboot.jpa; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name;// Not perfect!!

Should be a proper object! private String role;// Not perfect!! An enum should be a better choice!

protected User() { } public User(String name, String role) { super(); this.name = name; this.role = role; } public Long getId() { return id; } public String getName() { return name; } public String getRole() { return role; } @Override public String toString() { return String.format("User [id=%s, name=%s, role=%s]", id, name, role); } } /src/main/java/com/in28minutes/springboot/jpa/UserCommandLineRunner.java New package com.in28minutes.springboot.jpa; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component public class UserCommandLineRunner implements CommandLineRunner { private static final Logger log = LoggerFactory .getLogger(UserCommandLineRunner.class); @Autowired private UserRepository repository; @Override public void run(String...

args) { // save a couple of customers repository.save(new User("Ranga", "Admin")); repository.save(new User("Ravi", "User")); repository.save(new User("Satish", "Admin")); repository.save(new User("Raghu", "User")); log.info("-------------------------------"); log.info("Finding all users"); log.info("-------------------------------"); for (User user : repository.findAll()) { log.info(user.toString()); } log.info("-------------------------------"); log.info("Finding user with id 1"); log.info("-------------------------------"); User user = repository.findOne(1L); log.info(user.toString()); log.info("-------------------------------"); log.info("Finding all Admins"); log.info("-------------------------------"); for (User admin : repository.findByRole("Admin")) { log.info(admin.toString()); // Do something...

} } } /src/main/java/com/in28minutes/springboot/jpa/UserRepository.java New package com.in28minutes.springboot.jpa; import java.util.List; import org.springframework.data.repository.CrudRepository; public interface UserRepository extends CrudRepository<User, Long> { List<User> findByRole(String description); } /src/main/java/com/in28minutes/springboot/jpa/UserRestRepository.java New package com.in28minutes.springboot.jpa; import java.util.List; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.springframework.data.rest.core.annotation.RepositoryRestResource; @RepositoryRestResource(collectionResourceRel = "users", path = "users") public interface UserRestRepository extends PagingAndSortingRepository<User, Long> { List<User> findByRole(@Param("role") String role); } /src/main/java/com/in28minutes/springboot/model/Question.java New package com.in28minutes.springboot.model; import java.util.List; public class Question { private String id; private String description; private String correctAnswer; private List<String> options; // Needed by Caused by: com.fasterxml.jackson.databind.JsonMappingException: // Can not construct instance of com.in28minutes.springboot.model.Question: // no suitable constructor found, can not deserialize from Object value // (missing default constructor or creator, or perhaps need to add/enable // type information?) public Question() { } public Question(String id, String description, String correctAnswer, List<String> options) { super(); this.id = id; this.description = description; this.correctAnswer = correctAnswer; this.options = options; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getDescription() { return description; } public String getCorrectAnswer() { return correctAnswer; } public List<String> getOptions() { return options; } @Override public String toString() { return String .format("Question [id=%s, description=%s, correctAnswer=%s, options=%s]", id, description, correctAnswer, options); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (id == null ?

0 : id.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Question other = (Question) obj; if (id == null) { if (other.id != null) { return false; } } else if (!id.equals(other.id)) { return false; } return true; } } /src/main/java/com/in28minutes/springboot/model/Survey.java New package com.in28minutes.springboot.model; import java.util.List; public class Survey { private String id; private String title; private String description; private List<Question> questions; public Survey(String id, String title, String description, List<Question> questions) { super(); this.id = id; this.title = title; this.description = description; this.questions = questions; } public String getId() { return id; } public String getTitle() { return title; } public String getDescription() { return description; } public List<Question> getQuestions() { return questions; } @Override public String toString() { return String.format( "Survey [id=%s, title=%s, description=%s, questions=%s]", id, title, description, questions); } } /src/main/java/com/in28minutes/springboot/service/SurveyService.java New package com.in28minutes.springboot.service; import java.math.BigInteger; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.springframework.stereotype.Component; import com.in28minutes.springboot.model.Question; import com.in28minutes.springboot.model.Survey; @Component public class SurveyService { private static List<Survey> surveys = new ArrayList<>(); static { Question question1 = new Question("Question1", "Largest Country in the World", "Russia", Arrays.asList( "India", "Russia", "United States", "China")); Question question2 = new Question("Question2", "Most Populus Country in the World", "China", Arrays.asList( "India", "Russia", "United States", "China")); Question question3 = new Question("Question3", "Highest GDP in the World", "United States", Arrays.asList( "India", "Russia", "United States", "China")); Question question4 = new Question("Question4", "Second largest english speaking country", "India", Arrays.asList("India", "Russia", "United States", "China")); List<Question> questions = new ArrayList<>(Arrays.asList(question1, question2, question3, question4)); Survey survey = new Survey("Survey1", "My Favorite Survey", "Description of the Survey", questions); surveys.add(survey); } public List<Survey> retrieveAllSurveys() { return surveys; } public Survey retrieveSurvey(String surveyId) { for (Survey survey : surveys) { if (survey.getId().equals(surveyId)) { return survey; } } return null; } public List<Question> retrieveQuestions(String surveyId) { Survey survey = retrieveSurvey(surveyId); if (survey == null) { return null; } return survey.getQuestions(); } public Question retrieveQuestion(String surveyId, String questionId) { Survey survey = retrieveSurvey(surveyId); if (survey == null) { return null; } for (Question question : survey.getQuestions()) { if (question.getId().equals(questionId)) { return question; } } return null; } private SecureRandom random = new SecureRandom(); public Question addQuestion(String surveyId, Question question) { Survey survey = retrieveSurvey(surveyId); if (survey == null) { return null; } String randomId = new BigInteger(130, random).toString(32); question.setId(randomId); survey.getQuestions().add(question); return question; } } /src/main/resources/application.properties New logging.level.org.springframework: INFO app.name: In28Minutes app.description: ${app.name} is your first Spring Boot application Properties welcome.message: Welcome to your first Spring Boot application basic.value: true basic.message: Dynamic Message basic.number: 123 /src/main/resources/application.yaml New logging: level: org.springframework: DEBUG app: name: In28Minutes description: ${app.name} is your first Spring Boot application welcome: message: Welcome to your first Spring Boot app!

basic: value: true message: Dynamic Message YAML number: 100 /src/test/java/com/in28minutes/springboot/controller/SurveyControllerIT.java New package com.in28minutes.springboot.controller; import java.util.Arrays; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.skyscreamer.jsonassert.JSONAssert; import org.springframework.boot.context.embedded.LocalServerPort; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.test.context.junit4.SpringRunner; import com.in28minutes.springboot.Application; @RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class SurveyControllerIT { @LocalServerPort private int port; private TestRestTemplate template = new TestRestTemplate(); HttpHeaders headers = new HttpHeaders(); @Before public void setupJSONAcceptType() { headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); } @Test public void retrieveSurveyQuestion() throws Exception { String expected = "{id:Question1,description:Largest Country in the World,correctAnswer:Russia,options:[India,Russia,United States,China]}"; ResponseEntity<String> response = template.exchange( createUrl("/surveys/Survey1/questions/Question1"), HttpMethod.GET, new HttpEntity<String>("DUMMY_DOESNT_MATTER", headers), String.class); JSONAssert.assertEquals(expected, response.getBody(), false); } private String createUrl(String uri) { return "http://localhost:" + port + uri; } } What You Will Learn during this Step: - First thing first : I hate the fact that we did not write tests until Step 20 of this course - I love TDD and use it in all my projects.

However, when learning something new, I think it is important to focus on one thing at a time!

You can learn more about unit testing here - https://www.youtube.com/playlist?list=PL83C941BB0D27A6AF - Let’s write a Integration Test for our service Useful Snippets and References First Snippet <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> UnRefactored Snippet package com.in28minutes.springboot.controller; import java.util.Arrays; import org.junit.Test; import org.junit.runner.RunWith; import org.skyscreamer.jsonassert.JSONAssert; import org.springframework.boot.context.embedded.LocalServerPort; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.test.context.junit4.SpringRunner; import com.in28minutes.springboot.Application; @RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class SurveyControllerIT { @LocalServerPort private int port; @Test public void testRetrieveSurveyQuestion() { String url = "http://localhost:" + port + "/surveys/Survey1/questions/Question1"; TestRestTemplate restTemplate = new TestRestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); HttpEntity<String> entity = new HttpEntity<String>(null, headers); ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); String expected = "{id:Question1,description:Largest Country in the World,correctAnswer:Russia}"; JSONAssert.assertEquals(expected, response.getBody(), false); } } Refactored Snippet package com.in28minutes.springboot.controller; import java.util.Arrays; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.skyscreamer.jsonassert.JSONAssert; import org.springframework.boot.context.embedded.LocalServerPort; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.test.context.junit4.SpringRunner; import com.in28minutes.springboot.Application; @RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class SurveyControllerIT { @LocalServerPort private int port; private TestRestTemplate template = new TestRestTemplate(); HttpHeaders headers = new HttpHeaders(); @Before public void setupJSONAcceptType() { headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); } @Test public void retrieveSurveyQuestion() throws Exception { String expected = "{id:Question1,description:Largest Country in the World,correctAnswer:Russia,options:[India,Russia,United States,China]}"; ResponseEntity<String> response = template.exchange( createUrl("/surveys/Survey1/questions/Question1"), HttpMethod.GET, new HttpEntity<String>("DUMMY_DOESNT_MATTER", headers), String.class); JSONAssert.assertEquals(expected, response.getBody(), false); } private String createUrl(String uri) { return "http://localhost:" + port + uri; } } Exercises - Try writing integration test for retrieveAllQuestions Files List pom.xml Deleted src/main/java/com/in28minutes/springboot/Application.java Deleted src/main/java/com/in28minutes/springboot/WelcomeController.java Deleted src/main/java/com/in28minutes/springboot/WelcomeService.java Deleted src/main/java/com/in28minutes/springboot/configuration/BasicConfiguration.java Deleted src/main/java/com/in28minutes/springboot/controller/SurveyController.java Deleted src/main/java/com/in28minutes/springboot/jpa/User.java Deleted src/main/java/com/in28minutes/springboot/jpa/UserCommandLineRunner.java Deleted src/main/java/com/in28minutes/springboot/jpa/UserRepository.java Deleted src/main/java/com/in28minutes/springboot/jpa/UserRestRepository.java Deleted src/main/java/com/in28minutes/springboot/model/Question.java Deleted src/main/java/com/in28minutes/springboot/model/Survey.java Deleted src/main/java/com/in28minutes/springboot/service/SurveyService.java Deleted src/main/resources/application-dev.properties Deleted src/main/resources/application-prod.properties Deleted src/main/resources/application.properties Deleted Step22.md /pom.xml Deleted /src/main/java/com/in28minutes/springboot/Application.java Deleted /src/main/java/com/in28minutes/springboot/configuration/BasicConfiguration.java Deleted /src/main/java/com/in28minutes/springboot/controller/SurveyController.java Deleted /src/main/java/com/in28minutes/springboot/jpa/User.java Deleted /src/main/java/com/in28minutes/springboot/jpa/UserCommandLineRunner.java Deleted /src/main/java/com/in28minutes/springboot/jpa/UserRepository.java Deleted /src/main/java/com/in28minutes/springboot/jpa/UserRestRepository.java Deleted /src/main/java/com/in28minutes/springboot/model/Question.java Deleted /src/main/java/com/in28minutes/springboot/model/Survey.java Deleted /src/main/java/com/in28minutes/springboot/service/SurveyService.java Deleted /src/main/resources/application.properties Deleted /src/main/resources/application.yaml Deleted /src/test/java/com/in28minutes/springboot/controller/SurveyControllerIT.java Deleted What You Will Learn during this Step: - Exercise from previous step - Integration Test for POST Request - Add To do Useful Snippets and References First Snippet @Test public void retrieveSurveyQuestions() throws Exception { ResponseEntity<List<Question>> response = template.exchange( createUrl("/surveys/Survey1/questions/"), HttpMethod.GET, new HttpEntity<String>("DUMMY_DOESNT_MATTER", headers), new ParameterizedTypeReference<List<Question>>() { }); Question sampleQuestion = new Question("Question1", "Largest Country in the World", "Russia", Arrays.asList( "India", "Russia", "United States", "China")); assertTrue(response.getBody().contains(sampleQuestion)); } Before Refactoring Snippet //NEEDS REFACTORING @Test public void retrieveAllSurveyQuestions() throws Exception { String url = "http://localhost:" + port + "/surveys/Survey1/questions"; TestRestTemplate restTemplate = new TestRestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); ResponseEntity<List<Question>> response = restTemplate.exchange(url, HttpMethod.GET, new HttpEntity<String>("DUMMY_DOESNT_MATTER", headers), new ParameterizedTypeReference<List<Question>>() { }); Question sampleQuestion = new Question("Question1", "Largest Country in the World", "Russia", Arrays.asList( "India", "Russia", "United States", "China")); assertTrue(response.getBody().contains(sampleQuestion)); } After Refactoring Snippet - We will discuss this in Step 23 @Test public void createSurveyQuestion() throws Exception { Question question = new Question("DOESN'T MATTER", "Smallest Number", "1", Arrays.asList("1", "2", "3", "4")); ResponseEntity<String> response = template.exchange( createUrl("/surveys/Survey1/questions/"), HttpMethod.POST, new HttpEntity<Question>(question, headers), String.class); assertThat(response.getHeaders().get(HttpHeaders.LOCATION).get(0), containsString("/surveys/Survey1/questions/")); } Files List pom.xml New <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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.in28minutes.springboot</groupId> <artifactId>first-springboot-project</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.0.RELEASE</version> </parent> <properties> <java.version>1.8</java.version> <maven-jar-plugin.version>3.1.1</maven-jar-plugin.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-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-rest</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</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-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-rest-hal-browser</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> src/main/java/com/in28minutes/springboot/Application.java New package com.in28minutes.springboot; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Profile; @SpringBootApplication public class Application { public static void main(String[] args) { ApplicationContext ctx = SpringApplication.run(Application.class, args); } @Profile("prod") @Bean public String dummy() { return "something"; } } src/main/java/com/in28minutes/springboot/WelcomeController.java New package com.in28minutes.springboot; import java.util.HashMap; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.in28minutes.springboot.configuration.BasicConfiguration; @RestController public class WelcomeController { //Auto wiring @Autowired private WelcomeService service; @Autowired private BasicConfiguration configuration; @RequestMapping("/welcome") public String welcome() { return service.retrieveWelcomeMessage(); } @RequestMapping("/dynamic-configuration") public Map dynamicConfiguration() { Map map = new HashMap(); map.put("message", configuration.getMessage()); map.put("number", configuration.getNumber()); map.put("value", configuration.isValue()); return map; } } src/main/java/com/in28minutes/springboot/WelcomeService.java New package com.in28minutes.springboot; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; //Spring to manage this bean and create an instance of this @Component public class WelcomeService { @Value("${welcome.message}") private String welcomeMessage; public String retrieveWelcomeMessage() { //Complex Method return welcomeMessage; } } src/main/java/com/in28minutes/springboot/configuration/BasicConfiguration.java New package com.in28minutes.springboot.configuration; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties("basic") public class BasicConfiguration { private boolean value; private String message; private int number; public boolean isValue() { return value; } public void setValue(boolean value) { this.value = value; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } } src/main/java/com/in28minutes/springboot/controller/SurveyController.java New package com.in28minutes.springboot.controller; import java.net.URI; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import com.in28minutes.springboot.model.Question; import com.in28minutes.springboot.service.SurveyService; @RestController class SurveyController { @Autowired private SurveyService surveyService; @GetMapping("/surveys/{surveyId}/questions") public List<Question> retrieveQuestions(@PathVariable String surveyId) { return surveyService.retrieveQuestions(surveyId); } // GET "/surveys/{surveyId}/questions/{questionId}" @GetMapping("/surveys/{surveyId}/questions/{questionId}") public Question retrieveDetailsForQuestion(@PathVariable String surveyId, @PathVariable String questionId) { return surveyService.retrieveQuestion(surveyId, questionId); } // /surveys/{surveyId}/questions @PostMapping("/surveys/{surveyId}/questions") public ResponseEntity<Void> addQuestionToSurvey( @PathVariable String surveyId, @RequestBody Question newQuestion) { Question question = surveyService.addQuestion(surveyId, newQuestion); if (question == null) return ResponseEntity.noContent().build(); // Success - URI of the new resource in Response Header // Status - created // URI -> /surveys/{surveyId}/questions/{questionId} // question.getQuestionId() URI location = ServletUriComponentsBuilder.fromCurrentRequest().path( "/{id}").buildAndExpand(question.getId()).toUri(); // Status return ResponseEntity.created(location).build(); } } src/main/java/com/in28minutes/springboot/jpa/User.java New package com.in28minutes.springboot.jpa; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; private String role; protected User() { } public User(String name, String role) { super(); this.name = name; this.role = role; } public Long getId() { return id; } public String getName() { return name; } public String getRole() { return role; } @Override public String toString() { return "User [id=" + id + ", name=" + name + ", role=" + role + "]"; } } src/main/java/com/in28minutes/springboot/jpa/UserCommandLineRunner.java New package com.in28minutes.springboot.jpa; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component public class UserCommandLineRunner implements CommandLineRunner { private static final Logger log = LoggerFactory .getLogger(UserCommandLineRunner.class); @Autowired private UserRepository repository; @Override public void run(String...

args) throws Exception { repository.save(new User("Ranga", "Admin")); repository.save(new User("Ravi", "User")); repository.save(new User("Satish", "Admin")); repository.save(new User("Raghu", "User")); for (User user : repository.findAll()) { log.info(user.toString()); } log.info("Admin users are....."); log.info("____________________"); for (User user : repository.findByRole("Admin")) { log.info(user.toString()); } } } src/main/java/com/in28minutes/springboot/jpa/UserRepository.java New package com.in28minutes.springboot.jpa; import java.util.List; import org.springframework.data.repository.CrudRepository; public interface UserRepository extends CrudRepository<User, Long> { List<User> findByRole(String role); } src/main/java/com/in28minutes/springboot/jpa/UserRestRepository.java New package com.in28minutes.springboot.jpa; import java.util.List; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.springframework.data.rest.core.annotation.RepositoryRestResource; @RepositoryRestResource(path = "users", collectionResourceRel = "users") public interface UserRestRepository extends PagingAndSortingRepository<User, Long> { List<User> findByRole(@Param("role") String role); } src/main/java/com/in28minutes/springboot/model/Question.java New package com.in28minutes.springboot.model; import java.util.List; public class Question { private String id; private String description; private String correctAnswer; private List<String> options; // Needed by Caused by: com.fasterxml.jackson.databind.JsonMappingException: // Can not construct instance of com.in28minutes.springboot.model.Question: // no suitable constructor found, can not deserialize from Object value // (missing default constructor or creator, or perhaps need to add/enable // type information?) public Question() { } public Question(String id, String description, String correctAnswer, List<String> options) { super(); this.id = id; this.description = description; this.correctAnswer = correctAnswer; this.options = options; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getDescription() { return description; } public String getCorrectAnswer() { return correctAnswer; } public List<String> getOptions() { return options; } @Override public String toString() { return String .format("Question [id=%s, description=%s, correctAnswer=%s, options=%s]", id, description, correctAnswer, options); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((id == null) ?

0 : id.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Question other = (Question) obj; if (id == null) { if (other.id != null) return false; } else if (!id.equals(other.id)) return false; return true; } } src/main/java/com/in28minutes/springboot/model/Survey.java New package com.in28minutes.springboot.model; import java.util.List; public class Survey { private String id; private String title; private String description; private List<Question> questions; public Survey(String id, String title, String description, List<Question> questions) { super(); this.id = id; this.title = title; this.description = description; this.questions = questions; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public List<Question> getQuestions() { return questions; } public void setQuestions(List<Question> questions) { this.questions = questions; } @Override public String toString() { return "Survey [id=" + id + ", title=" + title + ", description=" + description + ", questions=" + questions + "]"; } } src/main/java/com/in28minutes/springboot/service/SurveyService.java New package com.in28minutes.springboot.service; import java.math.BigInteger; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.springframework.stereotype.Component; import com.in28minutes.springboot.model.Question; import com.in28minutes.springboot.model.Survey; @Component public class SurveyService { private static List<Survey> surveys = new ArrayList<>(); static { Question question1 = new Question("Question1", "Largest Country in the World", "Russia", Arrays.asList( "India", "Russia", "United States", "China")); Question question2 = new Question("Question2", "Most Populus Country in the World", "China", Arrays.asList( "India", "Russia", "United States", "China")); Question question3 = new Question("Question3", "Highest GDP in the World", "United States", Arrays.asList( "India", "Russia", "United States", "China")); Question question4 = new Question("Question4", "Second largest english speaking country", "India", Arrays .asList("India", "Russia", "United States", "China")); List<Question> questions = new ArrayList<>(Arrays.asList(question1, question2, question3, question4)); Survey survey = new Survey("Survey1", "My Favorite Survey", "Description of the Survey", questions); surveys.add(survey); } public List<Survey> retrieveAllSurveys() { return surveys; } public Survey retrieveSurvey(String surveyId) { for (Survey survey : surveys) { if (survey.getId().equals(surveyId)) { return survey; } } return null; } public List<Question> retrieveQuestions(String surveyId) { Survey survey = retrieveSurvey(surveyId); if (survey == null) { return null; } return survey.getQuestions(); } public Question retrieveQuestion(String surveyId, String questionId) { Survey survey = retrieveSurvey(surveyId); if (survey == null) { return null; } for (Question question : survey.getQuestions()) { if (question.getId().equals(questionId)) { return question; } } return null; } private SecureRandom random = new SecureRandom(); public Question addQuestion(String surveyId, Question question) { Survey survey = retrieveSurvey(surveyId); if (survey == null) { return null; } String randomId = new BigInteger(130, random).toString(32); question.setId(randomId); survey.getQuestions().add(question); return question; } } src/main/resources/application-dev.properties New logging.level.org.springframework: TRACE src/main/resources/application-prod.properties New logging.level.org.springframework: INFO src/main/resources/application.properties New logging.level.org.springframework: DEBUG app.name=in28Minutes welcome.message=Welcome message from property file!

Welcome to ${app.name} basic.value=true basic.message=Welcome to in28minutes basic.number=200 src/test/java/com/in28minutes/springboot/controller/SurveyControllerIT.java New package com.in28minutes.springboot.controller; import static org.junit.Assert.assertTrue; import java.util.Arrays; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.skyscreamer.jsonassert.JSONAssert; import org.springframework.boot.context.embedded.LocalServerPort; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.test.context.junit4.SpringRunner; import com.in28minutes.springboot.Application; import com.in28minutes.springboot.model.Question; @RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class SurveyControllerIT { @LocalServerPort private int port; //NEEDS REFACTORING @Test public void testRetrieveSurveyQuestion() { String url = "http://localhost:" + port + "/surveys/Survey1/questions/Question1"; TestRestTemplate restTemplate = new TestRestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); HttpEntity<String> entity = new HttpEntity<String>(null, headers); ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); String expected = "{id:Question1,description:Largest Country in the World,correctAnswer:Russia}"; JSONAssert.assertEquals(expected, response.getBody(), false); } //NEEDS REFACTORING @Test public void retrieveAllSurveyQuestions() throws Exception { String url = "http://localhost:" + port + "/surveys/Survey1/questions"; TestRestTemplate restTemplate = new TestRestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); ResponseEntity<List<Question>> response = restTemplate.exchange(url, HttpMethod.GET, new HttpEntity<String>("DUMMY_DOESNT_MATTER", headers), new ParameterizedTypeReference<List<Question>>() { }); Question sampleQuestion = new Question("Question1", "Largest Country in the World", "Russia", Arrays.asList( "India", "Russia", "United States", "China")); assertTrue(response.getBody().contains(sampleQuestion)); } //NEEDS REFACTORING @Test public void addQuestion() { String url = "http://localhost:" + port + "/surveys/Survey1/questions"; TestRestTemplate restTemplate = new TestRestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); Question question = new Question("DOESNTMATTER", "Question1", "Russia", Arrays.asList("India", "Russia", "United States", "China")); HttpEntity entity = new HttpEntity<Question>(question, headers); ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class); String actual = response.getHeaders().get(HttpHeaders.LOCATION).get(0); assertTrue(actual.contains("/surveys/Survey1/questions/")); } } Step23.md What You Will Learn during this Step: - Lets do some cleanup - Lets Refactor the SurveyControllerIT.java Exercises - Test and make sure everything is working fine Files List src/test/java/com/in28minutes/springboot/controller/SurveyControllerIT.java package com.in28minutes.springboot.controller; import static org.junit.Assert.assertTrue; import java.util.Arrays; import java.util.List; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.skyscreamer.jsonassert.JSONAssert; import org.springframework.boot.context.embedded.LocalServerPort; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.test.context.junit4.SpringRunner; import com.in28minutes.springboot.Application; import com.in28minutes.springboot.model.Question; @RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class SurveyControllerIT { @LocalServerPort private int port; TestRestTemplate restTemplate = new TestRestTemplate(); HttpHeaders headers = new HttpHeaders(); @Before public void before() { headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); } @Test public void testRetrieveSurveyQuestion() { HttpEntity<String> entity = new HttpEntity<String>(null, headers); ResponseEntity<String> response = restTemplate.exchange( createURLWithPort("/surveys/Survey1/questions/Question1"), HttpMethod.GET, entity, String.class); String expected = "{id:Question1,description:Largest Country in the World,correctAnswer:Russia}"; JSONAssert.assertEquals(expected, response.getBody(), false); } @Test public void retrieveAllSurveyQuestions() throws Exception { ResponseEntity<List<Question>> response = restTemplate.exchange( createURLWithPort("/surveys/Survey1/questions"), HttpMethod.GET, new HttpEntity<String>("DUMMY_DOESNT_MATTER", headers), new ParameterizedTypeReference<List<Question>>() { }); Question sampleQuestion = new Question("Question1", "Largest Country in the World", "Russia", Arrays.asList( "India", "Russia", "United States", "China")); assertTrue(response.getBody().contains(sampleQuestion)); } @Test public void addQuestion() { Question question = new Question("DOESNTMATTER", "Question1", "Russia", Arrays.asList("India", "Russia", "United States", "China")); HttpEntity entity = new HttpEntity<Question>(question, headers); ResponseEntity<String> response = restTemplate.exchange( createURLWithPort("/surveys/Survey1/questions"), HttpMethod.POST, entity, String.class); String actual = response.getHeaders().get(HttpHeaders.LOCATION).get(0); assertTrue(actual.contains("/surveys/Survey1/questions/")); } private String createURLWithPort(final String uri) { return "http://localhost:" + port + uri; } } Step24.md What You Will Learn during this Step: - Write a Unit Test for retrieving a specific question from a survey.

Different between Unit Test and Integration Test - Basics of Mocking - MockMvc framework - @MockBean Programming Tip - Become an expert at Mockito - https://courses.in28minutes.com/p/mockito-for-beginner-in-5-steps Exercises - Write unit test for retrieve all questions for a survey Files List src/test/java/com/in28minutes/springboot/controller/SurveyControllerTest.java New package com.in28minutes.springboot.controller; import java.util.Arrays; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.skyscreamer.jsonassert.JSONAssert; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.RequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import com.in28minutes.springboot.model.Question; import com.in28minutes.springboot.service.SurveyService; @RunWith(SpringRunner.class) @WebMvcTest(value = SurveyController.class) public class SurveyControllerTest { @Autowired private MockMvc mockMvc; // Mock @Autowired @MockBean private SurveyService surveyService; @Test public void retrieveDetailsForQuestion() throws Exception { Question mockQuestion = new Question("Question1", "Largest Country in the World", "Russia", Arrays.asList( "India", "Russia", "United States", "China")); Mockito.when( surveyService.retrieveQuestion(Mockito.anyString(), Mockito .anyString())).thenReturn(mockQuestion); RequestBuilder requestBuilder = MockMvcRequestBuilders.get( "/surveys/Survey1/questions/Question1").accept( MediaType.APPLICATION_JSON); MvcResult result = mockMvc.perform(requestBuilder).andReturn(); String expected = "{id:Question1,description:Largest Country in the World,correctAnswer:Russia}"; JSONAssert.assertEquals(expected, result.getResponse() .getContentAsString(), false); // Assert } } Step25.md What You Will Learn during this Step: - Exercise from previous step - Unit test for createTodo Files List src/test/java/com/in28minutes/springboot/controller/SurveyControllerTest.java Modified @Test public void createSurveyQuestion() throws Exception { Question mockQuestion = new Question("1", "Smallest Number", "1", Arrays.asList("1", "2", "3", "4")); String questionJson = "{\"description\":\"Smallest Number\",\"correctAnswer\":\"1\",\"options\":[\"1\",\"2\",\"3\",\"4\"]}"; //surveyService.addQuestion to respond back with mockQuestion Mockito.when( surveyService.addQuestion(Mockito.anyString(), Mockito .any(Question.class))).thenReturn(mockQuestion); //Send question as body to /surveys/Survey1/questions RequestBuilder requestBuilder = MockMvcRequestBuilders.post( "/surveys/Survey1/questions") .accept(MediaType.APPLICATION_JSON).content(questionJson) .contentType(MediaType.APPLICATION_JSON); MvcResult result = mockMvc.perform(requestBuilder).andReturn(); MockHttpServletResponse response = result.getResponse(); assertEquals(HttpStatus.CREATED.value(), response.getStatus()); assertEquals("http://localhost/surveys/Survey1/questions/1", response .getHeader(HttpHeaders.LOCATION)); } Step26.md What You Will Learn during this Step: - Securing our services with Basic Authentication using Spring Security - Executing Requests using Basic Authentication with Postman - default user name is user - default security password is printed in console Useful Snippets and References First Snippet <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> Second Snippet Using default security password: Third Snippet : Executing a GET to http://localhost:8080/surveys/Survey1/questions/ { "timestamp": 1483514297025, "status": 401, "error": "Unauthorized", "message": "Full authentication is required to access this resource", "path": "/surveys/Survey1/questions/" } Step27.md What You Will Learn during this Step: - Configure different user roles for survey and other services - Update integration tests - Update unit tests Files List pom.xml Modified New Lines <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> src/main/java/com/in28minutes/springboot/security/SecurityConfig.java New package com.in28minutes.springboot.security; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { // Authentication : User --> Roles protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().passwordEncoder(org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance()).withUser("user1").password("secret1") .roles("USER").and().withUser("admin1").password("secret1") .roles("USER", "ADMIN"); } // Authorization : Role -> Access // survey -> USER protected void configure(HttpSecurity http) throws Exception { http.httpBasic().and().authorizeRequests().antMatchers("/surveys/**") .hasRole("USER").antMatchers("/users/**").hasRole("USER") .antMatchers("/**").hasRole("ADMIN").and().csrf().disable() .headers().frameOptions().disable(); } } src/test/java/com/in28minutes/springboot/controller/SurveyControllerIT.java Modified New Lines @RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class SurveyControllerIT { //Other Code HttpHeaders headers = new HttpHeaders(); @Before public void before() { headers.add("Authorization", createHttpAuthenticationHeaderValue( "user1", "secret1")); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); } //Other Code private String createHttpAuthenticationHeaderValue(String userId, String password) { String auth = userId + ":" + password; byte[] encodedAuth = Base64.encode(auth.getBytes(Charset .forName("US-ASCII"))); String headerValue = "Basic " + new String(encodedAuth); return headerValue; } } src/test/java/com/in28minutes/springboot/controller/SurveyControllerTest.java Modified @WebMvcTest(value = SurveyController.class, secure = false) Step28.md What You Will Learn during this Step: - A Deep Dive into Autoconfiguration - spring-boot-autoconfigure-1.4.0.RELEASE.jar - /META-INF/spring.factories - Package org.springframework.boot.autoconfigure - Lets look at the log in debug mode!

Examples - JdbcTemplateAutoConfiguration - HttpMessageConvertersAutoConfiguration - Programming Tips - Understand Design Patterns - https://www.youtube.com/watch?v=Vp7q_pE7Fzg - Understand Modern Development Practices - https://www.youtube.com/watch?v=0Kqzfyp-w4s Useful Snippets and References String[] beanNames = ctx.getBeanDefinitionNames(); Arrays.sort(beanNames); for (String beanName : beanNames) { System.out.println(beanName); }

People Also Asked

Tutorials :: Spring Boot?

What you will learn You will build: - A web application to manage your to-dos - A basic REST service to manage survey questions This course is a perfect first step into Spring Boot. You will learn Spring Boot step by step — in more than 90 hands-on steps.

Spring Boot Tutorial - GeeksforGeeks?

You will work with: - REST services - Spring (dependency management) - Spring MVC - Spring Boot and Spring Boot Starter projects - Spring Security (authentication & authorization) - Bootstrap (UI styling) - Maven (dependency management) - Eclipse (IDE) and embedded Tomcat (web server) We’ll help you set up each of these.

Spring Boot Tutorial - Online Tutorials Library?

Topics covered - Basics of Spring Boot - Auto-configuration and Spring Boot “magic” - Spring Boot Starter projects and Spring Initializr - DispatcherServlet and request flow - Todo management application with login/logout - Models, Controllers, ViewResolver, and Filters - Forms: data binding and validation - Annotation-based handling: @RequestParam ,@ModelAttribute ,@SessionAttributes , etc.

Spring Boot Tutorial for Beginners: Step-by-Step Guide | Coding Shuttle?

For help: Installation Guide (Video Playlist) Spring Level 1 to Level 6 — Section Overview Here’s a quick overview of the different sections of the course: - Level 1: Introduction to Spring in 10 Steps - Level 2: Develop a Todo Management Web Application in 30 Steps - Level 3: Introduction to Unit Testing with JUnit in 5 Steps - Level 4: Introduction to Mocking with Mockito in 5 Steps - Level 5: A...

Spring Boot For Beginners in 100 Steps - Spring Boot Tutorial?

Introduction to JUnit — 5 steps - Introduction to Mockito — 5 steps - Introduction to JPA — 10 steps - Introduction to Spring — 10 steps Web Application with Spring Boot - Step 01 — Part 1: Basic Spring Boot web application setup - Step 01 — Part 2: pom.xml , Spring Boot application, andapplication.properties - Step 02 — Part 1: First Spring MVC controller — @ResponseBody vs@Controller - Step 02 —...