Backend Development 13 min read

Building a Supplier Management System with SpringBoot, Thymeleaf, MyBatis‑Plus, and Redis Caching

This article walks through the design, implementation, and optimization of a complete supplier management system built with SpringBoot, Thymeleaf, MyBatis‑Plus, MySQL, and Redis, covering project setup, configuration, core Java code, file upload handling, and caching strategies.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Building a Supplier Management System with SpringBoot, Thymeleaf, MyBatis‑Plus, and Redis Caching

Difficulty Analysis

Although the author was initially shocked by the scope of a full‑featured management system, the overall workflow (design → documentation → coding → delivery) proved straightforward, allowing the core system to be built in about one and a half days, with an additional half day for optimization.

Project Review

Final Effect Demo:

Technical Stack

SpringBoot

Thymeleaf

MyBatis‑Plus

MySQL

PageHelper

Lombok

Redis (used later for page optimization)

Business Process Overview

The system includes login, user management with role assignment, news announcement management, product management (including categories and orders), and role‑based access control using Thymeleaf expressions, as well as basic file upload functionality.

Project Setup (Using a Template Engine)

1. Create Maven Project

Add the required dependencies and create the standard directory structure.

2. Write yaml configuration file

server:
  port: 8080

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/supplier?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
    username: root
    password: root
  # thymeleaf configuration
  thymeleaf:
    cache: false
    prefix: classpath:/templates/

mybatis-plus:
  mapper-locations: classpath*:/mapper/**/*.xml

3. Initial Project Setup

Configure common infrastructure such as exception handling, interceptors, filters, and constant classes.

Exception handling

@ControllerAdvice
public class ExceptionHandler {

    private final org.slf4j.Logger logger = LoggerFactory.getLogger(this.getClass());

    @org.springframework.web.bind.annotation.ExceptionHandler(Exception.class)
    public ModelAndView exception(HttpServletRequest request, Exception e) throws Exception {
        logger.error("Request URL:{},Exception:{}", request.getRequestURL(), e);
        if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
            throw e;
        }
        ModelAndView mv = new ModelAndView();
        mv.addObject("url", request.getRequestURL());
        mv.addObject("exception", e);
        mv.setViewName("error/error");
        return mv;
    }
}

Interceptor

Used to protect resources that require login.

public class LoginInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (request.getSession().getAttribute("user") == null) {
            response.sendRedirect("/api");
            return false;
        }
        return true;
    }
}

Register the interceptor:

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api", "/api/doLogin");
    }
}

4. Write Controller (FileController)

File upload page navigation:

// Navigate to file‑upload page
@RequestMapping("/file-upload")
public String userList() {
    return "file-upload";
}

File upload handling (single file):

@RequestMapping("/doAddForUser")
public String doAdd(User user, @RequestParam("file") MultipartFile files, HttpServletRequest request) throws IOException {
    if (files != null && !files.isEmpty()) {
        String name = UUID.randomUUID().toString().replace("-", "");
        String ext = FilenameUtils.getExtension(files.getOriginalFilename());
        String url = request.getSession().getServletContext().getRealPath("/upload/");
        File dir = new File(url);
        if (!dir.exists()) {
            dir.mkdir();
        }
        files.transferTo(new File(url + "/" + name + "." + ext));
        user.setAvatar(request.getContextPath() + "/upload/" + name + "." + ext);
    }
    // password encryption and other fields omitted for brevity
    userService.save(user);
    return "redirect:/api/users";
}

Note: Multi‑file upload code is provided but not used in this project.

private void commons(Object obj, @RequestParam("file") CommonsMultipartFile[] files, HttpServletRequest request) throws IOException {
    for (int i = 0; i < files.length; i++) {
        if (files[i] != null && !files[i].isEmpty()) {
            String name = UUID.randomUUID().toString().replace("-", "");
            String ext = FilenameUtils.getExtension(files[i].getOriginalFilename());
            String url = request.getSession().getServletContext().getRealPath("/upload/");
            File dir = new File(url);
            if (!dir.exists()) {
                dir.mkdir();
            }
            files[i].transferTo(new File(url + "/" + name + "." + ext));
            // set URL fields on obj according to index i
        }
    }
}

5. Project Optimization (Redis Page Caching)

For non‑SPA projects, page‑level caching reduces database load during traffic spikes.

Import Redis dependencies

org.springframework.boot
spring-boot-starter-data-redis
org.apache.commons
commons-pool2

Redis yaml configuration

## Redis configuration
redis:
  host: localhost
  port: 6379
  database: 0
  connect-timeout: 10000ms
  lettuce:
    pool:
      max-active: 8
      max-wait: 10000ms
      max-idle: 200
      min-idle: 5

Redis serialization bean

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate
redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate
redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }
}

Cache the rendered HTML of the news list page:

@Autowired
private NewsService newsService;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private ThymeleafViewResolver viewResolver;

@RequestMapping(value = "/news", produces = "text/html;charset=utf-8")
@ResponseBody
public String roles(Model model, @RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
                    @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
                    HttpServletRequest request, HttpServletResponse response) {
    ValueOperations valueOps = redisTemplate.opsForValue();
    String html = (String) valueOps.get("news-list");
    if (!StringUtils.isEmpty(html)) {
        return html;
    }
    PageHelper.startPage(pageNo, pageSize);
    List
list = newsService.list();
    PageInfo
pageInfo = new PageInfo<>(list);
    model.addAttribute("news", list);
    model.addAttribute("pageInfo", pageInfo);
    WebContext context = new WebContext(request, response, request.getServletContext(), request.getLocale(), model.asMap());
    html = viewResolver.getTemplateEngine().process("news-list", context);
    if (!StringUtils.isEmpty(html)) {
        valueOps.set("news-list", html, 60, TimeUnit.SECONDS);
    }
    return html;
}

6. Remarks

Distinguish between @Controller (returns view pages) and @RestController (returns JSON). When caching whole pages, add @ResponseBody so the rendered HTML can be stored as a JSON string in Redis.

All source code has been synchronized to Gitee: https://gitee.com/gao-wumao/supplier . Feel free to clone the repository.

backendJavaRedisSpringBootMyBatis-PlusThymeleafproject-setup
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.