Mini Todo Management Project using Spring Boot + Spring MVC + Spring Security + JSP + Hibernate + MySQL

In this article, we will learn how to develop Spring MVC Todo Management web application using Spring Boot, Spring MVC, Spring Security, JSP, JPA and MySQL as a database.

This mini project is mainly for beginners to learn how to develop Spring MVC web applications step by step using Spring Boot.

The following features are implemented in this mini todo management project:

You can implement the following features to this Todo Management project(take this is an exercise):

Refer below article to implement logging effectively in this Todo management project Spring Boot 2 Logging Spring Boot 2 Logging SLF4j Logback and LOG4j2 Example

Refer below article to implement role-based Spring security effectively in this Todo management project Spring Boot + Spring MVC + Role Based Spring Security + JPA + Thymeleaf + MySQL Tutorial

Refer below article to implement exception handling effectively in this Todo management project Spring Boot 2 Exception Handling for REST APIs

Refer below article to implement validation effectively in this Todo management project Spring Boot CRUD REST APIs Validation Example

Refer below article to implement auditing effectively in this Todo management project Spring Data JPA Auditing with Spring Boot 2 and MySQL Example

What we’ll build

We will develop step by step Spring MVC Todo management web application using Spring Boot, Spring MVC, Spring Security, JSP, JPA and MySQL as a database.

Tools and Technologies Used

Creating and Importing a Project

There are many ways to create a Spring Boot application. The simplest way is to use Spring Initializr at http://start.spring.io/, which is an online Spring Boot application generator.

Look at the above diagram, we have specified the following details:

Once, all the details are entered, click on Generate Project button will generate a spring boot project and downloads it. Next, Unzip the downloaded zip file and import it into your favorite IDE.

Packaging Structure

Following is the packing structure for your reference -

The pom.xml File

    <?xml version="1.0" encoding="UTF-8"?>
    <project
        xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>net.guides.springboot</groupId>
        <artifactId>todo-management-spring-boot</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>jar</packaging>
        <name>todo-management-spring-boot</name>
        <description>Demo project for Spring Boot</description>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.4.RELEASE</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>1.8</java.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>jstl</artifactId>
            </dependency>
            <dependency>
                <groupId>org.webjars</groupId>
                <artifactId>bootstrap</artifactId>
                <version>3.3.6</version>
            </dependency>
            <dependency>
                <groupId>org.webjars</groupId>
                <artifactId>bootstrap-datepicker</artifactId>
                <version>1.0.1</version>
            </dependency>
            <dependency>
                <groupId>org.webjars</groupId>
                <artifactId>jquery</artifactId>
                <version>1.9.1</version>
            </dependency>
            <dependency>
                <groupId>org.apache.tomcat.embed</groupId>
                <artifactId>tomcat-embed-jasper</artifactId>
                <scope>provided</scope>
            </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-security</artifactId>
            </dependency>
            <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>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-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>
    

Note that we have used Spring Data JPA starter to talk to MySQL database.

Create the JPA Entity - Todo.java

package net.guides.springboot.todomanagement.model;

    import java.util.Date;
    
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.Table;
    import javax.validation.constraints.Size;
    
    /**
     * @author Ramesh Fadatare
     *
     */
    
    @Entity
    @Table(name = "todos")
    public class Todo {
    
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private long id;
    
        private String userName;
    
        @Size(min = 10, message = "Enter at least 10 Characters...")
        private String description;
    
        private Date targetDate;
    
        public Todo() {
            super();
        }
    
        public Todo(String user, String desc, Date targetDate, boolean isDone) {
            super();
            this.userName = user;
            this.description = desc;
            this.targetDate = targetDate;
        }
    
        public long getId() {
            return id;
        }
    
        public void setId(long id) {
            this.id = id;
        }
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    
        public String getDescription() {
            return description;
        }
    
        public void setDescription(String description) {
            this.description = description;
        }
    
        public Date getTargetDate() {
            return targetDate;
        }
    
        public void setTargetDate(Date targetDate) {
            this.targetDate = targetDate;
        }
    }

Next, create the Spring Data JPA repository for the Todo entity.

Spring Data JPA Repository Interface - TodoRepository.java

package net.guides.springboot.todomanagement.repository;

    import java.util.List;
    
    import org.springframework.data.jpa.repository.JpaRepository;
    
    import net.guides.springboot.todomanagement.model.Todo;
    
    public interface TodoRepository extends JpaRepository < Todo, Long > {
        List < Todo > findByUserName(String user);
    }

Spring Security Configuration - SecurityConfiguration.java

package net.guides.springboot.todomanagement.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;
    import org.springframework.security.crypto.password.NoOpPasswordEncoder;
    
    @Configuration
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
        @Autowired
        public void configureGlobalSecurity(AuthenticationManagerBuilder auth)
        throws Exception {
            auth.inMemoryAuthentication()
                .passwordEncoder(NoOpPasswordEncoder.getInstance())
                .withUser("admin").password("admin")
                .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();
        }
    }

This class extends WebSecurityConfigurerAdapter and overrides a couple of its methods to set some specifics of the web security configuration.

The configure(HttpSecurity) method defines which URL paths should be secured and which should not. Specifically, the "/" and "/login" paths are configured to not require any authentication. All other paths must be authenticated.

As for the method, it sets up userDetailsService()an in-memory user store with a single user. That user is given a username of "admin", a password of "admin", and a role of "ADMIN".

Controller Layer - TodoController.java

This class contains request handling methods for create, update, delete and list of Todos.

package net.guides.springboot.todomanagement.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 net.guides.springboot.todomanagement.model.Todo;
    import net.guides.springboot.todomanagement.service.ITodoService;
    
    @Controller
    public class TodoController {
    
        @Autowired
        private ITodoService todoService;
    
        @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", todoService.getTodosByUser(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());
            return "todo";
        }
    
        @RequestMapping(value = "/delete-todo", method = RequestMethod.GET)
        public String deleteTodo(@RequestParam long id) {
            todoService.deleteTodo(id);
            // service.deleteTodo(id);
            return "redirect:/list-todos";
        }
    
        @RequestMapping(value = "/update-todo", method = RequestMethod.GET)
        public String showUpdateTodoPage(@RequestParam long id, ModelMap model) {
            Todo todo = todoService.getTodoById(id).get();
            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.setUserName(getLoggedInUserName(model));
            todoService.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.setUserName(getLoggedInUserName(model));
            todoService.saveTodo(todo);
            return "redirect:/list-todos";
        }
    }

Controller Layer - WelcomeController.java

after a user successfully authenticated will navigates to welcome page and this request handled by this WelcomeController Class.

package net.guides.springboot.todomanagement.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();
        }
    
    }

Controller Layer - ErrorController.java

ErrorController used to map custom error page.

package net.guides.springboot.todomanagement.controller;

    import javax.servlet.http.HttpServletRequest;
    
    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;
        }
    
    }

Controller Layer - LogoutController.java

This Class handles, after logout successfully then navigate to a home page with a proper message.

package net.guides.springboot.todomanagement.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.web.authentication.logout.SecurityContextLogoutHandler;
    import org.springframework.stereotype.Controller;
    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:/";
        }
    }

Service Layer - ITodoService.java

Let's explore all the methods required to process Todo features in this interface.

package net.guides.springboot.todomanagement.service;

        import java.util.Date;
        
        import java.util.List;
        import java.util.Optional;
        
        import net.guides.springboot.todomanagement.model.Todo;
        
        public interface ITodoService {
        
            List < Todo > getTodosByUser(String user);
        
            Optional < Todo > getTodoById(long id);
        
            void updateTodo(Todo todo);
        
            void addTodo(String name, String desc, Date targetDate, boolean isDone);
        
            void deleteTodo(long id);
        
            void saveTodo(Todo todo);
        }

Service Layer - TodoService.java

package net.guides.springboot.todomanagement.service;

            import java.util.Date;
            import java.util.List;
            import java.util.Optional;
            
            import org.springframework.beans.factory.annotation.Autowired;
            import org.springframework.stereotype.Service;
            
            import net.guides.springboot.todomanagement.model.Todo;
            import net.guides.springboot.todomanagement.repository.TodoRepository;
            
            @Service
            public class TodoService implements ITodoService {
            
                @Autowired
                private TodoRepository todoRepository;
            
                @Override
                public List < Todo > getTodosByUser(String user) {
                    return todoRepository.findByUserName(user);
                }
            
                @Override
                public Optional < Todo > getTodoById(long id) {
                    return todoRepository.findById(id);
                }
            
                @Override
                public void updateTodo(Todo todo) {
                    todoRepository.save(todo);
                }
            
                @Override
                public void addTodo(String name, String desc, Date targetDate, boolean isDone) {
                    todoRepository.save(new Todo(name, desc, targetDate, isDone));
                }
            
                @Override
                public void deleteTodo(long id) {
                    Optional < Todo > todo = todoRepository.findById(id);
                    if (todo.isPresent()) {
                        todoRepository.delete(todo.get());
                    }
                }
            
                @Override
                public void saveTodo(Todo todo) {
                    todoRepository.save(todo);
                }
            }

Configuring MySQL Database and JSP View Resolver

Configure application.properties to connect to your MySQL database. Let's open an application.properties file and add following database configuration to it.

## Spring view resolver set up
                spring.mvc.view.prefix=/WEB-INF/jsp/
                spring.mvc.view.suffix=.jsp
                
                ## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
                spring.datasource.url = jdbc:mysql://localhost:3306/users_database?useSSL=false
                spring.datasource.username = root
                spring.datasource.password = root
                
                
                ## Hibernate Properties
                # The SQL dialect makes Hibernate generate better SQL for the chosen database
                spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
                
                # Hibernate ddl auto (create, create-drop, validate, update)
                spring.jpa.hibernate.ddl-auto = update

Common JSP page fragments - footer.jspf, header.jspf and navigation.jspf

footer.jspf View

    <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>
    
    <div class="footer">
        Fixed Footer
        <h1>
            <a href="http://www.javaguides.net/p/spring-boot-tutorial.html">
                Spring Boot Tutorial
            </a>
        </h1>
    </div>
    </body>
    
    </html>
    

header.jspf

        <%@ 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>Todo Management</title>
        <link href="webjars/bootstrap/3.3.6/css/bootstrap.min.css"
         rel="stylesheet">
        
        <style>
        .footer {
         position: fixed;
         left: 0;
         bottom: 0;
         width: 100%;
         background-color: black;
         color: white;
         height: 100px;
         text-align: center;
        }
        </style>
        
        </head>
        
        <body>
        

navigation.jspf View

    <nav role="navigation" class="navbar navbar-default">
     <div class="">
      <a href="http://www.javaguides.net" class="navbar-brand">Java Guides</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>
    

Now, we will include above page fragments into upcoming JSP pages.

Welcome Page View

    <%@ include file="common/header.jspf" %>
    <%@ include file="common/navigation.jspf" %>
    <div class="container">
    
     <div class="panel panel-primary">
         <div class="panel-heading">Home Page</div>
            <div class="panel-body">
               Welcome ${name}!! <a href="/list-todos">Click here</a> to manage your
               todo's.
            </div>
         </div>
     </div>
    <%@ include file="common/footer.jspf" %>
    

Todo Page View

    <%@ include file="common/header.jspf" %>
    <%@ include file="common/navigation.jspf" %>
    <div class="container">
     <div class="row">
      <div class="col-md-6 col-md-offset-3">
       <div class="panel panel-primary">
        <div class="panel-heading">Add TODO</div>
        <div class="panel-body">
         <form:form method="post" modelAttribute="todo">
          <form:hidden path="id" />
          <fieldset class="form-group">
           <form:label path="description">Description</form:label>
           <form:input path="description" type="text" class="form-control"
            required="required" />
           <form:errors path="description" 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">Save</button>
         </form:form>
        </div>
       </div>
      </div>
     </div>
    </div>
    <%@ include file="common/footer.jspf" %>
    

List Todo Page View

<%@ include file="common/header.jspf"%>
    <%@ include file="common/navigation.jspf"%>
    
    <div class="container">
     <pre>
      <div>
       <a type="button" class="btn btn-primary btn-md" href="/add-todo">Add Todo</a>
      </div>
      <br>
      <div class="panel panel-primary">
       <div class="panel-heading">
        <h3>List of TODO's</h3>
       </div>
       <div class="panel-body">
        <table class="table table-striped">
         <thead>
          <tr>
           <th width="40%">Description</th>
           <th width="40%">Target Date</th>
           <th width="20%"></th>
          </tr>
         </thead>
         <tbody>
          <c:forEach items="${todos}" var="todo">
           <tr>
            <td>${todo.description}</td>
            <td><fmt:formatDate value="${todo.targetDate}"
              pattern="dd/MM/yyyy" /></td>
            <td><a type="button" class="btn btn-success"
             href="/update-todo?id=${todo.id}">Update</a>
            <a type="button" class="btn btn-warning"
             href="/delete-todo?id=${todo.id}">Delete</a></td>
           </tr>
          </c:forEach>
         </tbody>
        </table>
       </div>
      </div>
     </pre>
    </div>
    
    <%@ include file="common/footer.jspf"%>
    

Simple Error Page View

<%@ include file="common/header.jspf"%>
        <%@ include file="common/navigation.jspf"%>
        <div class="container">
        <pre>
        An exception occurred! Please contact Support!
        </pre>
        </div>
        <%@ include file="common/footer.jspf"%>
        

Running the Application

We have successfully developed the Mini Todo Management web application. Now it's time to deploy our application in a servlet container(embedded tomcat). Two ways we can start the standalone Spring boot application.

1.From the root directory of the application and type the following command to run it -

$ mvn spring-boot:run

2.From your IDE, run the TodoManagementSpringBoot2Application.main() method as a standalone Java class that will start the embedded Tomcat server on port 8080 and point the browser to http://localhost:8080/.

Demo

1. Login Page

URL : http://localhost:8080/login

Username: admin

Password: admin

This is simple Spring Security provided login page. You can also create a custom login page.

2. Home Page

After successfully user logged in navigate to a home page.

3. List Todos

URL: http://localhost:8080/list-todos

4. Create Todo

URL: http://localhost:8080/add-todo

5. Update Todo

URL: http://localhost:8080/update-todo?id=28

6. Logout Page

URL: http://localhost:8080/login?logout

I am done with developing mini Todo Management Spring MVC web application.