Redis in a Spring Boot School Management System: A Journey

Redis in a Spring Boot School Management System: A Journey

Software Development
Sep 7, 2024
5-6 min

Share blog

Introduction

I was recently assigned to a school management project for a client in Riyadh, Saudi Arabia, by my company. The project was built using Spring Boot for the backend and Angular for the frontend. As a member of the backend team, I was excited to dive into this project and work on building a robust system that could handle the complex requirements of managing a school’s data and operations.

The project journey was engaging, filled with challenges and learning opportunities. However, one thing quickly became clear: the amount of data we were pulling from the database on almost every page was substantial. Information like existing grade years, courses, and academic years was needed everywhere-from the dashboard to student profiles to report cards. Each time a user navigated to a different section of the app, we were hitting the database repeatedly for the same data. It wasn’t long before I realized that this approach was not only inefficient but also putting unnecessary strain on our database.

This is where Redis came into the picture.

Redis: Why It Was the Right Choice

Redis is an in-memory data structure store that’s often used as a cache. It’s incredibly fast because it stores data in memory, meaning retrieval times are minimal compared to querying a database. For our school management system, caching this commonly accessed data made perfect sense. Instead of making recurring queries to the database, we could fetch the necessary information from Redis, speeding up our application and reducing the load on our database.

Implementing Redis in Spring Boot:

Creating the Redis Service Interface

To keep things clean and reusable, I started by creating a Redis service interface that would handle basic operations like saving, retrieving, and deleting data in Redis.

javascript
1public interface RedisService<T extends BaseDto> {
2 String generateKey(T entity);
3 void save(T t, Long expiry);
4 Object get(String key);
5 void delete(String key);
6}

Creating the Redis Service Interface

Next, I implemented this interface in a RedisServiceImpl class. This is where the actual interaction with Redis happens. By extending this class, other services in the application can easily cache and retrieve data without worrying about the underlying Redis operations.

javascript
1@Service
2public class RedisServiceImpl<T extends BaseDto> implements RedisService<T> {
3
4 protected final RedisTemplate<String, Object> redisTemplate;
5 protected final ObjectMapper objectMapper;
6 public RedisServiceImpl(RedisTemplate<String, Object> redisTemplate,
7 ObjectMapper objectMapper) {
8 this.redisTemplate = redisTemplate;
9 this.objectMapper = objectMapper;
10 }
11
12 @Override
13 public void save(T entity, Long expiry) {
14 String key = generateKey(entity);
15 redisTemplate.opsForValue().set(key, entity);
16 if (expiry != null) {
17 redisTemplate.expire(key, expiry, TimeUnit.SECONDS);
18 }
19 }
20
21 @Override
22 public Object get(String key) {
23 return redisTemplate.opsForValue().get(key);
24 }
25
26 @Override
27 public void delete(String key) {
28 redisTemplate.delete(key);
29 }
30}

Specialized Service for Grade Years and Courses

One of the key areas where Redis caching was particularly beneficial was in managing grade years and their associated courses. For this, I extended the RedisServiceImpl class in a new service, YearsWithCoursesService. This service not only managed the caching of grade years and courses but also provided methods to retrieve and sort them.

javascript
1@Service
2public class YearsWithCoursesService extends RedisServiceImpl<YearDto> {
3 public YearsWithCoursesService(RedisTemplate<String, Object> redisTemplate,
4 ObjectMapper objectMapper) {
5 super(redisTemplate, objectMapper);
6 }
7
8 @Override
9 public String generateKey(YearDto yearDto) {
10 return "year:" + yearDto.getId();
11 }
12
13 public YearDto getYear(Long yearId) {
14 String key = "year:" + yearId;
15 Object value = get(key);
16 if (value == null) {
17 return null;
18 }
19 return objectMapper.convertValue(value, YearDto.class);
20 }
21
22 public List<YearDto> getYears() {
23 List<YearDto> yearDtos = new ArrayList<>();
24 Set<String> keys = redisTemplate.keys("year*");
25
26 if (keys != null) {
27 for (String key : keys) {
28 Object value = redisTemplate.opsForValue().get(key);
29 if (value != null) {
30 YearDto yearDto = objectMapper.convertValue(value, YearDto.class);
31 yearDtos.add(yearDto);
32 }
33 }
34 }
35
36 return yearDtos.stream().sorted(Comparator.comparing(YearDto::getId)).toList();
37 }
38}

Overcoming Roadblocks: Challenges Faced and How I Tackled Them

Running Redis on a Windows Machine

One of the unexpected challenges I faced during the project was that Redis is primarily designed to run on Unix-based systems like Ubuntu. However, I was developing on a Windows machine, which meant I couldn’t just install Redis directly as I would on Linux.

Solution: Using Windows Subsystem for Linux (WSL)

To get Redis up and running on my Windows machine, I decided to use the Windows Subsystem for Linux (WSL). WSL allows you to run a Linux distribution on Windows, which gave me the flexibility to install and manage Redis just as if I were on a Linux machine. If you would like to know more about how to set up Redis on Windows using WSL check out my blog [reference to blog] !

Let's Build Something Great Together

Ready to transform your idea into a powerful software solution? Talk to our experts and get a free consultation.

Contact Us

This setup allowed me to develop and test the Redis integration on my Windows machine without any issues. The use of WSL bridged the gap between the Unix-based nature of Redis and the Windows environment I was working in.

Handling Data Inconsistency

One of the challenges I anticipated was ensuring that the cached data in Redis remained consistent with the database. Since Redis doesn’t automatically sync with the database, I needed a strategy to handle updates, particularly when a user added or modified grade years or courses.

To solve this, I employed a cache invalidation strategy. Whenever there was a change in the database, I forced the system to refresh the Redis cache by setting a forceDB flag. Here’s how I implemented this in a YearService class:

javascript
1@Service
2public class YearService {
3 private final YearsWithCoursesService yearsWithCoursesService;
4
5 @Transactional(readOnly = true)
6 public List<YearDto> findYearsWithCourses(Long schoolId, boolean forceDB) {
7
8 List<YearDto> yearDtos = new ArrayList<>();
9
10 if (!forceDB) {
11 yearDtos = yearsWithCoursesService.getYears();
12 }
13
14 if (yearDtos.isEmpty()) {
15 List<Year> years = yearRepository.findAll();
16 List<Course> courses = courseRepository.findByYearIdIn(
17 years.stream().map(Year::getId).toList());
18
19 Map<Long, List<CourseDto>> coursesByYear = courses.stream()
20 .collect(Collectors.groupingBy(
21 course -> course.getYear().getId(),
22 Collectors.mapping(
23 itm -> createCourse(itm, schoolId),
24 Collectors.toList()
25 )
26 ));
27
28 yearDtos = yearMapper.yearsToYearDtos(years);
29 List<YearDto> existingYearDtos = yearsWithCoursesService.getYears();
30 for (YearDto dto : existingYearDtos) {
31 String key = yearsWithCoursesService.generateKey(dto);
32 yearsWithCoursesService.delete(key);
33 }
34 for (YearDto yearDto : yearDtos) {
35 yearDto.setCourses(coursesByYear.get(yearDto.getId()));
36 if (yearDto.getCourses() == null) {
37 yearDto.setCourses(new ArrayList<>());
38 }
39 yearDto.setStudentsCount(yearDto.getCourses().stream()
40 .map(CourseDto::getStudentsCount).reduce(0, Integer::sum));
41 yearsWithCoursesService.save(yearDto, null);
42 }
43 } else {
44 yearDtos = yearsWithCoursesService.getYears();
45
46 }
47
48 return yearDtos;
49 }
50}

In this setup, if the forceDB flag is set to true, the service fetches fresh data from the database, deletes the outdated cache entries, and saves the updated information in Redis. This way, the application always serves the most accurate and up-to-date data to the users.

Final Thoughts

Using Redis in our Spring Boot project significantly improved the performance of our school management system. By caching frequently accessed data like grade years, courses, and academic years we reduced the load on the database and sped up response times for end users. Implementing the Redis service as a reusable component allowed us to easily manage caching throughout the application.

While there were challenges, like ensuring data consistency and handling serialization, the overall experience was positive. Redis proved to be a powerful tool that, when used correctly, can greatly enhance the performance of any data-intensive application. If you’re dealing with similar challenges, I highly recommend considering Redis as a solution. It’s fast, flexible, and relatively easy to integrate into a Spring Boot project.

And that’s it! I hope you enjoyed joining me on this journey through Redis and Spring Boot and found some useful insights and maybe even a bit of inspiration for your own projects. Happy coding!

Blogs

Discover the latest insights and trends in technology with the Omax Tech Blog.

View All Blogs
Omax | Blog | How to Add LiveKit Video Calling to a Next.js App
12-14 min
September 11, 2026

How to Add LiveKit Video Calling to a Next.js App

Add embedded video & audio calling to Next.js with LiveKit Cloud. Compared vs Twilio, Daily, Agora, Zoom — plus token auth, guests & recording.

Read More
Omax | Blog | We chose ECS over EKS: what we gained and what we gave up
8-10 min
September 10, 2026

We chose ECS over EKS: what we gained and what we gave up

An honest comparison of ECS vs EKS the costs, tradeoffs, and real-world reasoning behind choosing ECS for a production platform on AWS.

Read More
Omax | Blog | Upgrading Legacy Systems: From Outdated Technology to Competitive Advantage
8-10 min
September 07, 2026

Upgrading Legacy Systems: From Outdated Technology to Competitive Advantage

Learn how to upgrade legacy systems through application modernization, API integration, cloud migration, security improvements, and incremental system upgrades without disrupting business operations.

Read More
Omax | Blog | Building Distributed Tracing and Observability with AWS X-Ray
12-14 min
September 04, 2026

Building Distributed Tracing and Observability with AWS X-Ray

A practical guide to correlating requests across a multi-tier application using correlation IDs, AWS X-Ray segments, and structured logging for faster incident debugging.

Read More
Omax | Blog | Designing Before and After AI: What Really Changed
6-7 min
September 03, 2026

Designing Before and After AI: What Really Changed

A look at how AI has transformed UI/UX design from manual wireframes and slow research to AI-assisted prototyping, design-to-code, and personalization at scale.

Read More
Omax | Blog | Beyond Prompting: Managing Context and Tokens in AI Coding Tools
12-14 min
September 03, 2026

Beyond Prompting: Managing Context and Tokens in AI Coding Tools

Ever wondered why your AI coding agent starts losing context or hits a hard limit mid-task? The answer lies in tokens and the context window. Good AI coding is not about giving the model the most information. It is about giving it the right information at the right time.

Read More
Omax | Blog | What Is llms.txt? How It Helps Google, AI Search, and Agentic Browsing Find Your Website
10-12 min
August 31, 2026

What Is llms.txt? How It Helps Google, AI Search, and Agentic Browsing Find Your Website

Learn what llms.txt is, how it differs from sitemap.xml and robots.txt, and how it can help your site get found by Google, AI search tools, and AI agents.

Read More
Omax | Blog | Build an Automated Image Compression Script with Sharp and SVGO
7-8 min
August 28, 2026

Build an Automated Image Compression Script with Sharp and SVGO

Compress images from the terminal with a Node.js script powered by Sharp and SVGO a safe, two-step workflow that keeps your site fast without bloating your repo.

Read More
Omax | Blog | The Right Way to Migrate from MySQL to AWS Aurora DSQL
7-8 min
August 25, 2026

The Right Way to Migrate from MySQL to AWS Aurora DSQL

Migrating a production database is one of the highest-risk changes you can make to an application. Moving from MySQL to AWS Aurora DSQL raises the stakes further...

Read More