Build a High‑Performance Lottery System with Spring Boot, MyBatis‑Plus & Redis

This tutorial walks through creating a Spring Boot lottery application that caches activity, prize, and record data in Redis, covering project setup, Maven dependencies, YML configuration, MyBatis‑Plus code generation, Redis key management, lottery API, event‑driven data loading, stock handling, and asynchronous record saving.

Java Backend Technology
Java Backend Technology
Java Backend Technology
Build a High‑Performance Lottery System with Spring Boot, MyBatis‑Plus & Redis

1. Project Introduction

This is a simple example based on Spring Boot, MyBatis‑Plus and Redis. Activity content, prize information and draw records are cached in Redis, and the entire lottery process operates on data stored in Redis.

2. Project Demo

Below are screenshots of the running project.

3. Table Structure

The project contains four tables: activity, prize, award and draw‑record. The SQL statements are provided at the end of the article.

4. Project Setup

4.1 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
      validation-query: SELECT 1 FROM DUAL
  redis:
    host: 127.0.0.1
    port: 6379
    lettuce:
      pool:
        max-active: -1
        max-idle: 2000
        max-wait: -1
        min-idle: 1
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    auto-mapping-behavior: full
  mapper-locations: classpath*:mapper/**/*Mapper.xml
async:
  executor:
    thread:
      core-pool-size: 6
      max-pool-size: 12
      queue-capacity: 100000
      name-prefix: lottery-service-

4.3 Code Generation

public class MybatisPlusGeneratorConfig {
    public static void main(String[] args) {
        AutoGenerator mpg = new AutoGenerator();
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath + "/src/main/java");
        gc.setAuthor("chen");
        gc.setOpen(false);
        gc.setSwagger2(false);
        mpg.setGlobalConfig(gc);
        // data source, package, strategy, template configuration omitted for brevity
        mpg.execute();
    }
    public static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入" + tip + ":");
        if (scanner.hasNext()) {
            String ipt = scanner.next();
            if (StringUtils.isNotEmpty(ipt)) {
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "!");
    }
}

4.4 Redis Configuration

@Configuration
public class RedisTemplateConfig {
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);
        template.setValueSerializer(serializer);
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        template.afterPropertiesSet();
        return template;
    }
}

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 enum PrizeTypeEnum { THANK(-1), NORMAL(1), UNIQUE(2); private int value; PrizeTypeEnum(int v){this.value=v;} public int getValue(){return value;} }
    // other constants omitted for brevity
}

4.6 Business Code

4.6.1 Lottery API

@GetMapping("/{id}")
public ResultResp<LotteryItemVo> doDraw(@PathVariable("id") Integer id, HttpServletRequest request) {
    String accountIp = CusAccessObjectUtil.getIpAddress(request);
    ResultResp<LotteryItemVo> resp = new ResultResp<>();
    try {
        checkDrawParams(id, accountIp);
        DoDrawDto dto = new DoDrawDto();
        dto.setAccountIp(accountIp);
        dto.setLotteryId(id);
        lotteryService.doDraw(dto);
        resp.setCode(ReturnCodeEnum.SUCCESS.getCode());
        resp.setMsg(ReturnCodeEnum.SUCCESS.getMsg());
        resp.setResult(lotteryConverter.dto2LotteryItemVo(dto));
    } catch (Exception e) {
        return ExceptionUtil.handlerException4biz(resp, e);
    } finally {
        redisTemplate.delete(RedisKeyManager.getDrawingRedisKey(accountIp));
    }
    return resp;
}

The API uses Redis SETNX to prevent duplicate draws from the same user.

4.6.2 Data Initialization (Event‑Driven)

public class InitPrizeToRedisEvent extends ApplicationEvent {
    private Integer lotteryId;
    private CountDownLatch countDownLatch;
    public InitPrizeToRedisEvent(Object source, Integer lotteryId, CountDownLatch latch) {
        super(source);
        this.lotteryId = lotteryId;
        this.countDownLatch = latch;
    }
    // getters and setters omitted
}
@Component
public class InitPrizeToRedisListener implements ApplicationListener<InitPrizeToRedisEvent> {
    @Autowired RedisTemplate redisTemplate;
    @Autowired LotteryPrizeMapper prizeMapper;
    @Autowired LotteryItemMapper itemMapper;
    @Override
    public void onApplicationEvent(InitPrizeToRedisEvent ev) {
        Boolean first = redisTemplate.opsForValue().setIfAbsent(RedisKeyManager.getLotteryPrizeRedisKey(ev.getLotteryId()), "1");
        if (!first) { ev.getCountDownLatch().countDown(); return; }
        // load items and prizes from DB and store them as HASHes in Redis
        // ... omitted for brevity ...
        ev.getCountDownLatch().countDown();
    }
}

4.6.3 Lottery Core Logic

private LotteryItem doPlay(Lottery lottery) {
    List<LotteryItem> items = (List<LotteryItem>) redisTemplate.opsForValue().get(RedisKeyManager.getLotteryItemRedisKey(lottery.getId()));
    if (items == null) { items = lotteryItemMapper.selectList(new QueryWrapper<>().eq("lottery_id", lottery.getId())); }
    if (items.isEmpty()) { throw new BizException(...); }
    Collections.shuffle(items);
    Map<Integer, int[]> scope = new HashMap<>();
    int last = 0;
    for (LotteryItem it : items) {
        int cur = last + new BigDecimal(it.getPercent()).multiply(new BigDecimal(1000)).intValue();
        scope.put(it.getId(), new int[]{last + 1, cur});
        last = cur;
    }
    int lucky = new Random().nextInt(1000);
    int prizeId = 0;
    for (Map.Entry<Integer, int[]> e : scope.entrySet()) {
        if (lucky >= e.getValue()[0] && lucky <= e.getValue()[1]) { prizeId = e.getKey(); break; }
    }
    for (LotteryItem it : items) { if (it.getId() == prizeId) return it; }
    return null;
}

4.6.4 Stock Adjustment and Record

public class HasStockRewardProcessor extends AbstractRewardProcessor {
    @Override
    protected void processor(RewardContext ctx) {
        Long left = redisTemplate.opsForHash().increment(ctx.getKey(), "validStock", -1);
        if (left.intValue() < 0) { throw new UnRewardException(...); }
        List<Object> props = redisTemplate.opsForHash().multiGet(ctx.getKey(), Arrays.asList("id", "prizeName"));
        ctx.setPrizeId(Integer.parseInt(props.get(0).toString()));
        ctx.setPrizeName(props.get(1).toString());
        lotteryPrizeMapper.updateValidStock(ctx.getPrizeId());
    }
    @Override
    protected void afterProcessor(RewardContext ctx) {
        asyncLotteryRecordTask.saveLotteryRecord(ctx.getAccountIp(), ctx.getLotteryItem(), ctx.getPrizeName());
    }
    // getAwardType omitted
}

4.6.5 Asynchronous Record Saving

@Component
public class AsyncLotteryRecordTask {
    @Autowired LotteryRecordMapper recordMapper;
    @Async("lotteryServiceExecutor")
    public void saveLotteryRecord(String ip, LotteryItem item, String prizeName) {
        LotteryRecord rec = new LotteryRecord();
        rec.setAccountIp(ip);
        rec.setItemId(item.getId());
        rec.setPrizeName(prizeName);
        rec.setCreateTime(LocalDateTime.now());
        recordMapper.insert(rec);
    }
}

4.7 Summary

The article demonstrates how to build a complete lottery system, from front‑end interaction to back‑end implementation, using Spring Boot, MyBatis‑Plus, Redis, event‑driven data loading, distributed locking, and asynchronous processing.

5. Project Repository

https://gitee.com/cl1429745331/redis-demo

Remember to adjust the activity end time in the database before running the project.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaLottery SystemBackend DevelopmentredisSpring Bootmybatis-plus
Java Backend Technology
Written by

Java Backend Technology

Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!

0 followers
Reader feedback

How this landed with the community

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.