Use Spring Boot, ConcurrentHashMap, Thymeleaf and Bootstrap to implement user caching functions

Original Lutiao Programming Lutiao Programming 2023-10-11 08:00 Published in Hebei

included in collection

#cache3

#SpringBoot75

Picture

This series of courses will cover many key technologies and tools of Spring Boot, including Mybatis-Plus, Redis, Mongodb, MinIO, Kafka, MySQL, Message Queuing (MQ), OAuth2 and other related content.

Use Spring Boot, ConcurrentHashMap, Thymeleaf and Bootstrap to implement user caching functions

The following is the key code and configuration files of a sample project using Spring Boot, ConcurrentHashMap, Thymeleaf and Bootstrap to implement user caching functionality. First, provide the MySQL user table DDL statement:

CREATE TABLE map_users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(255) NOT NULL,
    email VARCHAR(255) NOT NULL,
    ageINT
);

Next, provide the relevant pom.xml file configuration:

<dependencies>
    <!-- Spring Boot Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.4</version>
    </dependency>

    <!-- Spring Boot Web Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Thymeleaf -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
  
    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

    <!-- MySQL Connector -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
</dependencies>

Next, provide the application.properties file property configuration:

# Database connection configuration
spring.datasource.url=jdbc:mysql://localhost:3306/your_database_name
spring.datasource.username=your_username
spring.datasource.password=your_password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# Enable JPA SQL log
spring.jpa.show-sql=true

# Use ConcurrentMapCache as cache provider (default)
spring.cache.type=simple

# Thymeleaf configuration
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.mode=HTML5

Next, provide a User entity class:

package com.icoderoad.example.user.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import lombok.Data;

@Entity
@Data
@Table(name="map_users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String email;
    private int age;

}

Then, create a UserRepository interface:

package com.icoderoad.example.user.repository;

import org.springframework.data.repository.CrudRepository;

import com.icoderoad.example.user.entity.User;

public interface UserRepository extends CrudRepository<User, Long> {
}

Create a UserService class:

package com.icoderoad.example.user.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import com.icoderoad.example.user.entity.User;
import com.icoderoad.example.user.repository.UserRepository;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;
    
    @Cacheable("users")
    public Iterable<User> findAll() {
        return userRepository.findAll();
    }
    
    @CacheEvict(value = "users", allEntries = true)
    public void save(User user) {
        userRepository.save(user);
        
    }
    
    public User findById( Long id) {
         User user = userRepository.findById(id).orElse(null);
         return user;
    }
    
    @CacheEvict(value = "users", allEntries = true)
    public void deleteById(Long id) {
        userRepository.deleteById(id);
    }
}

Next, create a UserController controller class:

package com.icoderoad.example.user.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
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 com.icoderoad.example.user.service.UserService;
import com.icoderoad.example.user.entity.User;

@Controller
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/")
    public String index(Model model) {
        model.addAttribute("users", userService.findAll());
        return "index";
    }

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

    @PostMapping("/add")
    public String addUserSubmit(@ModelAttribute User user) {
        userService.save(user);
        return "redirect:/";
    }

    @GetMapping("/edit/{id}")
    public String editUserForm(@PathVariable Long id, Model model) {
        User user = userService.findById(id);
        if (user != null) {
            model.addAttribute("user", user);
            return "edit";
        } else {
            return "redirect:/";
        }
    }

    @PostMapping("/edit/{id}")
    public String editUserSubmit(@PathVariable Long id, @ModelAttribute User user) {
        user.setId(id);
        userService.save(user);
        return "redirect:/";
    }

    @GetMapping("/delete/{id}")
    public String deleteUser(@PathVariable Long id) {
        userService.deleteById(id);
        return "redirect:/";
    }
}

Application startup class

package com.icoderoad.example.user;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@EnableCaching
@SpringBootApplication
public class UserSimpleApplication {

    public static void main(String[] args) {
        SpringApplication.run(UserSimpleApplication.class, args);
    }

}

Picture

Create Thymeleaf template files (index.html, add.html, edit.html)

User list page index.html:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>User list</title>
    <link href="https://cdn.bootcdn.net/ajax/libs/bootstrap/5.0.0/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="container">
        <h1 class="mt-4">User list</h1>
        <table class="table table-bordered mt-3">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>Username</th>
                    <th>Email</th>
                    <th>Age</th>
                    <th>Operation</th>
                </tr>
            </thead>
            <tbody>
                <tr th:each="user : ${users}">
                    <td th:text="${user.id}"></td>
                    <td th:text="${user.username}"></td>
                    <td th:text="${user.email}"></td>
                    <td th:text="${user.age}"></td>
                    <td>
                        <a th:href="@{'/edit/' + ${user.id}}" class="btn btn-primary btn-sm">Edit</a>
                        <a th:href="@{'/delete/' + ${user.id}}" class="btn btn-danger btn-sm">Delete</a>
                    </td>
                </tr>
            </tbody>
        </table>
        <a href="/add" class="btn btn-success">Add user</a>
    </div>
</body>
</html>

User add page add.html:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Add user</title>
    <link href="https://cdn.bootcdn.net/ajax/libs/bootstrap/5.0.0/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="container">
        <h1 class="mt-4">Add user</h1>
        <form th:action="@{/add}" method="post">
            <div class="form-group">
                <label for="username">Username</label>
                <input type="text" class="form-control" id="username" name="username" required>
            </div>
            <div class="form-group">
                <label for="email">Email</label>
                <input type="email" class="form-control" id="email" name="email" required>
            </div>
            <div class="form-group">
                <label for="age">Age</label>
                <input type="number" class="form-control" id="age" name="age" required>
            </div>
            <button type="submit" class="btn btn-primary">Add</button>
        </form>
    </div>
</body>
</html>

User edit page edit.html:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Edit User</title>
    <link href="https://cdn.bootcdn.net/ajax/libs/bootstrap/5.0.0/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="container">
        <h1 class="mt-4">Edit user</h1>
        <form th:action="@{/edit/{id}}" th:object="${user}" method="post">
            <input type="hidden" th:field="*{id}">
            <div class="form-group">
                <label for="username">Username</label>
                <input type="text" class="form-control" id="username" th:field="*{username}" required>
            </div>
            <div class="form-group">
                <label for="email">Email</label>
                <input type="email" class="form-control" id="email" th:field="*{email}" required>
            </div>
            <div class="form-group">
                <label for="age">Age</label>
                <input type="number" class="form-control" id="age" th:field="*{age}" required>
            </div>
            <button type="submit" class="btn btn-primary">Save</button>
        </form>
    </div>
</body>
</html>

These template files use Bootstrap styles to beautify the page, and use Thymeleaf tags to bind data and handle form submissions.

Add 10 user data when the system is initialized

Create a new class, name it DataInitializer, and add the following code:

package com.icoderoad.example.user.init;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import com.icoderoad.example.user.entity.User;
import com.icoderoad.example.user.repository.UserRepository;

@Component
public class DataInitializer implements CommandLineRunner {

    private final UserRepository userRepository;

    @Autowired
    public DataInitializer(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public void run(String... args) {
        if( userRepository.count() == 0 ) {
            //Add 10 initial user data
            for (int i = 1; i <= 10; i + + ) {
                User user = new User();
                user.setUsername("User" + i);
                user.setEmail("user" + i + "@example.com");
                user.setAge(25 + i);
                userRepository.save(user);
            }
        }
    }
}

In the above code, we created a DataInitializer class that implements the CommandLineRunner interface, and added 10 initial user data in the run method.

Start the application:

Run the Spring Boot application and visit http://localhost:8080/ to display the user list page and view the console to see the executed sql log records. Refresh the page again. There is no sql log in the console, indicating that the data has been read from ConcurrentHashMap.

The complete code in the example can be obtained from the following URL:

https://gitee.com/jlearning/wechatdemo.git

https://github.com/icoderoad/wxdemo.git

That’s it for today. If you have any questions and need consultation, you can leave a message directly or scan the QR code below to follow the official account. You can also add happyzjp WeChat to be invited to join the learning community, and we will try our best to answer your questions. The practice website has been officially launched. You can log in to the website http://www.icoderoad.com to practice the examples in the article.