SpringBoot + Thymeleaf Supplier Management System: Design, Implementation, and Redis Optimization
This article details the end‑to‑end development of a supplier management system using SpringBoot, Thymeleaf, Mybatis‑Plus, MySQL and Redis, covering project planning, code implementation, file upload handling, caching optimization, and deployment considerations.
The author describes the complete workflow of building a supplier management system, starting from design, documentation, coding, and delivery, and notes that the entire project was completed by a single developer within two days.
Technology stack includes SpringBoot, Thymeleaf, Mybatis‑Plus, MySQL, PageHelper, Lombok, and Redis for later page‑level optimization.
The business flow covers login, user and role management, news announcements, product and order modules, and permission checks using Thymeleaf expressions.
Project setup begins with creating a Maven project and adding required dependencies, followed by configuring application.yml :
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:
# disable cache
cache: false
prefix: classpath:/templates/
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xmlKey infrastructure such as exception handling and interceptors are implemented:
@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;
}
} 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;
}
} @Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api", "/api/doLogin");
}
}File upload functionality is provided through a controller:
// Navigate to upload page
@RequestMapping("/file-upload")
public String userList(){
return "file-upload";
}
@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 file = new File(url);
if (!file.exists()) { file.mkdir(); }
files.transferTo(new File(url + "/" + name + "." + ext));
user.setAvatar(request.getContextPath() + "/upload/" + name + "." + ext);
}
user.setId(UUID.randomUUID().toString());
String salt = PasswordUtils.getSalt();
String encode = PasswordUtils.encode(user.getPassword(), salt);
user.setSalt(salt);
user.setPassword(encode);
user.setCreateTime(new Date());
userService.save(user);
return "redirect:/api/users";
}A multi‑file upload method is also shown (though not used in the final 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 file = new File(url);
if (!file.exists()) { file.mkdir(); }
files[i].transferTo(new File(url + "/" + name + "." + ext));
if (i == 0) obj.setUrl1(request.getContextPath() + "/upload/" + name + "." + ext);
if (i == 1) obj.setUrl2(request.getContextPath() + "/upload/" + name + "." + ext);
if (i == 2) obj.setUrl3(request.getContextPath() + "/upload/" + name + "." + ext);
if (i == 3) obj.setUrl4(request.getContextPath() + "/upload/" + name + "." + ext);
if (i == 4) obj.setUrl5(request.getContextPath() + "/upload/" + name + "." + ext);
}
}
}To improve performance, the author adds Redis caching for page content. Required Maven dependencies:
org.springframework.boot
spring-boot-starter-data-redis
org.apache.commons
commons-pool2Redis configuration in application.yml :
## 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: 5RedisTemplate bean for serialization:
@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;
}
}Controller method that caches the rendered HTML of a news list page in Redis for 60 seconds:
@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 valueOperations = redisTemplate.opsForValue();
String html = (String) valueOperations.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)) {
valueOperations.set("news-list", html, 60, TimeUnit.SECONDS);
}
return html;
}Additional notes clarify the difference between @Controller (template rendering) and @RestController (JSON response), and remind developers to add @ResponseBody when caching full pages as JSON strings.
The complete source code is available on Gitee at https://gitee.com/gao-wumao/supplier , and readers are encouraged to clone the repository.
Java Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.