Backend Development 23 min read

Building a Spring Boot Lottery System with Redis and MyBatis‑Plus

This article demonstrates how to create a simple lottery application using Spring Boot, MyBatis‑Plus, and Redis, covering project setup, database schema, dependency configuration, Redis integration, event‑driven data initialization, lottery draw logic, stock management, and asynchronous record handling.

Top Architect
Top Architect
Top Architect
Building a Spring Boot Lottery System with Redis and MyBatis‑Plus

This tutorial walks through the implementation of a lottery system based on Spring Boot, MyBatis‑Plus, and Redis. The project caches activity, prize, and record data in Redis and performs the entire draw process using Redis operations.

1. Project Introduction

The demo is a Spring Boot application that stores activity content, prize information, and draw records in Redis, allowing the entire lottery process to be performed with Redis data operations.

2. Project Demonstration

After reviewing the UI effect, the article dives into the implementation details.

3. Database Schema

The system uses four tables: activity, prize, prize item, and draw record. The SQL statements are provided at the end of the article.

4. Project Setup

Start a standard Spring Boot project in IDEA and add the required dependencies:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.3</version>
    </dependency>
    ... (other dependencies omitted for brevity) ...
</dependencies>

4.2 YML Configuration

server:
  port: 8080
  servlet:
    context-path: /
spring:
  datasource:
    druid:
      url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver
      initial-size: 30
      max-active: 100
      min-idle: 10
      max-wait: 60000
  redis:
    port: 6379
    host: 127.0.0.1
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    auto-mapping-behavior: full
async:
  executor:
    thread:
      core-pool-size: 6
      max-pool-size: 12
      queue-capacity: 100000
      name-prefix: lottery-service-

4.4 Redis Configuration

@Configuration
public class RedisTemplateConfig {
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate
redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        // configure ObjectMapper ...
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

4.5 Constant Management

public class LotteryConstants {
    public static final String DRAWING = "DRAWING";
    public static final String LOTTERY = "LOTTERY";
    public static final String LOTTERY_PRIZE = "LOTTERY_PRIZE";
    public static final String DEFAULT_LOTTERY_PRIZE = "DEFAULT_LOTTERY_PRIZE";
    public static final String LOTTERY_ITEM = "LOTTERY_ITEM";
    public static final String DEFAULT_LOTTERY_ITEM = "DEFAULT_LOTTERY_ITEM";
    public enum PrizeTypeEnum { THANK(-1), NORMAL(1), UNIQUE(2); ... }
}

4.6 Business Logic

The draw API checks if the user has an unfinished draw using setIfAbsent (NX) on Redis, then proceeds with the draw, records the result, and finally clears the flag.

@GetMapping("/{id}")
public ResultResp
doDraw(@PathVariable("id") Integer id, HttpServletRequest request) {
    String accountIp = CusAccessObjectUtil.getIpAddress(request);
    // set NX flag for 60 seconds
    Boolean result = redisTemplate.opsForValue().setIfAbsent(RedisKeyManager.getDrawingRedisKey(accountIp), "1", 60, TimeUnit.SECONDS);
    if (!result) {
        throw new RewardException(ReturnCodeEnum.LOTTER_DRAWING.getCode(), ReturnCodeEnum.LOTTER_DRAWING.getMsg());
    }
    // call service, handle exceptions, finally delete flag
    redisTemplate.delete(RedisKeyManager.getDrawingRedisKey(accountIp));
    return resultResp;
}

The draw process loads prize data from Redis (or DB if missing), shuffles items, builds probability intervals, generates a random number, and selects the winning prize.

private LotteryItem doPlay(Lottery lottery) {
    // load items from Redis or DB
    // build intervals based on item.getPercent()
    int luckyNumber = new Random().nextInt(mulriple);
    // find matching interval and return the item
}

Reward processing uses a strategy pattern. AbstractRewardProcessor defines a template method; concrete processors handle stock deduction and record insertion. Stock deduction is performed atomically via opsForHash().increment(..., -1) . If stock becomes negative, an UnRewardException triggers a fallback to a default prize.

public class HasStockRewardProcessor extends AbstractRewardProcessor {
    @Override
    protected void processor(RewardContext context) {
        Long result = redisTemplate.opsForHash().increment(context.getKey(), "validStock", -1);
        if (result.intValue() < 0) {
            throw new UnRewardException(...);
        }
        // update DB stock and set prize info in context
    }
    @Override
    protected void afterProcessor(RewardContext context) {
        asyncLotteryRecordTask.saveLotteryRecord(context.getAccountIp(), context.getLotteryItem(), context.getPrizeName());
    }
}

Asynchronous record saving uses Spring's @Async with a custom thread pool defined in the YML and a ThreadPoolTaskExecutor bean.

@Async("lotteryServiceExecutor")
public void saveLotteryRecord(String accountIp, LotteryItem lotteryItem, String prizeName) {
    LotteryRecord record = new LotteryRecord();
    record.setAccountIp(accountIp);
    record.setItemId(lotteryItem.getId());
    record.setPrizeName(prizeName);
    record.setCreateTime(LocalDateTime.now());
    lotteryRecordMapper.insert(record);
}

The article also shows an event‑driven initialization ( InitPrizeToRedisEvent and listener) that loads prize data into Redis at draw time, and an ApplicationRunner alternative for loading data on application startup.

5. Project Repository

Source code: https://gitee.com/cl1429745331/redis-demo . Adjust the activity end time in the database before running.

backendjavaRedisSpring BootMyBatis-Pluslottery
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn 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.