Spring Web MVC framework

The Spring Web model-view-controller (MVC) is designed around a DispatcherServlet that dispatches requests to handlers, with configurable handler mapping, view resolution, locale and theme resolution as well as support for uploading files.

Features of Spring Web MVC

  1. Clear seperation of roles:- Each role like controller, DispatcherServlet can be fulfilled by a specialized object.
  2. Adaptability, non-intrusiveness, and flexibility:- Define any controller method signature you need. (Such as @RequestParam)
  3. Customizable binding and validation:- Type mismatches as application-level validation errors that keep the offending value, localized date and number binding, and so on instead of String-only form objects with manual parsing and conversion to business objects.

The DispatcherServlet

Implementing Controllers

Controllers provide access to the application behavior that you typically define through a service interface. Controllers interpret user input and transform it into a model that is represented to the user by the view. Spring implements a controller in a very abstract way, which enables you to create a wide variety of controllers such as @RequestMapping, @RequestParam, @ModelAttribute, and so on.

URI Template Patterns

URI templates can be used for convenient access to selected parts of a URL in a @RequestMapping method. It is like URI-like string, containing one or more variable names. For example, the URI Template http://www.example.com/users/{userId} contains the variable userId.

package com.BlogCRUD.Blog.models;

import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.CreationTimestamp;

import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.*;

@Data
@Entity
@EqualsAndHashCode(callSuper = true)
@Table(name = "posts")
public class Post extends BaseModel {


@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;

@Column(name = "title")
private String title;

@Column(name = "excerpt")
private String excerpt;

@Lob
@Column(name = "content", columnDefinition = "LONGTEXT")
private String content;

@Column(name = "author")
private String author;

@Column(name = "published_at")
@CreationTimestamp
private LocalDateTime publishedAt;

@Column(nullable = false, columnDefinition = "TINYINT(1)")
private boolean isPublished = true;

@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
@JoinTable(name = "post_tags",
joinColumns = {@JoinColumn(name = "post_id")},
inverseJoinColumns = {@JoinColumn(name = "tag_id")})
private Set<Tag> tags = new HashSet<>();


@OneToMany(mappedBy="post",
cascade= {CascadeType.REMOVE})

List<Comment> comments = new ArrayList<>();


@Column(name = "tag")
private String tag;






public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public String getExcerpt() {
return excerpt;
}

public void setExcerpt(String excerpt) {
this.excerpt = excerpt;
}

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}

public String getAuthor() {
return author;
}

public void setAuthor(String author) {
this.author = author;
}

public LocalDateTime getPublishedAt() {
return publishedAt;
}

public void setPublishedAt(LocalDateTime publishedAt) {
this.publishedAt = publishedAt;
}

public boolean isPublished() {
return isPublished;
}

public void setPublished(boolean published) {
this.isPublished = published;
}

public String getTag() {
return tag;
}

public void setTag(String tag) {
this.tag = tag;
}

public Set<Tag> getTags() {
return tags;
}

public void setTags(Set<Tag> tags) {
this.tags = tags;
}

public boolean hasTags(Post tag) {
for (Tag postTag : getTags()) {
if (postTag.getId() == tag.getId()) {
return true;
}
}
return false;
}

public List<Comment> getComments() {
return comments;
}

public void setComments(List<Comment> comments) {
this.comments = comments;
}

public boolean hasComments(Comment comment){
for(Comment comment1: getComments()){
if(comment1.getId()==comment.getId()){
return true;
}
}
return false;
}


}

Sample View using Thymleaf

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

<head>
<meta charset="UTF-8">
<title>Blog</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
</head>

<body>

<div class="container my-2">
<h1> Posts List </h1>

<a th:href="@{/posts/showNewPostsForm}" class="btn btn-primary btn-sm mb-3">Add Posts</a>
<a th:href="@{'/posts/page/' + ${currentPage} + '?sortField=publishedAt&sortDir=' + ${reverseSortDir}}">
Sort</a>

<form th:action="@{/posts/list}">
<input type="text" name="keyword2" id="keyword2" size="50" th:value="${keyword2}"/>
&nbsp;<br>

<div class="form-group">
<label for="author">Select dips</label>
<select class="form-control selectpicker" th:field="*{newPost.author}" id="author" multiple>

<option th:each="post : ${listPost1}"
th:value="${post.author}"
th:text="${post.author}">author
</option>

</select>
</div>
<input type="submit" value="Filter"/>
</form>
<form th:action="@{/posts/list}">
<input type="text" name="keyword" id="keyword" size="50" th:value="${keyword}"/>
&nbsp;<br>
<input type="submit" value="Search"/>
&nbsp;
<table border="1" class="table table-striped table-responsive-md">
<thead>
<tr>

<th> Title</th>
<th> Excerpt</th>
<th> Content</th>
<th> Author</th>
<th> Published Date time</th>

</tr>
</thead>
<tbody>


<tr th:each="posts : ${listPost1}">
<td th:text="${posts.title}"></td>
<td th:text="${posts.excerpt}"></td>
<td th:text="${posts.content}"></td>
<td th:text="${posts.author}"></td>
<td th:text="${posts.publishedAt}"></td>
</td>

<td>
<a th:href="@{/posts/{id}(id=${posts.id})}" class="btn btn-primary"> View Post</a>
<a th:href="@{/posts/showFormForUpdate/{id}(id=${posts.id})}" class="btn btn-primary">Update</a>
<a th:href="@{/posts/deletePosts/{id}(id=${posts.id})}" class="btn btn-danger">Delete</a>
</td>
</tr>
</tbody>
</table>


<div th:if="${totalPages > 0}">
<div class="row col-sm-10">
<div class="col-sm-2">
Total Rows: [[${totalItems}]]
</div>
<div class="col-sm-1">
<span th:each="i: ${#numbers.sequence(1, totalPages)}">
<a th:if="${currentPage != i}"
th:href="@{'/posts/page/' + ${i}+ '?sortField=' + ${sortField} + '&sortDir=' + ${sortDir}}">[[${i}]]</a>
<span th:unless="${currentPage != i}">[[${i}]]</span> &nbsp; &nbsp;
</span>
</div>
<div class="col-sm-1">
<a th:if="${currentPage < totalPages}"
th:href="@{'/posts/page/' + ${currentPage + 1}+ '?sortField=' + ${sortField} + '&sortDir=' + ${sortDir}}">Next</a>
<span th:unless="${currentPage < totalPages}">Next</span>
</div>

<div class="col-sm-1">
<a th:if="${currentPage < totalPages}"
th:href="@{'/posts/page/' + ${totalPages}+ '?sortField=' + ${sortField} + '&sortDir=' + ${sortDir}}">Last</a>
<span th:unless="${currentPage < totalPages}">Last</span>
</div>
</div>
</div>

</form>

</div>
<script th:src="@{/webjars/jquery/jquery.min.js}"></script>
<script th:src="@{/webjars/popper.js/umd/popper.min.js}"></script>
<script th:src="@{/webjars/bootstrap/js/bootstrap.min.js}"></script>
<script th:src="@{assets/bootstrap-select-1.13.9/dist/js/bootstrap-select.js}"></script>

</body>

</html>

Sample Controller

package com.BlogCRUD.Blog.controllers;

import com.BlogCRUD.Blog.models.Comment;
import com.BlogCRUD.Blog.models.Post;
import com.BlogCRUD.Blog.models.Tag;
import com.BlogCRUD.Blog.models.User;
import com.BlogCRUD.Blog.repository.CommentRepository;
import com.BlogCRUD.Blog.repository.PostRepository;
import com.BlogCRUD.Blog.repository.TagRepository;
import com.BlogCRUD.Blog.repository.UserRepository;
import com.BlogCRUD.Blog.services.PostService;
import com.BlogCRUD.Blog.services.TagService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.metrics.StartupStep;
import org.springframework.data.domain.Page;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Optional;

@Controller
//@RequestMapping("/posts")
public class PostController {

public String keyword1=null, filterKeyword;

@Autowired
private PostService postsService;

@Autowired
private TagService tagService;

@Autowired
private UserRepository userRepository;

@Autowired
private CommentRepository commentRepository;

@Autowired
private PostRepository postRepository;

@GetMapping("/")
public String viewHomePage(){
return "index";
}

@GetMapping("/register")
public String showRegistrationForm(Model model) {
model.addAttribute("user", new User());

return "signupForm";
}

@PostMapping("/process_register")
public String processRegister(User user) {

userRepository.save(user);

return "RegisterSuccess";
}

@GetMapping("/login")
public String login(Model model){
model.addAttribute("user", new User());
return "login";
}

@PostMapping("/processLogin")
public String processLogin(User user){
if(user.getEmail().equals("admin1@gmail.com") && user.getPassword().equals("admin1")){
return "redirect:/posts/list";
}
return "login";
}

@GetMapping("/posts/list")
public String viewPostsList(Model model, @Param("keyword") String keyword, @Param("keyword2") String keyword2,
@Param("author") String author) {
System.out.println(author);
keyword1=keyword;
filterKeyword =keyword2;
System.out.println(keyword2);
List<Post> listPosts = postsService.listAll(keyword);
model.addAttribute(("listPosts"), listPosts);
String authorName = null;
model.addAttribute("authorName", authorName);
//Post newPost = new Post();
//model.addAttribute("newPost", newPost);
return findPaginated(1, "publishedAt", "asc", model);
}

@GetMapping("/listUnPublishedPosts")
public String listUnPublishedPosts(Model model) {
model.addAttribute("listPosts", postsService.getAllUnPublishedPosts());
return "UnPublishedPosts";

}

@GetMapping("/posts/showNewPostsForm")
public String showNewPostsForm(Model model) {
Post posts = new Post();
model.addAttribute("posts", posts);
return "NewPosts";
}

@GetMapping("/posts/addTag/{id}")
public String addTag(@PathVariable("id") int postId, Model model){
model.addAttribute("tag", tagService.findAll());
model.addAttribute("posts", postsService.findOne(postId));
Tag tags = new Tag();
model.addAttribute("tags", tags);
return "AddPostTag";
}

@GetMapping("/posts/{id}")
public String viewPost(@PathVariable("id")int postId, Model model){
model.addAttribute("posts", postsService.getPostsById(postId));
model.addAttribute("comments", commentRepository.findByPostId(postId));

return "ViewPost";
}
@RequestMapping("/posts/addComment/{postsId}/comment")
public String addComments(@PathVariable("postsId")int postId, @ModelAttribute("newComment") Comment newComment) {

Post currentPosts = postsService.getPostsById(postId);
newComment.setPost(currentPosts);
commentRepository.save(newComment);

currentPosts.getComments().add(newComment);

postsService.savePosts(currentPosts);
//model.addAttribute("posts", postsService.getPostsById(postId));
return "redirect:/posts/{postsId}";
}

@PostMapping("/posts/savePosts")
public String savePosts(@ModelAttribute("posts") Post posts) {
postsService.savePosts(posts);

String tag = posts.getTag();
String[] listTag = tag.split(",");

for(String tags:listTag){
Tag tag1 = new Tag(tags);
tagService.saveTags(tag1);

posts.getTags().add(tag1);
}


postsService.savePosts(posts);

return "redirect:/posts/list";
}

@PostMapping("/posts/saveTag/{id}")
public String saveTags(@PathVariable("id") int postId, @ModelAttribute("tags") Tag tags) {
Post currentPosts = postsService.getPostsById(postId);
currentPosts.getTags().add(tags);
tagService.saveTags(tags);

return "redirect:/posts/listUnPublishedPosts";
}

@GetMapping("/posts/showFormForUpdate/{id}")
public String showFormForUpdate(@PathVariable(value = "id") int id, Model model){
Post posts = postsService.getPostsById(id);
model.addAttribute("posts", posts);
return "UpdatePosts";
}

@GetMapping("/posts/deletePosts/{id}")
public String deletePosts(@PathVariable(value = "id") int id) {
this.postsService.deletePostsById(id);
return "redirect:/posts/list";
}

@GetMapping("/posts/page/{pageNo}")
public String findPaginated(@PathVariable(value = "pageNo") int pageNo,
@RequestParam("sortField") String sortField,
@RequestParam("sortDir") String sortDir,
Model model) {
int pageSize = 10;

Page < Post > page = postsService.findPaginated(pageNo, pageSize, sortField, sortDir);
List < Post > listPosts = page.getContent();

model.addAttribute("currentPage", pageNo);
model.addAttribute("totalPages", page.getTotalPages());
model.addAttribute("totalItems", page.getTotalElements());

model.addAttribute("sortField", sortField);
model.addAttribute("sortDir", sortDir);
model.addAttribute("reverseSortDir", sortDir.equals("desc") ? "asc" : "desc");

List<Post> listPosts1;
if(keyword1!=null){
listPosts1 = postsService.listAll(keyword1);
model.addAttribute("listPost1", listPosts1);
}

if(keyword1==null){
model.addAttribute("listPost1", listPosts);

}

String authorName=null;
model.addAttribute("authorName", authorName);

Post newPost = new Post();
model.addAttribute("newPost", newPost);
System.out.println(newPost);
return "PostsList";
}

@RequestMapping(value = "posts/addComment/{id}", method = RequestMethod.GET)
public String addComment(@PathVariable("id") int postsId, Model model){
model.addAttribute("comment", commentRepository.findAll());

Comment newComment = new Comment();
model.addAttribute("newComment", newComment);
model.addAttribute("posts", postsService.getPostsById(postsId));
return "AddComment";
}

@RequestMapping(value = "/posts/{postsId}/updateComments/{commentId}", method = RequestMethod.GET)
public String updateComment(@PathVariable("postsId") int postsId,
@PathVariable("commentId") int commentId ,Model model){
model.addAttribute("posts", postsService.getPostsById(postsId));
model.addAttribute("newComment", commentRepository.findById(commentId));
return "AddComment";
}

@RequestMapping(value = "/posts/{postsId}/deleteComments/{commentId}", method = RequestMethod.GET)
public String deleteComment(@PathVariable("postsId") int postsId,
@PathVariable("commentId") int commentId ,Model model){
Optional<Comment> comment = commentRepository.findById(commentId);
this.commentRepository.deleteById(commentId);

return "redirect:/posts/{postsId}";
}
}

Output:-

Conclusion

Spring MVC framework is widely used to create web based application. For more details you can see Official docs.