Spring Boot Lottery System with Redis and MyBatis-Plus: Project Overview and Implementation Guide
This article presents a step‑by‑step guide to building a Spring Boot lottery application that leverages MyBatis‑Plus and Redis for caching activity, prize, and record data, covering project setup, dependencies, configuration, code generation, Redis integration, lottery logic, and stock management.
1. Project Introduction
This is a simple example based on Spring Boot, MyBatis‑Plus and Redis.
The activity content, prize information and record information are cached in Redis, and the entire lottery process operates on data stored in Redis.
The overall content is straightforward; the specific operations are analyzed below.
2. Project Demo
First, take a look at the screenshots of the project effect. If it looks acceptable, we will examine the implementation details.
3. Database Schema
The project contains four tables: activity, prize, award and winning‑record tables. The specific SQL statements are provided at the end of the article.
4. Project Setup
First, create a standard Spring Boot project using IDEA and select the required dependencies.
4.1 Dependencies
The project mainly uses Redis, Thymeleaf, MyBatis‑Plus and other 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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.72</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.22</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.2.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>1.4.2.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.4.2.Final</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.6</version>
</dependency>
</dependencies>4.2 YML Configuration
After adding the dependencies, configure database connection, Redis, MyBatis‑Plus, thread pool, etc.
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
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
filters: stat,wall
redis:
port: 6379
host: 127.0.0.1
lettuce:
pool:
max-active: -1
max-idle: 2000
max-wait: -1
min-idle: 1
time-between-eviction-runs: 5000
mvc:
view:
prefix: classpath:/templates/
suffix: .html
# mybatis-plus
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
auto-mapping-behavior: full
mapper-locations: classpath*:mapper/**/*Mapper.xml
# thread pool
async:
executor:
thread:
core-pool-size: 6
max-pool-size: 12
queue-capacity: 100000
name-prefix: lottery-service-4.3 Code Generation
Use MyBatis‑Plus code generator to create basic CRUD code, reducing repetitive work.
public class MybatisPlusGeneratorConfig {
public static void main(String[] args) {
// Code generator
AutoGenerator mpg = new AutoGenerator();
// Global config
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 config
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://127.0.0.1:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
mpg.setDataSource(dsc);
// Package config
PackageConfig pc = new PackageConfig();
pc.setParent("com.example.lottery");
pc.setEntity("dal.model");
pc.setMapper("dal.mapper");
pc.setService("service");
pc.setServiceImpl("service.impl");
mpg.setPackageInfo(pc);
// Template config
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
// Strategy config
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setSuperEntityClass("com.baomidou.mybatisplus.extension.activerecord.Model");
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
strategy.setInclude(scanner("lottery,lottery_item,lottery_prize,lottery_record").split(","));
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotEmpty(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
}4.4 Redis Configuration
If you use RedisTemplate in code, you need to add a configuration class to inject it into the Spring container.
@Configuration
public class RedisTemplateConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate
redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// Use Jackson2JsonRedisSerializer to replace the default serializer
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(DateTime.class, new JodaDateTimeJsonSerializer());
simpleModule.addDeserializer(DateTime.class, new JodaDateTimeJsonDeserializer());
objectMapper.registerModule(simpleModule);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// Set value and key serializers
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
class JodaDateTimeJsonSerializer extends JsonSerializer
{
@Override
public void serialize(DateTime dateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString(dateTime.toString("yyyy-MM-dd HH:mm:ss"));
}
}
class JodaDateTimeJsonDeserializer extends JsonDeserializer
{
@Override
public DateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
String dateString = jsonParser.readValueAs(String.class);
DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
return dateTimeFormatter.parseDateTime(dateString);
}
}4.5 Constant Management
Common constants used across the code are extracted into a dedicated class.
public class LotteryConstants {
/**
* Flag for a user currently drawing
*/
public final static String DRAWING = "DRAWING";
/**
* Activity flag: LOTTERY:lotteryID
*/
public final static String LOTTERY = "LOTTERY";
/**
* Prize data: LOTTERY_PRIZE:lotteryID:PrizeId
*/
public final static String LOTTERY_PRIZE = "LOTTERY_PRIZE";
/**
* Default prize data: DEFAULT_LOTTERY_PRIZE:lotteryID
*/
public final static String DEFAULT_LOTTERY_PRIZE = "DEFAULT_LOTTERY_PRIZE";
public enum PrizeTypeEnum {
THANK(-1), NORMAL(1), UNIQUE(2);
private int value;
private PrizeTypeEnum(int value) { this.value = value; }
public int getValue() { return this.value; }
}
/**
* Prize cache: LOTTERY_ITEM:LOTTERY_ID
*/
public final static String LOTTERY_ITEM = "LOTTERY_ITEM";
/**
* Default prize: DEFAULT_LOTTERY_ITEM:LOTTERY_ID
*/
public final static String DEFAULT_LOTTERY_ITEM = "DEFAULT_LOTTERY_ITEM";
} public enum ReturnCodeEnum {
SUCCESS("0000", "成功"),
LOTTER_NOT_EXIST("9001", "指定抽奖活动不存在"),
LOTTER_FINISH("9002", "活动已结束"),
LOTTER_REPO_NOT_ENOUGHT("9003", "当前奖品库存不足"),
LOTTER_ITEM_NOT_INITIAL("9004", "奖项数据未初始化"),
LOTTER_DRAWING("9005", "上一次抽奖还未结束"),
REQUEST_PARAM_NOT_VALID("9998", "请求参数不正确"),
SYSTEM_ERROR("9999", "系统繁忙,请稍后重试");
private String code;
private String msg;
private ReturnCodeEnum(String code, String msg) { this.code = code; this.msg = msg; }
public String getCode() { return code; }
public String getMsg() { return msg; }
public String getCodeString() { return getCode() + ""; }
}Redis keys are managed uniformly through a helper class.
public class RedisKeyManager {
public static String getDrawingRedisKey(String accountIp) {
return new StringBuilder(LotteryConstants.DRAWING).append(":").append(accountIp).toString();
}
public static String getLotteryRedisKey(Integer id) {
return new StringBuilder(LotteryConstants.LOTTERY).append(":").append(id).toString();
}
public static String getLotteryPrizeRedisKey(Integer lotteryId) {
return new StringBuilder(LotteryConstants.LOTTERY_PRIZE).append(":").append(lotteryId).toString();
}
public static String getLotteryPrizeRedisKey(Integer lotteryId, Integer prizeId) {
return new StringBuilder(LotteryConstants.LOTTERY_PRIZE).append(":").append(lotteryId).append(":").append(prizeId).toString();
}
public static String getDefaultLotteryPrizeRedisKey(Integer lotteryId) {
return new StringBuilder(LotteryConstants.DEFAULT_LOTTERY_PRIZE).append(":").append(lotteryId).toString();
}
public static String getLotteryItemRedisKey(Integer lotteryId) {
return new StringBuilder(LotteryConstants.LOTTERY_ITEM).append(":").append(lotteryId).toString();
}
public static String getDefaultLotteryItemRedisKey(Integer lotteryId) {
return new StringBuilder(LotteryConstants.DEFAULT_LOTTERY_ITEM).append(":").append(lotteryId).toString();
}
}4.6 Business Code
4.6.1 Lottery API
The controller receives the lottery ID from the front‑end, validates the request, performs the draw, and returns the result.
@GetMapping("/{id}")
public ResultResp
doDraw(@PathVariable("id") Integer id, HttpServletRequest request) {
String accountIp = CusAccessObjectUtil.getIpAddress(request);
log.info("begin LotteryController.doDraw, access user {}, lotteryId,{}:", accountIp, id);
ResultResp
resultResp = new ResultResp<>();
try {
// Check whether the previous draw has finished
checkDrawParams(id, accountIp);
// Perform draw
DoDrawDto dto = new DoDrawDto();
dto.setAccountIp(accountIp);
dto.setLotteryId(id);
lotteryService.doDraw(dto);
// Set response
resultResp.setCode(ReturnCodeEnum.SUCCESS.getCode());
resultResp.setMsg(ReturnCodeEnum.SUCCESS.getMsg());
resultResp.setResult(lotteryConverter.dto2LotteryItemVo(dto));
} catch (Exception e) {
return ExceptionUtil.handlerException4biz(resultResp, e);
} finally {
// Clear the drawing flag
redisTemplate.delete(RedisKeyManager.getDrawingRedisKey(accountIp));
}
return resultResp;
}
private void checkDrawParams(Integer id, String accountIp) {
if (null == id) {
throw new RewardException(ReturnCodeEnum.REQUEST_PARAM_NOT_VALID.getCode(), ReturnCodeEnum.REQUEST_PARAM_NOT_VALID.getMsg());
}
// Use setNx to ensure the previous draw has finished
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());
}
}Redis setNx prevents duplicate draw requests from the same user.
4.6.2 Data Initialization
After the request passes validation, the service loads prize data into Redis using an event‑driven approach.
@Override
public void doDraw(DoDrawDto drawDto) throws Exception {
RewardContext context = new RewardContext();
LotteryItem lotteryItem = null;
try {
CountDownLatch countDownLatch = new CountDownLatch(1);
// Validate activity
Lottery lottery = checkLottery(drawDto);
// Publish event to load prize data into Redis
applicationContext.publishEvent(new InitPrizeToRedisEvent(this, lottery.getId(), countDownLatch));
// Perform draw
lotteryItem = doPlay(lottery);
// Wait for prize data initialization
countDownLatch.await();
String key = RedisKeyManager.getLotteryPrizeRedisKey(lottery.getId(), lotteryItem.getPrizeId());
int prizeType = Integer.parseInt(redisTemplate.opsForHash().get(key, "prizeType").toString());
context.setLottery(lottery);
context.setLotteryItem(lotteryItem);
context.setAccountIp(drawDto.getAccountIp());
context.setKey(key);
// Adjust stock and record winning info
AbstractRewardProcessor.rewardProcessorMap.get(prizeType).doReward(context);
} catch (UnRewardException u) {
// No prize, return default item
context.setKey(RedisKeyManager.getDefaultLotteryPrizeRedisKey(lotteryItem.getLotteryId()));
lotteryItem = (LotteryItem) redisTemplate.opsForValue().get(RedisKeyManager.getDefaultLotteryItemRedisKey(lotteryItem.getLotteryId()));
context.setLotteryItem(lotteryItem);
AbstractRewardProcessor.rewardProcessorMap.get(LotteryConstants.PrizeTypeEnum.THANK.getValue()).doReward(context);
}
// Populate response data
drawDto.setLevel(lotteryItem.getLevel());
drawDto.setPrizeName(context.getPrizeName());
drawDto.setPrizeId(context.getPrizeId());
}The event InitPrizeToRedisEvent carries the lottery ID and a CountDownLatch to synchronize the initialization.
public class InitPrizeToRedisEvent extends ApplicationEvent {
private Integer lotteryId;
private CountDownLatch countDownLatch;
public InitPrizeToRedisEvent(Object source, Integer lotteryId, CountDownLatch countDownLatch) {
super(source);
this.lotteryId = lotteryId;
this.countDownLatch = countDownLatch;
}
public Integer getLotteryId() { return lotteryId; }
public void setLotteryId(Integer lotteryId) { this.lotteryId = lotteryId; }
public CountDownLatch getCountDownLatch() { return countDownLatch; }
public void setCountDownLatch(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; }
}The listener loads activity, prize and item data from the database into Redis, using HASH structures for atomic stock operations.
@Slf4j
@Component
public class InitPrizeToRedisListener implements ApplicationListener
{
@Autowired
RedisTemplate redisTemplate;
@Autowired
LotteryPrizeMapper lotteryPrizeMapper;
@Autowired
LotteryItemMapper lotteryItemMapper;
@Override
public void onApplicationEvent(InitPrizeToRedisEvent event) {
log.info("begin InitPrizeToRedisListener," + event);
Boolean result = redisTemplate.opsForValue().setIfAbsent(RedisKeyManager.getLotteryPrizeRedisKey(event.getLotteryId()), "1");
if (!result) {
log.info("already initial");
event.getCountDownLatch().countDown();
return;
}
// Load items
QueryWrapper
itemWrapper = new QueryWrapper<>();
itemWrapper.eq("lottery_id", event.getLotteryId());
List
items = lotteryItemMapper.selectList(itemWrapper);
LotteryItem defaultItem = items.parallelStream().filter(o -> o.getDefaultItem().intValue() == 1).findFirst().orElse(null);
Map
itemMap = new HashMap<>(16);
itemMap.put(RedisKeyManager.getLotteryItemRedisKey(event.getLotteryId()), items);
itemMap.put(RedisKeyManager.getDefaultLotteryItemRedisKey(event.getLotteryId()), defaultItem);
redisTemplate.opsForValue().multiSet(itemMap);
// Load prizes
QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.eq("lottery_id", event.getLotteryId());
List
prizes = lotteryPrizeMapper.selectList(queryWrapper);
AtomicReference
defaultPrize = new AtomicReference<>();
prizes.forEach(prize -> {
if (prize.getId().equals(defaultItem.getPrizeId())) {
defaultPrize.set(prize);
}
String key = RedisKeyManager.getLotteryPrizeRedisKey(event.getLotteryId(), prize.getId());
setLotteryPrizeToRedis(key, prize);
});
String defaultKey = RedisKeyManager.getDefaultLotteryPrizeRedisKey(event.getLotteryId());
setLotteryPrizeToRedis(defaultKey, defaultPrize.get());
event.getCountDownLatch().countDown();
log.info("finish InitPrizeToRedisListener," + event);
}
private void setLotteryPrizeToRedis(String key, LotteryPrize prize) {
redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
redisTemplate.opsForHash().put(key, "id", prize.getId());
redisTemplate.opsForHash().put(key, "lotteryId", prize.getLotteryId());
redisTemplate.opsForHash().put(key, "prizeName", prize.getPrizeName());
redisTemplate.opsForHash().put(key, "prizeType", prize.getPrizeType());
redisTemplate.opsForHash().put(key, "totalStock", prize.getTotalStock());
redisTemplate.opsForHash().put(key, "validStock", prize.getValidStock());
}
}Alternatively, an ApplicationRunner can load data at application startup.
@Slf4j
@Component
public class LoadDataApplicationRunner implements ApplicationRunner {
@Autowired
RedisTemplate redisTemplate;
@Autowired
LotteryMapper lotteryMapper;
@Override
public void run(ApplicationArguments args) throws Exception {
log.info("=========begin load lottery data to Redis===========");
Lottery lottery = lotteryMapper.selectById(1);
// Additional loading logic can be added here
log.info("=========finish load lottery data to Redis===========");
}
}4.6.3 Lottery Logic
The draw process first attempts to fetch prize data from Redis; if missing, it falls back to the database and caches the result. It then shuffles the prize list, builds probability intervals, generates a random number, and selects the winning prize.
private LotteryItem doPlay(Lottery lottery) {
LotteryItem lotteryItem = null;
QueryWrapper
queryWrapper = new QueryWrapper<>();
queryWrapper.eq("lottery_id", lottery.getId());
Object lotteryItemsObj = redisTemplate.opsForValue().get(RedisKeyManager.getLotteryItemRedisKey(lottery.getId()));
List
lotteryItems;
if (lotteryItemsObj == null) {
lotteryItems = lotteryItemMapper.selectList(queryWrapper);
} else {
lotteryItems = (List
) lotteryItemsObj;
}
if (lotteryItems.isEmpty()) {
throw new BizException(ReturnCodeEnum.LOTTER_ITEM_NOT_INITIAL.getCode(), ReturnCodeEnum.LOTTER_ITEM_NOT_INITIAL.getMsg());
}
int lastScope = 0;
Collections.shuffle(lotteryItems);
Map
awardItemScope = new HashMap<>();
for (LotteryItem item : lotteryItems) {
int currentScope = lastScope + new BigDecimal(item.getPercent().floatValue()).multiply(new BigDecimal(mulriple)).intValue();
awardItemScope.put(item.getId(), new int[]{lastScope + 1, currentScope});
lastScope = currentScope;
}
int luckyNumber = new Random().nextInt(mulriple);
int luckyPrizeId = 0;
if (!awardItemScope.isEmpty()) {
for (Map.Entry
entry : awardItemScope.entrySet()) {
if (luckyNumber >= entry.getValue()[0] && luckyNumber <= entry.getValue()[1]) {
luckyPrizeId = entry.getKey();
break;
}
}
}
for (LotteryItem item : lotteryItems) {
if (item.getId().intValue() == luckyPrizeId) {
lotteryItem = item;
break;
}
}
return lotteryItem;
}4.6.4 Stock Adjustment and Recording
Different prize types are handled by distinct processors. For normal prizes, the stock is decremented atomically in Redis; if the stock becomes negative, an exception triggers a fallback to a default prize.
public interface RewardProcessor
{
void doReward(RewardContext context);
} public abstract class AbstractRewardProcessor implements RewardProcessor
, ApplicationContextAware {
public static Map
rewardProcessorMap = new ConcurrentHashMap<>();
@Autowired
protected RedisTemplate redisTemplate;
@Override
public void doReward(RewardContext context) {
beforeProcessor(context);
processor(context);
afterProcessor(context);
}
protected abstract void beforeProcessor(RewardContext context);
protected abstract void processor(RewardContext context);
protected abstract void afterProcessor(RewardContext context);
protected abstract int getAwardType();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
rewardProcessorMap.put(LotteryConstants.PrizeTypeEnum.THANK.getValue(), (RewardProcessor) applicationContext.getBean(NoneStockRewardProcessor.class));
rewardProcessorMap.put(LotteryConstants.PrizeTypeEnum.NORMAL.getValue(), (RewardProcessor) applicationContext.getBean(HasStockRewardProcessor.class));
}
}Normal‑stock processor example:
@Override
protected void processor(RewardContext context) {
// Decrement stock atomically in Redis
Long result = redisTemplate.opsForHash().increment(context.getKey(), "validStock", -1);
if (result.intValue() < 0) {
throw new UnRewardException(ReturnCodeEnum.LOTTER_REPO_NOT_ENOUGHT.getCode(), ReturnCodeEnum.LOTTER_REPO_NOT_ENOUGHT.getMsg());
}
List
properties = Arrays.asList("id", "prizeName");
List
prizes = redisTemplate.opsForHash().multiGet(context.getKey(), properties);
context.setPrizeId(Integer.parseInt(prizes.get(0).toString()));
context.setPrizeName(prizes.get(1).toString());
// Update DB stock
lotteryPrizeMapper.updateValidStock(context.getPrizeId());
}After processing, an asynchronous task records the winning information.
@Override
protected void afterProcessor(RewardContext context) {
asyncLotteryRecordTask.saveLotteryRecord(context.getAccountIp(), context.getLotteryItem(), context.getPrizeName());
} @Slf4j
@Component
public class AsyncLotteryRecordTask {
@Autowired
LotteryRecordMapper lotteryRecordMapper;
@Async("lotteryServiceExecutor")
public void saveLotteryRecord(String accountIp, LotteryItem lotteryItem, String prizeName) {
log.info(Thread.currentThread().getName() + "---saveLotteryRecord");
LotteryRecord record = new LotteryRecord();
record.setAccountIp(accountIp);
record.setItemId(lotteryItem.getId());
record.setPrizeName(prizeName);
record.setCreateTime(LocalDateTime.now());
lotteryRecordMapper.insert(record);
}
}Thread‑pool configuration defined in YML and wired via a @Bean.
@Configuration
@EnableAsync
@EnableConfigurationProperties(ThreadPoolExecutorProperties.class)
public class ThreadPoolExecutorConfig {
@Bean(name = "lotteryServiceExecutor")
public Executor lotteryServiceExecutor(ThreadPoolExecutorProperties poolExecutorProperties) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(poolExecutorProperties.getCorePoolSize());
executor.setMaxPoolSize(poolExecutorProperties.getMaxPoolSize());
executor.setQueueCapacity(poolExecutorProperties.getQueueCapacity());
executor.setThreadNamePrefix(poolExecutorProperties.getNamePrefix());
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
} @Data
@ConfigurationProperties(prefix = "async.executor.thread")
public class ThreadPoolExecutorProperties {
private int corePoolSize;
private int maxPoolSize;
private int queueCapacity;
private String namePrefix;
}4.7 Summary
The article walks through the complete setup of a Spring Boot lottery project, from project creation, dependency configuration, database schema, Redis caching, event‑driven data initialization, draw algorithm, stock handling, to asynchronous record persistence. The front‑end simply calls the back‑end API and displays the prize returned.
5. Project Repository
https://gitee.com/cl1429745331/redis-demo
If you use the project directly, remember to adjust the activity end time in the database. The core implementation resides in the lottery module.
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.