Easymall项目分布式拆分整合(五)
目录
1.maven quickstart(springboot)
3.product工程还需要访问持久层,redis(common依赖的传递)
1.ProductController(之前的Colltroller被前端使用了,需要重写)
一.搭建后台商品系统
1.1搭建步骤
1.maven quickstart(springboot)
2.继承parent,依赖common
3.product工程还需要访问持久层,redis(common依赖的传递)
<dependencies>
<dependency>
<groupId>cn..tedu</groupId>
<artifactId>springboot-common-easymall</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
</dependencies>
4.application.properties
server.port=8092
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql:///easydb?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
spring.datasource.password=root
spring.datasource.username=root
mybatis.configuration.mapUnderscoreToCamelCase=true
mybatis.mapperLocations=classpath:mapper/*.xml
mybatis.typeAliasesPackage=com.jt.common.pojo
redis.nodes=10.9.151.60:6379
redis.maxTotal=200
redis.maxIdle=8
redis.minIdle=3
5.添加一个redis底层连接池的配置类
1.代码模板
/**
* 在当前工程扫描范围之内的configuration配置类
* 读取前缀是redis的所有key值,根据私有属性名称的getter/stter
* 赋值给属性
* @author TEDU
*
*/
@Configuration
@ConfigurationProperties(prefix="redis")
public class RedisCumConfiguration {
//根据前缀读取数据,私有属性名称,必须和
//properties中的值相同
private String nodes;
private Integer maxTotal;
private Integer maxIdle;
private Integer minIdle;
//编写初始化JedisShardPool对象的方法,@Bean将返回对象
//作为框架管理的bean
@Bean
public ShardedJedisPool initJedisPool(){
//利用本类中读取的属性,创建连接池对象
//先做一个config对象
GenericObjectPoolConfig config=new GenericObjectPoolConfig();
config.setMaxIdle(maxIdle);
config.setMaxTotal(maxTotal);
//config.setMinIdle(minIdle);
//解析nodes,生成一个list对象
//准备一个空内容
List<JedisShardInfo> infoList=new ArrayList<JedisShardInfo>();
String[] node = nodes.split(",");//{"10.9.9.9:6379","10l.9.9.9:6380",""}
for (String hostAndPort : node) {
String host=hostAndPort.split(":")[0];
int port=Integer.parseInt(hostAndPort.split(":")[1]);
infoList.add(new JedisShardInfo(host, port));
}
//list,config,构造连接池对象返回
return new ShardedJedisPool(config,infoList);
}
public String getNodes() {
return nodes;
}
public void setNodes(String nodes) {
this.nodes = nodes;
}
public Integer getMaxTotal() {
return maxTotal;
}
public void setMaxTotal(Integer maxTotal) {
this.maxTotal = maxTotal;
}
public Integer getMaxIdle() {
return maxIdle;
}
public void setMaxIdle(Integer maxIdle) {
this.maxIdle = maxIdle;
}
public Integer getMinIdle() {
return minIdle;
}
public void setMinIdle(Integer minIdle) {
this.minIdle = minIdle;
}
}
2.具体位置
6.nginx配置文件和hosts映射
1.nginx配置文件
2.hosts配置文件
7.商品后台系统的启动类StarterProduct
@SpringBootApplication
@MapperScan("com.jt.product.mapper")
public class StarterProduct {
public static void main(String[] args) {
SpringApplication.run(StarterProduct.class, args);
}
}
二.根据接口文件实现后台查询分页逻辑
1.接口文件
请求地址 | |
请求参数 | Integer page,Integer rows |
请求方式 | get提交 |
相应数据 | Page对象,totalPage总页数,currentPage当前页数,products分页查询结果; {"total":56,"currentPage":2;"products":[{},{},{},{},{}]} |
2.准备好所有内容(红色内容从以前项目粘贴过来)
1.ProductController(之前的Colltroller被前端使用了,需要重写)
@RestController
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping("product/queryByPage")
public Page queryByPage(Integer page,Integer rows){
return productService.queryByPage(page, rows);
}
//根据商品id查询某个商品对象
@RequestMapping("product/queryById/{productId}")
public Product queryById(@PathVariable String productId){
return productService.queryById(productId);
}
//查询管理商品页面的分页数据
@RequestMapping("product/manage/queryByPage")
public EasyUIResult queryManageByPage(Integer page,Integer rows){
return productService.queryByPageManage(page, rows);
}
//新增商品数据到数据库
@RequestMapping("product/saveProduct")
//productName=**&productPrice=**&productCategory=**
public Integer saveProduct(Product product){
try{
productService.saveProduct(product);
return 1;
}catch(Exception e){
e.printStackTrace();
return 0;
}
}
@RequestMapping("product/updateProduct")
public Integer updateProduct(Product product){
try{
productService.updateProductById(product);
return 1;
}catch(Exception e){
e.printStackTrace();
return 0;
}
}
}
2.ProductService
- 修改包com.jt.product.service;
- 重新导入Page的包com.jt.common.vo
- 重新导入ProductMapper的包
3.ProductMapper
- 修改包路径com.jt.product.mapper
- 重新导入所有的新包Product com.jt.common.product
4.ProductMapper.xml
- namespace="com.jt.product.mapper.ProductMapper"
5.运行代码
二.首页单个商品根据id查询
1.接口文件
请求地址 | |
请求参数 | 路径传参String productId |
请求方式 | get提交 |
相应数据 | Product对象的json字符串 |
2.ProductController
根据接口文件内容编写方法和返回值
//根据商品id查询某个商品对象
@RequestMapping("product/queryById/{productId}")
public Product queryById(@PathVariable String productId){
return productService.queryById(productId);
}
3.ProductService
1.缓存逻辑的使用
引入缓存技术redis,生成productKey
2.判断Key是否早redis存在,
- 存在:直接返回redis数据解析的对象
- 不存:到数据库查询,存放到redis中,供后续使用返回对象
3.redis缓存数据需要和数据库保持一致;
查询缓存高并发下,会发生在更新删除缓存之后,由于高并发的查询导致还没有更新数据之前,就由别的用户将缓存个重新添加了旧数据,最终执行更新导致缓存和数据库数据不一致
- 基本思路:在系统对数据库做改操作时,一并将缓存数据修改,覆盖删除
- 可以在更新数据时,删除缓存之前添加一个更新的锁product_productId.update.lock删除缓存完毕,更新数据库完毕,lock释放(超时时间)
- 查询的逻辑:
- 判断是否锁:有锁,不使用缓存,查数据库
- 没有锁:说明没有更新操作,执行缓存逻辑
@Autowired
private RedisCumUtils jedis;
public Product queryById(String productId) {
//定义更新锁的key值
String productLockKey="product_"+productId+".lock";
//定义缓存的key
String productKey="product_"+productId;
//先判断锁的存在
try{
if(jedis.isExist(productLockKey)){
//说明有人正在更新,不能操作缓存
return productMapper.queryById(productId);
}else{//正常执行缓存逻辑
if(jedis.isExist(productKey)){//缓存有数据
String productJson=jedis.query(productKey);
return MapperUtils.MP.readValue(productJson,
Product.class);
}else{
//查询数据库,并且再缓存中添加数据
Product product=productMapper.queryById(productId);
String json=MapperUtils.MP.writeValueAsString(product);
jedis.addOrUpdate(productKey, json);
return product;
}
}
}catch(Exception e){
e.printStackTrace();
return null;
}
}
三.商品数据的查询后台分页
请求地址 | http://product.easymall.com/product/manage/queryByPage?page=1&rows=5 |
请求参数 | Integer page,Integer rows |
请求方式 | get提交 |
相应数据 | EasyUIResult rows:分查询结果 total:总数量
|
1.ProductController
//查询管理商品页面的分页数据
@RequestMapping("product/manage/queryByPage")
public EasyUIResult queryManageByPage(Integer page,Integer rows){
return productService.queryByPageManage(page, rows);
}
2.ProductService
public EasyUIResult queryByPageManage(Integer page, Integer rows) {
EasyUIResult result=new EasyUIResult();
//总条数,count查询
int total=productMapper.queryTotal();
result.setTotal(total);
//查询的分页结果,计算start
int start=(page-1)*rows;
List<Product> pList = productMapper.queryByPage(start, rows);
result.setpList(pList);
return result;
}
3.分页数据的缓存考虑问题;
- 也可以做缓存,缓存逻辑和商品id查询逻辑一样
- key设计根据page和rows的值定义分页的缓存数据
- 分页数据太庞大了,直接使用String类型的数据无法高效实现
- 一旦出现了新增商品,删除商品,整体的所有分页数据都错误
四.商品数据的新增
请求地址 | |
请求参数 | Product product(productName=**&productPrice=**&) |
请求方式 | post |
相应数据 | 如果两个系统对接需要传递详细信息 1返回一个SysResult status代表各种状态数字,data,msg传递复杂的信息 2 返回一个int字符串1成功,0表示失败 |
1.ProductController
//新增商品数据到数据库
@RequestMapping("product/saveProduct")
//productName=**&productPrice=**&productCategory=**
public Integer saveProduct(Product product){
try{
productService.saveProduct(product);
return 1;
}catch(Exception e){
e.printStackTrace();
return 0;
}
}
2.ProductService
可以在新增数据商品到数据库时,直接添加缓存逻辑,但是由于重要的步骤是新增数据库数据,可以将缓存新增异步执行;
public void saveProduct(Product product) {
//新增调用持久层之前,将数据补充完整
product.setProductId(UUID.randomUUID().toString());
productMapper.saveProduct(product);
/*
rabbit.convertAndSend("dir","saveKeyRedis",product.getProductId())
*/
}
- rabbitmq,将本来应该同步执行的redis缓存添加逻辑
- 封装成消息形式,异步获取消息执行缓存添加
五.商品数据的修改(更新锁)
请求地址 | |
请求参数 | Product product(productName=**&productPrice=**&) |
请求方式 | post |
相应数据 | 如果两个系统对接需要传递详细信息 1返回一个SysResult status代表各种状态数字,data,msg传递复杂的信息 2 返回一个int字符串1成功,0表示失败 |
1.ProductController
@RequestMapping("product/updateProduct")
public Integer updateProduct(Product product){
try{
productService.updateProductById(product);
return 1;
}catch(Exception e){
e.printStackTrace();
return 0;
}
}
2.ProductService 添加更新锁
public void updateProductById(Product product) {
//更新锁添加到redis
String productLockKey="product_"+product.getProductId()+".lock";
//定义缓存的key
String productKey="product_"+product.getProductId();
//添加锁//可以定义锁的超时
jedis.addOrUpdate(productLockKey, "");
//删除已有的缓存
jedis.delete(productKey);
productMapper.updateProduct(product);
//释放锁
jedis.delete(productLockKey);
}