ideaJPA实现mysql_IDEA SpringBoot+JPA+MySql+Redis+RabbitMQ 秒杀系统

先放上github地址:spike-system,可以直接下载完整项目运行测试

SpringBoot+JPA+MySql+Redis+RabbitMQ 秒杀系统

技术栈:SpringBoot, MySql, Redis, RabbitMQ, JPA,(lombok)

Controller

/put : 上架 "watch"商品10个

@RequestMapping("/put")

String put(@RequestParam String orderName, @RequestParam Long count)

/sec : 秒杀购买商品

@RequestMapping("/sec")

String sec(String userName, String orderName)

Guide

项目结构

│ MainSystemApplication.java

├─config

│ MyRabbitConfig.java

│ MyRedisConfig.java

├─controller

│ Test.java

├─dao

│ StockRe.java

│ TOrderRe.java

├─domain

│ Stock.java

│ TOrder.java

└─service

MQOrderService.java

MQStockService.java

RedisService.java

说明

stock代表库存,TORder代表订单。当一个购买行为发生时,应用会先查询购买物品的库存是否足够,如果足够,则将库存减一,并生成一个订单数据到数据库保存,最后返回购买成功的消息给用户

START

首先利用idea创建springboot项目时勾选lombok\web\redis\RabbitMQ\MySql\JPA,生成的pom依赖如下

org.springframework.boot

spring-boot-starter-amqp

org.springframework.boot

spring-boot-starter-data-redis

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-devtools

runtime

true

org.projectlombok

lombok

true

org.springframework.boot

spring-boot-starter-test

test

org.junit.vintage

junit-vintage-engine

org.springframework.amqp

spring-rabbit-test

test

org.springframework.boot

spring-boot-starter-data-jpa

mysql

mysql-connector-java

首先要创建两个实体类到domain包下

package com.chunmiao.mainsystem.domain;

import lombok.Data;

import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.Id;

import java.io.Serializable;

/**

* 库存实体类

*/

@Data

@Entity

public class Stock implements Serializable {

@Id

@GeneratedValue

private Long id;

private String name;

//货品库存数量

private Long stock;

}

package com.chunmiao.mainsystem.domain;

import lombok.AllArgsConstructor;

import lombok.Builder;

import lombok.Data;

import lombok.NoArgsConstructor;

import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.Id;

import java.io.Serializable;

/**

* 订单实体类

*/

@Data

@Entity

@Builder

@NoArgsConstructor

@AllArgsConstructor

public class TOrder implements Serializable{

@Id

@GeneratedValue

private Long id;

private String orderName;

private String orderUser;

}

然后创建jpa的Repository到dao包下。

jpa实现crud的操作只需要继承JPARepository接口即可自动提供find\sava\delete实现类给用户使用。

package com.chunmiao.mainsystem.dao;

import com.chunmiao.mainsystem.domain.TOrder;

import org.springframework.data.jpa.repository.JpaRepository;

import org.springframework.stereotype.Repository;

@Repository

public interface TOrderRe extends JpaRepository {

}

由于JPA默认只提供findById的操作,如果想通过别的字段查询需要自己提供接口,当然,JPA也会为这个接口自动提供实现类

package com.chunmiao.mainsystem.dao;

import com.chunmiao.mainsystem.domain.Stock;

import org.springframework.data.jpa.repository.JpaRepository;

import org.springframework.stereotype.Repository;

@Repository

public interface StockRe extends JpaRepository {

Stock findByName(String name);

}

然后创建config包,配置RabbitMQ和Redis.

RabbitMQ这里涉及到交换机、队列、路由键的概念,需要搜索RabbitMQ基础教程了解一下

package com.chunmiao.mainsystem.config;

import org.springframework.amqp.core.*;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

@Configuration

public class MyRabbitConfig {

public final static String TORDER_EXCHANG = "TORDER_EXCHANG";

public final static String TORDER_QUEUE = "TORDER_QUEUE";

public final static String TORDER_ROUTING_KEY = "TORDER_ROUTING_KEY";

public final static String STOCK_EXCHANG = "STOCK_EXCHANG";

public final static String STOCK_QUEUE = "STOCK_QUEUE";

public final static String STOCK_ROUTING_KEY = "STOCK_ROUTING_KEY";

/**

* 订单消息

* 1.创建交换机

* 2.创建队列

* 3.通过路由键绑定交换机和队列

*/

@Bean

public Exchange getTOrderExchang() {

return ExchangeBuilder.directExchange(TORDER_EXCHANG).build();

}

@Bean

public Queue getTOrderQueue() {

return QueueBuilder.nonDurable(TORDER_QUEUE).build();

}

@Bean

public Binding bindTOrder() {

return BindingBuilder.bind(getTOrderQueue()).to(getTOrderExchang()).with(TORDER_ROUTING_KEY).noargs();

}

/**

* 库存消息

* 1.创建交换机

* 2.创建队列

* 3.通过路由键绑定交换机和队列

*/

@Bean

public Exchange getStockExchange() {

return ExchangeBuilder.directExchange(STOCK_EXCHANG).build();

}

@Bean

public Queue getStockQueue() {

return QueueBuilder.nonDurable(STOCK_QUEUE).build();

}

@Bean

public Binding bindStock() {

return BindingBuilder.bind(getStockQueue()).to(getStockExchange()).with(STOCK_ROUTING_KEY).noargs();

}

}

Redis的配置主要是序列化的配置,应该不配置也可以的。但是配置后查看的时候更美观,而且性能会更好更简洁。

package com.chunmiao.mainsystem.config;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.data.redis.connection.RedisConnectionFactory;

import org.springframework.data.redis.core.RedisTemplate;

import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;

import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration

public class MyRedisConfig {

@Bean

public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){

RedisTemplate re = new RedisTemplate<>();

re.setConnectionFactory(redisConnectionFactory);

re.setKeySerializer(new StringRedisSerializer());

re.setValueSerializer(new Jackson2JsonRedisSerializer<>(Long.class)); // 不能用generic的Serializer,有存Long取Integer的bug

re.afterPropertiesSet();

return re;

}

}

然后到Service层,逻辑是RedisService提供增加库存、查询库存服务,增加库存时需要调用StockRe保存增加库存到数据库。

package com.chunmiao.mainsystem.service;

import com.chunmiao.mainsystem.dao.StockRe;

import com.chunmiao.mainsystem.domain.Stock;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.redis.core.BoundValueOperations;

import org.springframework.data.redis.core.RedisTemplate;

import org.springframework.stereotype.Service;

/**

* stock信息缓存到redis

*/

@Service

public class RedisService {

@Autowired

private RedisTemplate redisTemplate;

@Autowired

private StockRe stockRe;

public void put(String key, Long value) {

BoundValueOperations bp = redisTemplate.boundValueOps(key);

Long count = (Long) bp.get();

if ( count!= null){

count = count >= 0 ? count + value : value;

} else count = value;

bp.set(count);

Stock stock = stockRe.findByName(key);

if (stock == null) {

stock = new Stock();

stock.setName(key);

stock.setStock(0l);

}

long l = stock.getStock() + value;

stock.setStock(l);

stockRe.save(stock);

}

// 返回当前商品库存-1的结果,如果库存小于0时直接返回,这样调用它的类就知道已经没有库存了

public Long decrBy(String key) {

BoundValueOperations bp = redisTemplate.boundValueOps(key);

Long count = (Long) bp.get();

if (count == null) return -1l;

if (count >= 0) {

count--;

bp.set(count);

}

return count;

}

}

MQOrderService用于消费队列中的订单消息,创建新订单保存到数据库。只需要使用@RabbitMQListener即可实现监听

package com.chunmiao.mainsystem.service;

import com.chunmiao.mainsystem.dao.TOrderRe;

import com.chunmiao.mainsystem.domain.TOrder;

import org.springframework.amqp.rabbit.annotation.RabbitListener;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import static com.chunmiao.mainsystem.config.MyRabbitConfig.TORDER_QUEUE;

import static com.chunmiao.mainsystem.config.MyRabbitConfig.STOCK_QUEUE;

/**

* 从MQ中拿消息,创建一个新订单到数据库

*/

@Service

public class MQOrderService {

@Autowired

private TOrderRe orderRe;

@RabbitListener(queues = TORDER_QUEUE)

public void saveOrder(TOrder order) {

System.out.println("创建新订单");

orderRe.save(order);

}

}

同理,监听库存消息并修改数据库值

package com.chunmiao.mainsystem.service;

import com.chunmiao.mainsystem.dao.StockRe;

import com.chunmiao.mainsystem.domain.Stock;

import org.springframework.amqp.rabbit.annotation.RabbitListener;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import static com.chunmiao.mainsystem.config.MyRabbitConfig.STOCK_QUEUE;

/**

* 从MQ拿消息,使库存减少一个

*/

@Service

public class MQStockService {

@Autowired

private StockRe stockRe;

@RabbitListener(queues = STOCK_QUEUE)

public void decrStock(String orderName) {

System.out.println("减少数据库的库存");

Stock stock = stockRe.findByName(orderName);

if (stock!= null) {

stock.setStock(stock.getStock() - 1);

stockRe.save(stock);

}

}

}

最后一个类,controller,提供接口。购买逻辑是直接调用redisService提供的方法,实现操作 库存-1,返回该结果,如果结果>=0说明库存充足,发送创建新订单和库存-1消息给RabbitMQ,然后直接返回购买成功的结果给用户即可;如果库存不足,直接返回购买失败即可。使用RabbitMQ的好处是,只需要关心是否有库存,然后简单的发送消息之后就不用再管了,同时做到了削峰的好处

package com.chunmiao.mainsystem.controller;

import com.chunmiao.mainsystem.domain.TOrder;

import com.chunmiao.mainsystem.service.RedisService;

import org.springframework.amqp.rabbit.core.RabbitTemplate;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestParam;

import org.springframework.web.bind.annotation.RestController;

import static com.chunmiao.mainsystem.config.MyRabbitConfig.*;

@RestController

public class Test {

@Autowired

private RedisService redisService;

@Autowired

private RabbitTemplate rabbitTemplate;

@RequestMapping("/put")

String put(@RequestParam String orderName, @RequestParam Long count) {

redisService.put(orderName, count);

return "上架商品\n" + orderName + ":" + count;

}

@RequestMapping("/sec")

String sec(String userName, String orderName) {

String msg = "秒杀用户:" + userName + "\n" + "秒杀商品: " + orderName;

System.out.println("\n---------------------------------------------");

System.out.println("秒杀用户:" + userName + "\n" + "秒杀商品: " + orderName);

Long count = redisService.decrBy(orderName);

// 秒杀成功

System.out.println("当前商品数量为: " + (count + 1));

if (count >= 0) {

System.out.println("库存充足");

// 创建新订单

rabbitTemplate.convertAndSend(TORDER_EXCHANG,TORDER_ROUTING_KEY,

TOrder.builder()

.orderName(orderName)

.orderUser(userName)

.build());

// 创建库存-1消息

rabbitTemplate.convertAndSend(STOCK_EXCHANG,STOCK_ROUTING_KEY,orderName);

System.out.println("秒杀成功");

msg += "成功";

} else {

System.out.println("库存不足");

msg += "失败";

}

return msg;

}

}

最后可以运行起来,先put一个商品进去,再利用Jmeter进行压力测试,Jmeter的使用可以参考本文参考的文章。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
大学生参加学科竞赛有着诸多好处,不仅有助于个人综合素质的提升,还能为未来职业发展奠定良好基础。以下是一些分析: 首先,学科竞赛是提高专业知识和技能水平的有效途径。通过参与竞赛,学生不仅能够深入学习相关专业知识,还能够接触到最新的科研成果和技术发展趋势。这有助于拓展学生的学科视野,使其对专业领域有更深刻的理解。在竞赛过程中,学生通常需要解决实际问题,这锻炼了他们独立思考和解决问题的能力。 其次,学科竞赛培养了学生的团队合作精神。许多竞赛项目需要团队协作来完成,这促使学生学会有效地与他人合作、协调分工。在团队合作中,学生们能够学到如何有效沟通、共同制定目标和分工合作,这对于日后进入职场具有重要意义。 此外,学科竞赛是提高学生综合能力的一种途径。竞赛项目通常会涉及到理论知识、实际操作和创新思维等多个方面,要求参赛者具备全面的素质。在竞赛过程中,学生不仅需要展现自己的专业知识,还需要具备创新意识和解决问题的能力。这种全面的综合能力培养对于未来从事各类职业都具有积极作用。 此外,学科竞赛可以为学生提供展示自我、树立信心的机会。通过比赛的舞台,学生有机会展现自己在专业领域的优势,得到他人的认可和赞誉。这对于培养学生的自信心和自我价值感非常重要,有助于他们更加积极主动地投入学习和未来的职业生涯。 最后,学科竞赛对于个人职业发展具有积极的助推作用。在竞赛中脱颖而出的学生通常能够引起企业、研究机构等用人单位的关注。获得竞赛奖项不仅可以作为个人履历的亮点,还可以为进入理想的工作岗位提供有力的支持。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值