Spring Boot + FreeMarker + Spring Data JPA - CRUD example
Hello everyone, Today we will learn how to develop a Spring Boot CRUD web application, using Spring Boot, FreeMarker template, H2DB, and JPA.
Apache FreeMarker is a template engine: a Java library to generate text output (HTML web pages, e-mails, configuration files, source code, etc.) based on templates and changing data. Templates are written in the FreeMarker Template Language (FTL), which is a simple, specialized language. Usually, a general-purpose programming language (like Java) is used to prepare the data (issue database queries, do business calculations). Then, Apache FreeMarker displays that prepared data using templates. In the template, you are focusing on how to present the data, and outside the template, you are focusing on what data to present.
More Info - https://freemarker.apache.org/
The technology stack used:
- Spring Boot 2.5.5
- Spring MVC 5.3.10
- Maven 3
- JDK 11
- FreeMarker 2.3.31
- H2DB
- Bootstrap
Project Structure
Dependency Management-Maven-Pom.xml
Puts spring-boot-starter-freemarker, spring-boot-starter-jpa, h2 dependencies
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.knf.dev.demo</groupId>
<artifactId>springbootfreemarkerjpacrud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springbootfreemarkerjpacrud</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</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>
Create the User Entity
package com.knf.dev.demo.springbootfreemarkerjpacrud.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
@Column(name = "email", nullable = false, length = 200)
private String email;
public User() {
super();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public User(String firstName, String lastName, String email) {
super();
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
@Override
public String toString() {
return "User [id=" + id + ", firstName=" + firstName + ", lastName="
+ lastName + ", email=" + email + "]";
}
}
Create CRUD Repository
package com.knf.dev.demo.springbootfreemarkerjpacrud.repository;
import com.knf.dev.demo.springbootfreemarkerjpacrud.model.User;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends CrudRepository<User, Long> {
}
Creat RecordNotFoundException
package com.knf.dev.demo.springbootfreemarkerjpacrud.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class RecordNotFoundException extends Exception {
private static final long serialVersionUID = 1L;
public RecordNotFoundException(String message) {
super(message);
}
public RecordNotFoundException(String message, Throwable t) {
super(message, t);
}
}
Create the User Service
package com.knf.dev.demo.springbootfreemarkerjpacrud.service;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import com.knf.dev.demo.springbootfreemarkerjpacrud.exception.RecordNotFoundException;
import com.knf.dev.demo.springbootfreemarkerjpacrud.model.User;
import com.knf.dev.demo.springbootfreemarkerjpacrud.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
UserRepository repository;
public List<User> getAllusers() {
List<User> result = (List<User>) repository.findAll();
if (result.size() > 0) {
return result;
} else {
return new ArrayList<User>();
}
}
public User getUserById(Long id) throws RecordNotFoundException {
Optional<User> user = repository.findById(id);
if (user.isPresent()) {
return user.get();
} else {
throw new RecordNotFoundException
("No user record exist for given id");
}
}
public User createOrUpdateUser(User entity) {
if (entity.getId() == null) {
entity = repository.save(entity);
return entity;
} else {
Optional<User> user = repository.findById(entity.getId());
if (user.isPresent()) {
User newEntity = user.get();
newEntity.setEmail(entity.getEmail());
newEntity.setFirstName(entity.getFirstName());
newEntity.setLastName(entity.getLastName());
newEntity = repository.save(newEntity);
return newEntity;
} else {
entity = repository.save(entity);
return entity;
}
}
}
public void deleteUserById(Long id) throws RecordNotFoundException {
Optional<User> user = repository.findById(id);
if (user.isPresent()) {
repository.deleteById(id);
} else {
throw new RecordNotFoundException
("No user record exist for given id");
}
}
}
Create the Web Controller
package com.knf.dev.demo.springbootfreemarkerjpacrud.controller;
import com.knf.dev.demo.springbootfreemarkerjpacrud.exception.RecordNotFoundException;
import com.knf.dev.demo.springbootfreemarkerjpacrud.model.User;
import com.knf.dev.demo.springbootfreemarkerjpacrud.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import java.util.List;
@Controller
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/")
public String getAllUserView(Model model) {
List<User> users = userService.getAllusers();
model.addAttribute("users", users);
return "home";
}
@GetMapping("/create")
public String createUserView(Model model) {
User user = new User();
model.addAttribute("user", user);
model.addAttribute("isUpdate", false);
return "create-update";
}
@PostMapping("/update/{id}")
public String createUser(@ModelAttribute("user") User user,
@PathVariable("id") Long id) {
user.setId(id);
userService.createOrUpdateUser(user);
return "redirect:/";
}
@GetMapping("/update/{id}")
public String updateUser(Model model, @PathVariable("id") Long id)
throws RecordNotFoundException {
User user = userService.getUserById(id);
model.addAttribute("user", user);
model.addAttribute("isUpdate", true);
return "create-update";
}
@PostMapping("/create")
public String createUser(@ModelAttribute("user") User user) {
userService.createOrUpdateUser(user);
return "redirect:/";
}
@GetMapping("/delete/{id}")
public String deleteUser(@PathVariable("id") Long id)
throws RecordNotFoundException {
userService.deleteUserById(id);
return "redirect:/";
}
}
Creating FreeMarker templates
home.ftlh
<!doctype html>
<head>
<title>Freemarker Example</title>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1" name="viewport">
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"
rel="stylesheet">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js">
</script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js">
</script>
</head>
<body>
<div class="container">
<h2>User CRUD operation with Freemarker Template</h2>
<a href="/create" class="btn btn-primary" role="button">Create New User</a>
<table class="table">
<thead >
<tr>
<th scope="col">Id</th>
<th scope="col">First Name</th>
<th scope="col">Last Name</th>
<th scope="col">Email</th>
<th scope="col"></th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<#list users as user>
<tr>
<th scope="row">${user.id}</th>
<td>${user.firstName}</td>
<td>${user.lastName}</td>
<td>${user.email}</td>
<td><a href="/update/${user.id}" class="btn btn-warning" role="button">
Update</a></td>
<td><a href="/delete/${user.id}" class="btn btn-danger" role="button">
Delete</a></td>
</tr>
</#list>
</tbody>
</table>
</div>
</body>
</html>
create-update.ftlh
<!doctype html>
<head>
<title>Freemarker Example</title>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1" name="viewport">
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"
rel="stylesheet">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js">
</script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js">
</script>
</head>
<body>
<div class="container">
<h2>
<#if !isUpdate>Create</#if>
<#if isUpdate>Update</#if>
User
</h2>
<div>
<form action="<#if isUpdate>/update/${user.id}</#if><#if !isUpdate>/create</#if>"
method="post" name="user">
<table class="table">
<tbody>
<thead>
<tr>
<th>Field</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<#if isUpdate>
<tr>
<th>ID</th>
<td>
<div name="id">${user.id}</div>
</td>
</tr>
</#if>
<tr>
<th>First Name</th>
<td><input name="firstName" type="text"
value="<#if user.firstName??>${user.firstName}</#if>"/>
</td>
</tr>
<tr>
<th>Last Name</th>
<td><input name="lastName" type="text"
value="<#if user.lastName??>${user.lastName}</#if>"/></td>
</tr>
<tr>
<th>Email</th>
<td><input name="email" type="text"
value="<#if user.email??>${user.email}</#if>"/></td>
</tr>
</tbody>
</table>
<button class="btn btn-primary" type="submit">Save</button>
</form>
</div>
</div>
</body>
</html>
Spring Boot Main Driver
package com.knf.dev.demo.springbootfreemarkerjpacrud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringbootfreemarkerjpacrudApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootfreemarkerjpacrudApplication.class, args);
}
}
Local Setup and Run the application
Step1: Download or clone the source code from GitHub to the local machine - Click here
Backend
Step 2: mvn clean install
Step 3: Run the Spring Boot application - mvn spring-boot:run
Step1: Download or clone the source code from GitHub to the local machine - Click here
Backend
Step 2: mvn clean install
Step 3: Run the Spring Boot application - mvn spring-boot:run
Access the URL in the browser: http://localhost:8080/