・ Learning Java / Spring Boot / Thymeleaf ・ I want to create a simple web application for practice
Create a simple web app that allows you to manage customers (register / update / delete / list). Certification / authorization / unit testing is omitted because it will be a lot. However, it may be better to add unit tests if you have time.
The completed image is as follows.
If you want to see the finished product first, please.
The source is here.
The one that works is here.
It is assumed that the development environment and PostgreSQL have been installed. If you have not installed it, please follow the link below.
The overall configuration of the web application created this time is as follows.
Roughly speaking ...
Since development is the main theme, functional design is easy to write.
No | screen name | Description |
---|---|---|
1 | the top screen | The screen that serves as the entrance to each screen. |
2 | New registration screen | A screen for newly registering customer information. |
3 | Update screen | A screen to change customer information. |
4 | List screen | A screen to list and delete customer information. |
As per [2. Goal](# 2-Goal).
I think you can understand it, so I omitted it.
No | URL | HTTP method | Description |
---|---|---|---|
1 | / | GET | the top screen |
2 | /customers | GET | List screen |
3 | /customers/create | GET | New registration screen |
4 | /customers/create | POST | New registration execution |
5 | /customers/{id}/update | GET | Update screen |
6 | /customers/{id}/update | POST | Update execution |
7 | /customers/{id}/delete | GET | Delete execution |
Designed with reference to here.
No | table name | Description |
---|---|---|
1 | customer | Manage customer information. |
No | Column name | PK | Non-null | Description |
---|---|---|---|---|
1 | id | ○ | ○ | Customer ID. Automatic numbering in sequence. |
2 | name | ○ | Customer name. |
Now let's move on to the development of the main subject.
Download the template from the Spring official website and develop it. The table is automatically generated from Model by the function of Spring JPA.
Create a template for your web app project. It's easy to download from Spring Initializr. You can download it by entering the following and pressing the "Generate" button.
Set the external libraries to be used.
You can do the same from the Eclipse menu. Specifically, they are "File", "New", "Other", "Spring Boot", and "Spring Starter Project".
Import the downloaded template into Eclipse. The specific procedure is as follows.
When imported, it will be built automatically, as per the dependencies in build.gradle, External libraries will be downloaded.
Some external libraries are not included in Spring Initializr. Specifically, the bootstrap java library is not included. Open the build.gradle file in Eclipse and add as follows.
build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.webjars:jquery:3.3.1' //add to
implementation 'org.webjars:bootstrap:4.3.1' //add to
implementation 'org.webjars:font-awesome:5.13.0' //add to
//abridgement. The full version is https://github.com/tk230to/tksystem
Look for these libraries in the Maven Repository (https://mvnrepository.com/).
Create each class and configuration file.
Create a Model / View / Controller / configuration file. What was this file? In that case, please read [5.2 Configuration](# 52-Configuration) again.
Create a class corresponding to [customer table](# 63-db design). Class name = table name, field name = column name.
Customer.java
package com.example.tksystem.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.validation.constraints.NotBlank;
import lombok.Data;
/**
*Customer class.
*/
@Entity
@Data
public class Customer {
/**Sequence name*/
private static final String SEQUENCE_NAME = "customer_id_seq";
/** ID */
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE_NAME)
@SequenceGenerator(name = SEQUENCE_NAME, sequenceName = SEQUENCE_NAME, allocationSize = 1)
private Long id;
/**name*/
@NotBlank
private String name;
}
Create a class to do CRUD operations on the customer table. It just inherits from JpaRepository.
CustomerRepository.java
package com.example.tksystem.model;
import org.springframework.data.jpa.repository.JpaRepository;
/**
*Customer repository class.
*/
public interface CustomerRepository extends JpaRepository<Customer, Long> {
}
The following methods (excerpts) will be available.
Create an HTML template for each screen. Since the navigation bar at the top is common to all screens, it will be shared as a common layout.
layout.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="head(title)">
<title th:text="'Customer management- ' + ${title}">tksystem</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" th:href="@{/webjars/bootstrap/4.3.1/css/bootstrap.min.css}" />
<link rel="stylesheet" th:href="@{/webjars/font-awesome/5.13.0/css/all.min.css}" />
<script th:src="@{/webjars/jquery/3.3.1/jquery.min.js}"></script>
<script th:src="@{/webjars/bootstrap/4.3.1/js/bootstrap.min.js}"></script>
</head>
<body>
<nav class="navbar navbar-dark bg-dark navbar-expand-sm mb-3" th:fragment="navbar">
<a class="navbar-brand" href="/">Customer management</a>
<button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#Navbar" aria-controls="Navbar" aria-expanded="false" aria-label="Switching navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div id="Navbar" class="collapse navbar-collapse">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" th:href="@{/customers/create/}">
<i class="fas fa-plus fa-lg" aria-hidden=”true”></i>sign up
</a>
</li>
<li class="nav-item">
<a class="nav-link" th:href="@{/customers/}">
<i class="fas fa-list fa-lg" aria-hidden=”true”></i>List
</a>
</li>
</ul>
</div>
</nav>
</body>
</html>
index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/layout :: head('Home')">
</head>
<body>
<div th:replace="fragments/layout :: navbar"></div>
<div class="container-fluid">
<h5>menu</h5>
<hr>
<div class="row">
<div class="col-sm-6">
<div class="card">
<div class="card-body">
<h5 class="card-title">
<a class="nav-link" th:href="@{/customers/create}">
<i class="fas fa-plus fa-lg" aria-hidden=”true”></i>sign up
</a>
</h5>
<p class="card-text">Register a new customer.</p>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="card">
<div class="card-body">
<h5 class="card-title">
<a class="nav-link" th:href="@{/customers/}">
<i class="fas fa-list fa-lg" aria-hidden=”true”></i>List
</a>
</h5>
<p class="card-text">Display a list of customers.</p>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
create.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/layout :: head('sign up')">
</head>
<body>
<div th:replace="fragments/layout :: navbar"></div>
<div class="container-fluid">
<h5>sign up</h5>
<hr>
<div class="row">
<div class="col-sm-12">
<form action="#" th:action="@{/customers/create/}" th:object="${customer}" method="post">
<div class="form-group">
<label for="name">name<span class="badge badge-danger">Mandatory</span></label>
<input type="text" id="name" class="form-control" placeholder="(Example)Yamada Taro" th:field="*{name}" />
<span th:if="${#fields.hasErrors('name')}" th:errors="*{name}" style="color: red"></span>
</div>
<button type="submit" class="btn btn-primary">Confirm</button>
</form>
</div>
</div>
</div>
</body>
</html>
It is almost the same as the registration screen. The finished product is here
list.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/layout :: head('List')">
</head>
<body>
<div th:replace="fragments/layout :: navbar"></div>
<div class="container-fluid">
<h5>List</h5>
<hr>
<div class="row">
<div class="col-sm-12">
<table class="table table-bordered table-hover">
<thead class="thead-dark">
<tr>
<th width="10%">ID</th>
<th width="80%">name</th>
<th width="10%">Delete</th>
</tr>
</thead>
<tbody>
<tr th:each="customer:${customer}">
<td th:text="${customer.id}"></td>
<td>
<a th:text="${customer.name}" th:href="@{/customers/{id}/update/(id=${customer.id})}">
</a>
</td>
<td>
<a th:href="@{/customers/{id}/delete/(id=${customer.id})}" onClick="return window.confirm('Are you sure you want to delete?')">
<i class="far fa-trash-alt fa-lg" aria-hidden=”true”></i>
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
Create a class to handle top screen requests.
IndexController.java
package com.example.tksystem.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
/**
*The controller class for the index screen.
*/
@Controller
@RequestMapping("/")
public class IndexController {
/**
*index screen
*
* @param model model
* @return Transition destination
*/
@RequestMapping("index")
public String index(Model model) {
return "index";
}
}
Create a class to handle requests on the customer screen (new registration / update / list screen).
CustomerController.java
package com.example.tksystem.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
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 org.springframework.web.bind.annotation.RequestMapping;
import com.example.tksystem.model.Customer;
import com.example.tksystem.model.CustomerRepository;
/**
*Customer screen controller class.
*/
@Controller
@RequestMapping("/customers")
public class CustomerController {
/**Registration/update/Redirect destination URL after deletion is completed*/
private static final String REDIRECT_URL = "redirect:/customers/";
/**HTML path*/
private static final String PATH_LIST = "customer/list";
private static final String PATH_CREATE = "customer/create";
private static final String PATH_UPDATE = "customer/update";
/**Model attribute name*/
private static final String MODEL_ATTRIBUTE_NAME = "customer";
/**Customer repository*/
@Autowired
private CustomerRepository customerRepository;
/**
*Display the list screen.
*
* @param model model
* @return Transition destination
*/
@GetMapping(value = "/")
public String list(Model model) {
model.addAttribute(MODEL_ATTRIBUTE_NAME, customerRepository.findAll(Sort.by("id")));
return PATH_LIST;
}
/**
*Display the registration screen.
*
* @param model model
* @return Transition destination
*/
@GetMapping(value = "/create")
public String create(Model model) {
model.addAttribute(MODEL_ATTRIBUTE_NAME, new Customer());
return PATH_CREATE;
}
/**
*Perform registration.
*
* @param customer Customer screen input value
* @param result Input check result
* @return Transition destination
*/
@PostMapping(value = "/create")
public String create(@Validated @ModelAttribute(MODEL_ATTRIBUTE_NAME) Customer customer,
BindingResult result) {
if (result.hasErrors()) {
return PATH_CREATE;
}
customerRepository.save(customer);
return REDIRECT_URL;
}
/**
*Display the update screen.
*
* @param id customer ID
* @param model model
* @return Transition destination
*/
@GetMapping(value = "/{id}/update")
public String update(@PathVariable("id") Long id, Model model) {
model.addAttribute(MODEL_ATTRIBUTE_NAME, customerRepository.getOne(id));
return PATH_UPDATE;
}
/**
*Perform the update.
*
* @param customer Customer screen input value
* @param result Input check result
* @return Transition destination
*/
@PostMapping(value = "/{id}/update")
public String update(@Validated @ModelAttribute(MODEL_ATTRIBUTE_NAME) Customer customer,
BindingResult result) {
if (result.hasErrors()) {
return PATH_UPDATE;
}
customerRepository.save(customer);
return REDIRECT_URL;
}
/**
*Perform deletion.
*
* @param id customer ID
* @return Transition destination
*/
@GetMapping(value = "/{id}/delete")
public String list(@PathVariable("id") Long id) {
customerRepository.deleteById(id);
return REDIRECT_URL;
}
}
application.yml This file is used to set DB connection information, etc. url / username / password is the default for PostgreSQL. If it is different from the default, change it.
application.yml
spring:
datasource:
#Postgres IP address/port number/DB name
url: jdbc:postgresql://localhost:5432/postgres
#Postgres username
username: postgres
#Postgres password
password: postgres
#Postgres JDBC driver
driver-class-name: org.postgresql.Driver
jpa:
hibernate:
# @Always drop the table corresponding to Entity&create.
ddl-auto: create-drop
hibernate.properties This setting avoids exceptions that occur in PostgreSQL x Spring JPA. There is no problem in operation without it, but it is unpleasant to get an exception every time, so it is better to set it. Details of the exception here
hibernate.properties
# PgConnection.createClob()Avoid method warnings
hibernate.jdbc.lob.non_contextual_creation = true
ValidationMessages.properties The message definition for the input check error message. I will make my own because the Japanese message has not been provided yet. A pull request has been captured at here, so I think it will be provided soon.
ValidationMessages.properties
javax.validation.constraints.AssertFalse.message =Please set to false
javax.validation.constraints.AssertTrue.message =Please set to true
javax.validation.constraints.DecimalMax.message = {value} ${inclusive == true ? 'Please set the following values' : 'Please make it smaller'}
javax.validation.constraints.DecimalMin.message = {value} ${inclusive == true ? 'Please set the value above' : 'Please set a larger value'}
javax.validation.constraints.Digits.message =The value should be in the following range(<integer{integer}digit>.<After the decimal point{fraction}digit>)
javax.validation.constraints.Email.message =Please use the correct format for your email address
javax.validation.constraints.Future.message =Please set to a future date
javax.validation.constraints.FutureOrPresent.message =Set to current or future date
javax.validation.constraints.Max.message = {value}Please set the following values
javax.validation.constraints.Min.message = {value}Please set the value above
javax.validation.constraints.Negative.message =Must be less than 0
javax.validation.constraints.NegativeOrZero.message =Please set the value to 0 or less
javax.validation.constraints.NotBlank.message =White space is not allowed
javax.validation.constraints.NotEmpty.message =Empty elements are not allowed
javax.validation.constraints.NotNull.message =null is not allowed
javax.validation.constraints.Null.message =Please make it null
javax.validation.constraints.Past.message =Please set to a past date
javax.validation.constraints.PastOrPresent.message =Set to current or past date
javax.validation.constraints.Pattern.message =Regular expressions"{regexp}"Please match to
javax.validation.constraints.Positive.message =Must be greater than 0
javax.validation.constraints.PositiveOrZero.message =Must be greater than or equal to 0
javax.validation.constraints.Size.message = {min}From{max}Please make the size between
WebConfig Make settings to use the above ValidationMessages.properties. See here for more information.
WebConfig.java
package com.example.tksystem;
import java.nio.charset.StandardCharsets;
import org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
*Web configuration class.
*
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public Validator getValidator() {
// ValidationMessages.UTF to properties-Allows you to set with 8.
ReloadableResourceBundleMessageSource messageSource =
new ReloadableResourceBundleMessageSource();
messageSource.setBasename(AbstractMessageInterpolator.USER_VALIDATION_MESSAGES);
messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name());
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setValidationMessageSource(messageSource);
return validator;
}
}
Now that all the files are complete, run the project in Eclipse. The procedure is to select the project and select "Right click" "Run" "Spring Boot application".
By setting ddl-auto set in application.yml The table is automatically generated according to the created Entity class.
Please access [http: // localhost: 8080](http: // localhost: 8080) and check the operation. If it works like [2. Goal](# 2-Goal), it is successful.
Thank you for your hard work.
Recommended Posts