springboot+redis实现电商小型案例

springboot+redis实现电商小型案例

  1. 目录以及主要功能:
package com.lqr.learnredis.controller;

import com.lqr.learnredis.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import java.util.HashMap;
import java.util.Map;

@Controller
public class IndexController {

    @Autowired
    private UserService userService;

    /**
     * 目录
     * @return
     */
    @RequestMapping("/index")
    public String index() { return "Index"; }

    /**
     * 填写个人信息
     * @return
     */
    @RequestMapping("/userInfo")
    public String userInfo(){
        return "UserInfo";
    }

    /**
     * 更改个人信息
     * @param name
     * @return
     */
    @RequestMapping("/updateInfo")
    public ModelAndView updateInfo(@RequestParam String name){
        ModelAndView modelAndView = new ModelAndView();
        Map map = userService.selectInfo(name);
        modelAndView.addObject("username",map.get("username"));
        modelAndView.addObject("age",map.get("age"));
        modelAndView.addObject("email",map.get("email"));
        modelAndView.addObject("phoneNumber",map.get("phoneNumber"));
        modelAndView.setViewName("UpdateInfo");
        return modelAndView;
    }

    /**
     * 查询商品
     * @return
     */
    @RequestMapping("/selectgoods")
    public String selectgoods(){
        return "SelectGoods";
    }

    /**
     * 上架商品
     * @return
     */
    @RequestMapping("/releasegoods")
    public String releasegoods(){
        return "ReleaseGoods";
    }

    /**
     * 购买商品
     * @return
     */
    @RequestMapping("/buygoods")
    public String buygoods() { return "BuyGoods"; }

    /**
     * 查看订单
     * @return
     */
    @RequestMapping("/selectorders")
    public String selectorders() { return "SelectOrders"; }
}

主要功能除了以上的还有查看、更新公告,每周(为方便测试目前用的60秒)更新各个类别的销量前五名的产品并显示。

  1. 在pom.xml文件中添加依赖
<dependencies>
        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!--freemarker-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        
        <!-- log4j -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j</artifactId>
            <version>1.3.8.RELEASE</version>
        </dependency>

        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.1.1</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
  1. 在yml中添加需要的配置
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shop1
    username: root
    password: 123456
  redis:
    host: 127.0.0.1
    port: 6379
    database: 0
    timeout: 60s
    jedis:
      pool:
        max-idle: 500
        min-idle: 50
        max-wait:  -1s
        max-active: -1
    password: 123456
    topic: __keyevent@0__:expired
  task:
    pool:
      corePoolSize: 10
      maxPoolSize: 20
      keepAliveSeconds: 60
      queueCapacity: 100
      threadNamePrefix: myThreadPool

mybatis:
  mapper-locations: classpath:mapping/*.xml
  type-aliases-package: com.lqr.learnredis.entity

其中spring.redis.topic和线程池为自定义变量

  1. 添加redis配置及封装redis工具类

redis配置

package com.lqr.learnredis.redis;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
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 RedisConfig {

    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

}

封装redis工具类

package com.lqr.learnredis.redis;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Redis工具类
 * 双重锁单例
 */

@Component
public final class RedisUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // =============================common============================

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     * @return
     */

    public boolean expire(String key, long time) {

        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     *
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */

    public long getExpire(String key) {

        return redisTemplate.getExpire(key, TimeUnit.SECONDS);

    }

    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */

    public boolean hasKey(String key) {

        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */

    @SuppressWarnings("unchecked")

    public void del(String... key) {

        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }


    // ============================String=============================

    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return 值
     */

    public Object get(String key) {

        return key == null ? null : redisTemplate.opsForValue().get(key);

    }


    /**
     * 普通缓存放入
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */

    public boolean set(String key, Object value) {

        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 普通缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */

    public boolean set(String key, Object value, long time) {

        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 递增
     *
     * @param key   键
     * @param delta 要增加几(大于0)
     * @return
     */

    public long incr(String key, long delta) {

        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);

    }


    /**
     * 递减
     *
     * @param key   键
     * @param delta 要减少几(小于0)
     * @return
     */

    public long decr(String key, long delta) {

        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);

    }


    // ================================Map=================================

    /**
     * HashGet
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return 值
     */

    public Object hget(String key, String item) {

        return redisTemplate.opsForHash().get(key, item);

    }


    /**
     * 获取hashKey对应的所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */

    public Map<?, ?> hmget(String key) {

        return redisTemplate.opsForHash().entries(key);

    }


    /**
     * HashSet
     *
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */

    public boolean hmset(String key, Map<?, ?> map) {

        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * HashSet 并设置时间
     *
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */

    public boolean hmset(String key, Map<?, ?> map, long time) {

        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @return true 成功 false失败
     */

    public boolean hset(String key, String item, Object value) {

        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */

    public boolean hset(String key, String item, Object value, long time) {

        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为null
     * @param item 项 可以使多个 不能为null
     */

    public void hdel(String key, Object... item) {

        redisTemplate.opsForHash().delete(key, item);

    }


    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */

    public boolean hHasKey(String key, String item) {

        return redisTemplate.opsForHash().hasKey(key, item);

    }


    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     * @return
     */

    public double hincr(String key, String item, double by) {

        return redisTemplate.opsForHash().increment(key, item, by);

    }


    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     * @return
     */

    public double hdecr(String key, String item, double by) {

        return redisTemplate.opsForHash().increment(key, item, -by);
    }


    // ============================set=============================

    /**
     * 根据key获取Set中的所有值
     *
     * @param key 键
     * @return
     */

    public Set<?> sGet(String key) {

        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */

    public boolean sHasKey(String key, Object value) {

        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */

    public long sSet(String key, Object... values) {

        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 将set数据放入缓存
     *
     * @param key    键
     * @param time   时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */

    public long sSetAndTime(String key, long time, Object... values) {

        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0)
                expire(key, time);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 获取set缓存的长度
     *
     * @param key 键
     * @return
     */

    public long sGetSetSize(String key) {

        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */

    public long setRemove(String key, Object... values) {

        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    // ===============================list=================================


    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0 到 -1代表所有值
     * @return
     */

    public List<?> lGet(String key, long start, long end) {

        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 获取list缓存的长度
     *
     * @param key 键
     * @return
     */

    public long lGetListSize(String key) {

        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 通过索引 获取list中的值
     *
     * @param key   键
     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     * @return
     */

    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 将list放入缓存(从右插入)
     *
     * @param key   键
     * @param value 值
     * @return
     */

    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存(从左插入)
     *
     * @param key   键
     * @param value 值
     * @return
     */

    public boolean lSetLeft(String key, Object value) {
        try {
            redisTemplate.opsForList().leftPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 若当时从右插入则将list作为队列,先进先出,踢出最先进入的元素
     * 若当时从左插入则将list作为栈,先进后出,踢出最后进入的元素
     *
     * @param key
     * @return
     */
    public boolean lLeftPop(String key) {
        try {
            redisTemplate.opsForList().leftPop(key);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 若当时从右插入则将list作为栈,先进后出,踢出最后进入的元素
     * 若当时从左插入则将list作为队列,先进先出,踢出最先进入的元素
     *
     * @param key
     * @return
     */
    public boolean lRightPop(String key) {
        try {
            redisTemplate.opsForList().rightPop(key);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存(从右插入)
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */

    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存(从左插入)
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */

    public boolean lSetLeft(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().leftPush(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 将list放入缓存(整体)
     *
     * @param key   键
     * @param value 值
     * @return
     */

    public boolean lSetall(String key, List<?> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存(整体)
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */

    public boolean lSetall(String key, List<?> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @return
     */

    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 移除N个值为value
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */

    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    // =============================Sortset(Zset)============================

    /**
     * 获取下标从start到end的值,按权值从小到大排序
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<?> range(String key, long start, long end){
        try {
            return redisTemplate.opsForZSet().range(key,start,end);
        }catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取下标从start到end的值,按权值从大到小排序
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<?> reverseRange(String key,long start,long end){
        try {
            return redisTemplate.opsForZSet().reverseRange(key,start,end);
        }catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取权值从start到end的值,按权值从小到大排序
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<?> rangebyscores(String key, long start, long end){
        try {
            return redisTemplate.opsForZSet().rangeByScore(key,start,end);
        }catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取权值从start到end的值,按权值从大到小排序
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<?> reverseRangebyscores(String key,long start,long end){
        try {
            return redisTemplate.opsForZSet().reverseRangeByScore(key,start,end);
        }catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将ZSet数据放入缓存
     *
     * @param key
     * @param value
     * @param scores
     * @return
     */
    public boolean zsSet(String key, Object value, double scores){
        try {
            redisTemplate.opsForZSet().add(key,value,scores);
            return true;
        }catch (Exception e) {
            return false;
        }
    }

}
  1. 填写个人信息和更改个人信息(偷懒没做登录注册,这里直接把用户名写死了)
  • 实体层
package com.lqr.learnredis.entity;

import lombok.Data;

import java.io.Serializable;

@Data
public class User implements Serializable {
    int id;
    String name;
    String password;
}

package com.lqr.learnredis.entity;

import lombok.Data;

import java.io.Serializable;

@Data
public class UserInfo implements Serializable {
    int id;
    String name;
    String username;
    Integer age;
    String email;
    String phoneNumber;
}

  • 视图层
<html>
<head>
    <title>填写个人信息</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<div>填写个人信息</div>
<form name="userInfo" action="/user/insertInfo" method="post">
    name: <input type="text" name="userName"><br/>
    age: <input type="text" name="userAge"><br/>
    email: <input type="text" name="email"><br/>
    phoneNumber: <input type="text" name="phoneNumber"><br/>
    <input type="text" name="name" hidden="hidden" value="lqr">
    <input type="submit" value="提交">
</form>
<a href="/index">目录</a><br>
</body>
</html>

<html>
<head>
    <title>修改个人信息</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<div>修改个人信息</div>
<form name="updateuserInfo" action="/user/updateInfo" method="post">
    name: <input type="text" name="userName" value=${username}><br/>
    age: <input type="text" name="userAge" value=${age}><br/>
    email: <input type="text" name="email" value=${email}><br/>
    phoneNumber: <input type="text" name="phoneNumber" value=${phoneNumber}><br/>
    <input type="text" name="name" hidden="hidden" value="lqr">
    <input type="submit" value="提交">
</form>
<a href="/index">目录</a><br>
</body>
</html>

  • 控制层
package com.lqr.learnredis.controller;

import com.lqr.learnredis.entity.UserInfo;
import com.lqr.learnredis.service.UserService;
import lombok.extern.slf4j.Slf4j;
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 javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 添加用户个人信息
     * @param request
     * @return
     */
    @RequestMapping("/insertInfo")
    public String insertInfo(HttpServletRequest request){
        try {
            UserInfo userInfo = new UserInfo();
            userInfo.setName(request.getParameter("name"));
            userInfo.setAge(Integer.parseInt(request.getParameter("userAge")));
            userInfo.setUsername(request.getParameter("userName"));
            userInfo.setEmail(request.getParameter("email"));
            userInfo.setPhoneNumber(request.getParameter("phoneNumber"));
            userService.insertUserinfo(userInfo);

            return "添加个人信息成功";
        }catch (Exception e) {
            return "添加个人信息错误";
        }
    }
    
    /**
     * 查询用户个人信息
     * @param name
     * @return
     */
    @RequestMapping("/selectinfo")
    public Map selectInfo(@RequestParam String name){
        try {
            return userService.selectInfo(name);
        }catch (Exception e){
            Map map = new HashMap<>();
            map.put("error","查询个人信息错误");
            return map;
        }
    }

    /**
     * 修改个人信息
     * @param request
     * @return
     */
    @RequestMapping("/updateInfo")
    public Map updateInfo(HttpServletRequest request){
        try {
            UserInfo userInfo = new UserInfo();
            userInfo.setName(request.getParameter("name"));
            userInfo.setAge(Integer.parseInt(request.getParameter("userAge")));
            userInfo.setUsername(request.getParameter("userName"));
            userInfo.setEmail(request.getParameter("email"));
            userInfo.setPhoneNumber(request.getParameter("phoneNumber"));
            return userService.updateInfo(userInfo);
        }catch (Exception e) {
            Map map = new HashMap<>();
            map.put("error","更新个人信息错误");
            return map;
        }
    }

}
  • 业务逻辑层
package com.lqr.learnredis.service;

import com.lqr.learnredis.entity.User;
import com.lqr.learnredis.entity.UserInfo;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;

@Service
public interface UserService {

    boolean insertUserinfo(UserInfo userInfo);

	Map selectInfo(String name);

    Map updateInfo(UserInfo userInfo);
}

package com.lqr.learnredis.service.impl;

import com.lqr.learnredis.entity.User;
import com.lqr.learnredis.entity.UserInfo;
import com.lqr.learnredis.mapper.UserMapper;
import com.lqr.learnredis.redis.RedisUtil;
import com.lqr.learnredis.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper usermapper;

    @Autowired
    private RedisUtil redisUtil;
    
    /**
     * 存入用户个人信息,若mysql存入无误,则存入redis并设置过期时间
     * @param userInfo
     * @return
     */
    @Override
    public boolean insertUserinfo(UserInfo userInfo){

        if(usermapper.insertUserinfo(userInfo)){
            String Key = userInfo.getName()+"个人信息";
            Map map = new HashMap<>();
            map.put("id",userInfo.getId());
            map.put("name",userInfo.getName());
            map.put("username",userInfo.getUsername());
            map.put("age",userInfo.getAge());
            map.put("email",userInfo.getEmail());
            map.put("phoneNumber",userInfo.getPhoneNumber());
            redisUtil.hmset(Key,map,300);

            return true;
        }else {
            return false;
        }
    }

	/**
     * 查询用户个人信息(优先从redis中查找)
     * @param name
     * @return
     */
    @Override
    public Map selectInfo(String name) {
        if (redisUtil.hmget(name+"个人信息").size()!=0){
            return redisUtil.hmget(name+"个人信息");
        }else {
            Map map = usermapper.selectInfo(name);
            redisUtil.hmset(name+"个人信息",map,300); //若不在redis中则存入redis,并设置过期时间
            log.info("mysql起来干活");
            return map;
        }
    }

    /**
     * 修改用户个人信息,MYSQL修改成功后修改redis中的信息
     * 若redis中信息过期则重新存入
     * 返回更改后的个人信息
     *
     * @param userInfo
     * @return
     */
    @Override
    public Map updateInfo(UserInfo userInfo) {
        if (usermapper.updateUserinfo(userInfo)>=0){
            if(redisUtil.hmget(userInfo.getName()+"个人信息").size()!=0){
                Map map = redisUtil.hmget(userInfo.getName()+"个人信息");
                if (!map.get("username").equals(userInfo.getUsername())){
                    redisUtil.hset(userInfo.getName()+"个人信息","username",userInfo.getUsername());
                }
                if (!map.get("age").equals(userInfo.getAge())){
                    redisUtil.hset(userInfo.getName()+"个人信息","age",userInfo.getAge());
                }
                if (!map.get("email").equals(userInfo.getEmail())){
                    redisUtil.hset(userInfo.getName()+"个人信息","email",userInfo.getEmail());
                }
                if (!map.get("phoneNumber").equals(userInfo.getPhoneNumber())){
                    redisUtil.hset(userInfo.getName()+"个人信息","phoneNumber",userInfo.getPhoneNumber());
                }
                redisUtil.expire(userInfo.getName()+"个人信息",300);
            }else {
                Map map = usermapper.selectInfo(userInfo.getName());
                redisUtil.hmset(userInfo.getName()+"个人信息",map,300);
            }
            log.info("修改成功");
            return redisUtil.hmget(userInfo.getName()+"个人信息");
        }else {
            Map map = new HashMap();
            map.put("errro","更改MYSQL个人信息表错误");
            return map;
        }
    }
}

初步思路是由于个人信息是用户私有,每张用户信息表并不会被过于频繁的查看,于是暂定填写个人信息后,更改个人信息后,查询个人信息后将该表存入redis,并设置过期时间,不活跃用户的个人信息在过期后到下一次被查询前都不会存入redis,达到节省空间的目的。

  • 数据库访问层
package com.lqr.learnredis.service;

import com.lqr.learnredis.entity.User;
import com.lqr.learnredis.entity.UserInfo;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;

@Service
public interface UserService {

    boolean insertUserinfo(UserInfo userInfo);
    
    Map selectInfo(String name);

    Integer updateUserinfo(UserInfo userInfo);
}

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.lqr.learnredis.mapper.UserMapper">
    <insert id="insertUserinfo" parameterType="com.lqr.learnredis.entity.UserInfo"
    useGeneratedKeys="true" keyProperty="id">
        INSERT INTO userinfo (name,username,age,email,phoneNumber) VALUES
        (#{name},#{username},#{age},#{email},#{phoneNumber});
    </insert>
    
    <select id="selectInfo" parameterType="java.lang.String" resultType="java.util.Map">
        SELECT * FROM USERINFO WHERE USERINFO.NAME=#{name};
    </select>

    <update id="updateUserinfo" parameterType="com.lqr.learnredis.entity.UserInfo">
        UPDATE userinfo SET username=#{username},
        age=#{age},email=#{email},phoneNumber=#{phoneNumber};
    </update>
</mapper>
  • 运行效果
    填写个人信息:
    在这里插入图片描述
    在这里插入图片描述
    打开Navicat可以看到mysql的用户信息表中加入了这条用户信息
    在这里插入图片描述
    打开redis可视化工具可以看到,redis中加入了一条key为lqr个人信息value为hash格式的记录,且有过期时间
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

查看,修改个人信息:
查看个人信息,返回json格式
在这里插入图片描述
修改个人信息:
在这里插入图片描述
当前页面获取登录账号(伪)的个人信息,由于没做前端,就直接放在表单的text里面了,修改后提交,返回修改后的个人信息:
在这里插入图片描述
可以看到redis里的信息也修改了,且过期时间重置
在这里插入图片描述
在这里插入图片描述

  1. 上架商品以及查看商品,更新每周热销前五的产品,并可以查看每周热销的产品
  • 实体层
package com.lqr.learnredis.entity;

import lombok.Data;

import java.io.Serializable;

@Data
public class Goods implements Serializable {
    Integer id;
    String goodsname;
    float price;
    Integer saleinfo;
    String goodskind;
    
}

这里为了偷懒不想针对两个属性写升序降序排序的方法,又定义了三个子类继承goods类实现Comparable接口,重写比较方法,就可以调用collection接口的sort方法对该类的集合进行排序了

package com.lqr.learnredis.entity.sortentity;

import com.lqr.learnredis.entity.Goods;

/**
 * 价格升序排列的商品
 */
public class GoodsSortbypriceAsc extends Goods implements Comparable<GoodsSortbypriceAsc> {
    @Override
    public int compareTo(GoodsSortbypriceAsc o) {
        if (this.getPrice()>=o.getPrice())
            return 1;
        else return -1;
    }

    public GoodsSortbypriceAsc(){
        super();
    }

    public GoodsSortbypriceAsc(Goods goods){
        this();
        this.setGoodskind(goods.getGoodskind());
        this.setGoodsname(goods.getGoodsname());
        this.setPrice(goods.getPrice());
        this.setSaleinfo(goods.getSaleinfo());
        this.setId(goods.getId());
    }
}

package com.lqr.learnredis.entity.sortentity;

import com.lqr.learnredis.entity.Goods;

/**
 * 价格降序排列的商品
 */
public class GoodsSortbypriceDesc extends Goods implements Comparable<GoodsSortbypriceDesc> {
    @Override
    public int compareTo(GoodsSortbypriceDesc o) {
        if (this.getPrice()<=o.getPrice())
        return 1;
        else return -1;
    }

    public GoodsSortbypriceDesc(){
        super();
    }

    public GoodsSortbypriceDesc(Goods goods){
        this();
        this.setGoodskind(goods.getGoodskind());
        this.setGoodsname(goods.getGoodsname());
        this.setPrice(goods.getPrice());
        this.setSaleinfo(goods.getSaleinfo());
        this.setId(goods.getId());
    }
}

package com.lqr.learnredis.entity.sortentity;

import com.lqr.learnredis.entity.Goods;

/**
 * 效率降序排列的商品
 */
public class GoodsSortbysaleinfoDesc extends Goods implements Comparable<GoodsSortbysaleinfoDesc> {
    @Override
    public int compareTo(GoodsSortbysaleinfoDesc o) {
        if (this.getSaleinfo()<=o.getSaleinfo())
            return 1;
        else return -1;
    }

    public GoodsSortbysaleinfoDesc(){
        super();
    }

    public GoodsSortbysaleinfoDesc(Goods goods){
        this();
        this.setGoodskind(goods.getGoodskind());
        this.setGoodsname(goods.getGoodsname());
        this.setPrice(goods.getPrice());
        this.setSaleinfo(goods.getSaleinfo());
        this.setId(goods.getId());
    }
}

  • 视图层
<html>
<head>
    <title>上架商品</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<div>上架商品</div>
<form name="releasegoods" action="/goods/releaseGoods" method="post">
    商品名称: <input type="text" name="goodsname"><br/>
    商品类别: <input type="text" name="goodskind"><br/>
    商品价格: <input type="text" name="price"><br/>
    <input type="submit" value="提交">
</form>
<a href="/index">目录</a><br>
</body>
</html>

<html>
<head>
    <title>查看商品信息</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<div>查看商品信息</div>
<form name="goodsInfo" action="/goods/selectGoods" method="post">
    商品名称: <input type="text" name="goodsname"><br/>
    商品类别: <input type="text" name="goodskind"><br/>
    排序: <input type="text" name="sort"><br/>
    <input type="submit" value="提交">
</form>
<a href="/index">目录</a><br>
</body>
</html>

  • 控制层
package com.lqr.learnredis.controller;

import com.lqr.learnredis.entity.Goods;
import com.lqr.learnredis.entity.sortentity.GoodsSortbypriceDesc;
import com.lqr.learnredis.service.GoodsService;
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 javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Set;

@RequestMapping("/goods")
@RestController
public class GoodsController {

    @Autowired
    private GoodsService goodsService;

	/**
     * 查看该类别的热销产品
     * @param kind
     * @return
     */
    @RequestMapping("/selecthotGoods")
    public Set<Goods> selecthotGoods(@RequestParam String kind){
        return goodsService.selecthotGoods(kind);
    }
    
    /**
     * 将所有类别的热销产品存入redis
     * @return
     */
    @RequestMapping("/findallhotGoods")
    public String findallhotGoods(){
        try {
            goodsService.findallhotGoods();
            return "热销产品存储成功";
        }catch (Exception e){
            return "热销产品存储失败";
        }
    }

	/**
     * 上架商品
     * @param request
     * @return
     */
    @RequestMapping("/releaseGoods")
    public String releaseGoods(HttpServletRequest request){
        try {
            Goods goods = new Goods();
            goods.setGoodskind(request.getParameter("goodskind"));
            goods.setGoodsname(request.getParameter("goodsname"));
            goods.setPrice(Float.parseFloat(request.getParameter("price")));
            goods.setSaleinfo(0);
            if(goodsService.releaseGoods(goods))
            return "上架商品成功";
            else return "上架商品失败";
        }catch (Exception e) {
            return "上架商品失败";
        }
    }

	/**
     * 查看商品
     * @param request
     * @return
     */
    @RequestMapping("/selectGoods")
    public List selectGoods(HttpServletRequest request){
        try {
            String goodsname = request.getParameter("goodsname");
            String goodskind = request.getParameter("goodskind");
            return goodsService.selectGoods(goodsname,goodskind,request.getParameter("sort"));
        }catch (Exception e) {
            return null;
        }
    }
}

  • 监听redis工具类及线程池配置

使用监听key过期事件的方式需要修改 redis.conf 文件,或者直接使用CONFIGSET 命令来开启或关闭键空间通知功能:
配置文件修改方式如下:
notify-keyspace-events Ex

监听工具类及线程池配置代码来源 https://blog.csdn.net/liuchuanhong1/article/details/70147149 (侵删)
创建线程池配置类,读取yml中的线程池配置信息

package com.lqr.learnredis.util;


import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Data
@ConfigurationProperties(prefix = "spring.task.pool") // 该注解的locations已经被启用,现在只要是在环境中,都会优先加载
public class TaskThreadPoolConfig {
    private int corePoolSize;

    private int maxPoolSize;

    private int keepAliveSeconds;

    private int queueCapacity;

    private String threadNamePrefix;
}

创建redis监听配置类,在程序运行时启动

package com.lqr.learnredis.util.RedisListener;

import com.lqr.learnredis.util.TaskThreadPoolConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

@Configuration
public class RedisMessageListenerContainerConfig {

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Autowired
    private TopicMessageListener messageListener;

    @Autowired
    private TaskThreadPoolConfig config;

    @Value("spring.redis.topic")
    private String topic;

    @Bean
    public RedisMessageListenerContainer configRedisMessageListenerContainer(Executor executor){
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        // 设置Redis的连接工厂
        container.setConnectionFactory(redisTemplate.getConnectionFactory());
        // 设置监听使用的线程池
        container.setTaskExecutor(executor);
        // 设置监听的Topic
        ChannelTopic channelTopic = new ChannelTopic("__keyevent@0__:expired");
        // 设置监听器
        container.addMessageListener(messageListener, channelTopic);
        return container;
    }

    @Bean // 配置线程池
    public Executor myTaskAsyncPool() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(config.getCorePoolSize());
        executor.setMaxPoolSize(config.getMaxPoolSize());
        executor.setQueueCapacity(config.getQueueCapacity());
        executor.setKeepAliveSeconds(config.getKeepAliveSeconds());
        executor.setThreadNamePrefix(config.getThreadNamePrefix());

        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

}

创建监听类,监听key过期事件,若有key过期,则匹配是否为热销产品key,若是,则将该类产品的热销产品重新存入redis,以达到定期更新的目的

package com.lqr.learnredis.util.RedisListener;

import com.lqr.learnredis.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;

@Component
public class TopicMessageListener implements MessageListener{

    @Autowired
    private GoodsService goodsService;

    @Override
    public void onMessage(Message message, byte[] bytes) {
        byte[] body = message.getBody();// 请使用valueSerializer
//        byte[] channel = message.getChannel();
//        String topic = new String(channel);
        String Key = new String(body);
        // 请参考配置文件,本例中key,value的序列化方式均为string。
        if (Key.substring(Key.length()-4,Key.length()).equals("热销产品")){
//            System.out.println("Key:"+Key+"死啦");
            goodsService.findhotGoods(Key.substring(0,Key.length()-4));
        }

    }
}

需要在启动类上加上注解,开启异步和配置文件支持

@EnableAsync
@EnableConfigurationProperties({TaskThreadPoolConfig.class} ) // 开启配置属性支持

这种思路对资源有一定的消耗,希望大神告知更优解决方案

  • 业务逻辑层
package com.lqr.learnredis.service;

import com.lqr.learnredis.entity.Goods;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Set;

@Service
public interface GoodsService {

    Goods findgoodsbyid(Integer goodsid);

    Set<Goods> selecthotGoods(String kind);

    void findhotGoods(String kind);

    void findallhotGoods();

    boolean releaseGoods(Goods goods);

    List selectGoods(String goodsname,String goodskind,String sort);

}

package com.lqr.learnredis.service.impl;

import com.lqr.learnredis.entity.Goods;
import com.lqr.learnredis.entity.sortentity.GoodsSortbypriceAsc;
import com.lqr.learnredis.entity.sortentity.GoodsSortbypriceDesc;
import com.lqr.learnredis.entity.sortentity.GoodsSortbysaleinfoDesc;
import com.lqr.learnredis.mapper.GoodsMapper;
import com.lqr.learnredis.redis.RedisUtil;
import com.lqr.learnredis.service.GoodsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

@Slf4j
@Service
public class GoodsServiceImpl implements GoodsService {

    @Autowired
    private GoodsMapper goodsMapper;

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 查找该类别的热销产品
     * @param kind
     * @return
     */
    @Override
    public Set<Goods> selecthotGoods(String kind) {
        return (Set<Goods>) redisUtil.reverseRange(kind+"热销产品",0,5);
    }

    /**
     * 根据id查找商品,优先在redis中查找,若不在redis中而在mysql中则存入redis
     * @param goodsid
     * @return
     */
    @Override
    public Goods findgoodsbyid(Integer goodsid) {
        List<Goods> goodsList = (List<Goods>)redisUtil.lGet("商品列表",0,-1);
        if (goodsList!=null && goodsList.size()!=0){
            for (Goods goods : goodsList){
                if (goods.getId()==goodsid){
                    return goods;
                }
            }
        }
        Goods g = goodsMapper.findgoodsbyid(goodsid);
        if (g!=null){
            redisUtil.lSet("商品列表",g);
            return g;
        }
        return null;
    }

    /**
     * 查找某类别热销产品并存入redis
     * @param kind
     */
    @Override
    public void findhotGoods(String kind) {
        List<Goods> list = goodsMapper.findhotGoods(kind);
        for (Goods g : list) {
            if(!redisUtil.zsSet(g.getGoodskind()+"热销产品",g,g.getSaleinfo())){
                log.info("Redis插入失败");
                return;
            }
        }
        redisUtil.expire(kind+"热销产品",10);
    }

    /**
     * 查找所有类别的热销产品并存入redis
     */
    @Override
    public void findallhotGoods(){
        try {
            List<String> list = goodsMapper.findgoodskind();
            for (String s : list){
                findhotGoods(s);
            }
        }catch (Exception e){
            log.info("热销产品存储失败");
            return;
        }
    }

    /**
     * 上架一个商品,存入MYSQL与redis中
     * 因为整个方法只有mysql和redis存储,所以发生异常可以不用手动回滚redis
     * @param goods
     * @return
     */
    @Override
    @Transactional
    public boolean releaseGoods(Goods goods) {
        if (goodsMapper.releaseGoods(goods)){
            redisUtil.lSet("商品列表",goods);
            return true;
        }else {
            return false;
        }
    }


    /**
     * 从redis中查询商品,可选择排序方式
     *
     * @param goodsname
     * @param goodskind
     * @param sort
     * @return
     */
    @Override
    public List selectGoods(String goodsname,String goodskind,String sort) {
        if (redisUtil.lGetListSize("商品列表")>0){
            List<Goods> list = (List<Goods>)redisUtil.lGet("商品列表",0,redisUtil.lGetListSize("商品列表"));
            if (sort.equals("priceDesc")){
                List<GoodsSortbypriceDesc> list1 = new LinkedList<>();
                for (Goods g : list){
                    if ((g.getGoodsname().indexOf(goodsname)>=0 || goodsname.isEmpty())
                            && (g.getGoodskind().indexOf(goodskind)>=0 || goodskind.isEmpty())){
                        GoodsSortbypriceDesc sg =new GoodsSortbypriceDesc(g);
                        list1.add(sg);
                    }
                }
                Collections.sort(list1);
                return list1;
            } else if (sort.equals("priceAsc")){
                List<GoodsSortbypriceAsc> list1 = new LinkedList<>();
                for (Goods g : list){
                    if ((g.getGoodsname().indexOf(goodsname)>=0 || goodsname.isEmpty())
                            && (g.getGoodskind().indexOf(goodskind)>=0 || goodskind.isEmpty())){
                        GoodsSortbypriceAsc sg =new GoodsSortbypriceAsc(g);
                        list1.add(sg);
                    }
                }
                Collections.sort(list1);
                return list1;
            } else if (sort.equals("saleinfoDesc")) {
                List<GoodsSortbysaleinfoDesc> list1 = new LinkedList<>();
                for (Goods g : list){
                    if ((g.getGoodsname().indexOf(goodsname)>=0 || goodsname.isEmpty())
                            && (g.getGoodskind().indexOf(goodskind)>=0 || goodskind.isEmpty())){
                        GoodsSortbysaleinfoDesc sg =new GoodsSortbysaleinfoDesc(g);
                        list1.add(sg);
                    }
                }
                Collections.sort(list1);
                return list1;
            } else {
                List<Goods> list1 = new LinkedList<>();
                for (Goods g : list){
                    if ((g.getGoodsname().indexOf(goodsname)>=0 || goodsname.isEmpty())
                            && (g.getGoodskind().indexOf(goodskind)>=0 || goodskind.isEmpty())){
                        list1.add(g);
                    }
                }
                return list1;
            }

        }else {
            List<Goods> goodsList = goodsMapper.selectGoods();
            if (goodsList!=null && goodsList.size()!=0){
                for (Goods g : goodsList){
                    redisUtil.lSet("商品列表",g);
                }
            }
            log.info("mysql起来干活");
            return goodsList;
        }
    }
}

可使用三种排序方式查看商品,分别是价格升序,价格降序,销量降序,热销产品表存储在redis的结构为sorted set,即有序set集合,权值为销量,查看的时候按销量从大到小排序返回set集合

  • 数据库访问层
package com.lqr.learnredis.mapper;

import com.lqr.learnredis.entity.Goods;
import com.lqr.learnredis.entity.Orders;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface GoodsMapper {

    Goods findgoodsbyid (Integer goodsid);

    List<Goods> findhotGoods (String kind);

    List<String> findgoodskind ();

    boolean releaseGoods(Goods goods);

    List<Goods> selectGoods();

}

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.lqr.learnredis.mapper.GoodsMapper">
    <select id="findgoodsbyid" parameterType="java.lang.Integer" resultType="com.lqr.learnredis.entity.Goods" >
        select * from goods where id=#{goodsid};
    </select>

    <select id="findhotGoods" parameterType="java.lang.String" resultType="com.lqr.learnredis.entity.Goods" >
        select * from goods where goodskind=#{kind} order by saleinfo limit 5;
    </select>

    <select id="findgoodskind" resultType="java.lang.String">
        select goodskind from goods;
    </select>

    <insert id="releaseGoods" parameterType="com.lqr.learnredis.entity.Goods"
    useGeneratedKeys="true" keyProperty="id">
        insert into goods (goodsname,price,goodskind,saleinfo) values (#{goodsname},#{price},#{goodskind},#{saleinfo});
    </insert>

    <select id="selectGoods" resultType="com.lqr.learnredis.entity.Goods">
        select * from goods;
    </select>

</mapper>
  • 运行效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到上架商品后存入mysql且存入redis
再上架一个商品以便观察每周(测试为10秒)更新热销前5名的方法是否成功,省略截图
将所有类别的商品存入redis
在这里插入图片描述
在这里插入图片描述
可以看到redis里存入了热销的5个产品(按销量从小到大排),现手动更改mysql数据库中的销量,观察当目前的key过期时(10秒,对应生产环境7天)是否有更新最新的热销产品排名。
在这里插入图片描述
监听更新成功

查看商品
在这里插入图片描述
可模糊搜索商品名称和商品类别,偷懒没把排序做成单选按钮或下拉框就采用文本框输入的方式测试了,模糊搜索生,按价格降序排序
在这里插入图片描述
这里因为访问的是redis,所以上面强行手动修改的mysql中雕牌洗衣粉的销量并未更新到redis,若用正常购买的方式则会更新

  1. 购买商品和查看订单
在这里插入代码片
  • 实体层
package com.lqr.learnredis.entity;

import lombok.Data;

import java.io.Serializable;

@Data
public class Goods implements Serializable {
    Integer id;
    String goodsname;
    float price;
    Integer saleinfo;
    String goodskind;

}

package com.lqr.learnredis.entity;

import lombok.Data;

import java.util.Date;

@Data
public class Orders {
    private Integer id;
    private Integer userid;
    private Date time;
    private Goods goods;
    private Integer number;
    private float totalprice;
}

  • 视图层
    这里偷懒就直接使用输入商品id的方式了,正常应做成表格,通过复选框或是别的方式传入js再由js向后端发起ajax请求
<html>
<head>
    <title>购买商品</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<div>购买商品</div>
<form name="buygoods" action="/orders/buyover" method="post">
    商品id: <input type="text" name="goodsid"><br/>
    购买数量: <input type="text" name="number"><br/>
    <input type="text" name="userid" value="1" hidden="hidden"><br/>
    <input type="submit" value="提交">
</form>
<a href="/index">目录</a><br>
</body>
</html>

<html>
<head>
    <title>查看订单信息</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<div>查看订单信息</div>
<form name="ordersInfo" action="/orders/selectmyorders" method="post">
    商品名称: <input type="text" name="goodsname"><br/>
    商品类别: <input type="text" name="goodskind"><br/>
    <input type="text" name="userid" value="1" hidden="hidden"><br/>
    <input type="submit" value="提交">
</form>
<a href="/index">目录</a><br>
</body>
</html>
  • 控制层
package com.lqr.learnredis.controller;

import com.lqr.learnredis.entity.Goods;
import com.lqr.learnredis.entity.Orders;
import com.lqr.learnredis.service.GoodsService;
import com.lqr.learnredis.service.OrdersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.List;

@RequestMapping("/orders")
@RestController
public class OrdersController {

    @Autowired
    private OrdersService ordersService;

    @Autowired
    private GoodsService goodsService;

    @RequestMapping("/buyover")
    public String buyOver(HttpServletRequest request){
        try {
            Orders orders = new Orders();
            Goods goods = goodsService.findgoodsbyid(Integer.parseInt(request.getParameter("goodsid")));
            orders.setTime(new Date());
            orders.setUserid(Integer.parseInt(request.getParameter("userid")));
            //原本应该写在js判断,或是做在页面上,偷懒就写在这里了
            if (Integer.parseInt(request.getParameter("number"))<=0){
                return "购买不能小于1件";
            }
            //----------------割-------------
            orders.setNumber(Integer.parseInt(request.getParameter("number")));
            if (goods!=null){
                orders.setGoods(goods);
                orders.setTotalprice(goods.getPrice()*orders.getNumber());
                if(ordersService.InsertOrders(orders)){
                    return "购买成功";
                }else {
                    return "订单添加失败,购买失败";
                }
            }else {
                return "商品不存在,购买失败";
            }
        }catch (Exception e){
            return "购买异常";
        }
    }

    @RequestMapping("/selectmyorders")
    public List selectOrders(HttpServletRequest request){
        try {
            return ordersService.selectmyorders(Integer.parseInt(request.getParameter("userid")),
                    request.getParameter("goodsname"),request.getParameter("goodskind"));
        }catch (Exception e){
            return null;
        }
    }
}

  • 业务逻辑层
package com.lqr.learnredis.service.impl;

import com.lqr.learnredis.entity.Goods;
import com.lqr.learnredis.entity.Orders;
import com.lqr.learnredis.mapper.GoodsMapper;
import com.lqr.learnredis.mapper.OrdersMapper;
import com.lqr.learnredis.redis.RedisUtil;
import com.lqr.learnredis.service.OrdersService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

@Slf4j
@Service
public class OrdersServiceImpl implements OrdersService {

    @Autowired
    private OrdersMapper ordersMapper;

    @Autowired
    private GoodsMapper goodsMapper;

    @Autowired
    private RedisUtil redisUtil;

    @Override
    @Transactional
    public boolean InsertOrders(Orders orders) {

        //若更改销量失败(即购买失败)则直接返回,无须回滚,所以不放入try中,异常抛出给外层
        if (goodsMapper.updatesaleinfo(orders.getGoods(),orders.getNumber())){
            List<Goods> goodsList = (List<Goods>)redisUtil.lGet("商品列表",0,-1);
            if (goodsList!=null && goodsList.size()!=0){
                try {   //若此处更新redis发生异常,则mysql通过事务回滚,redis通过catch中的代码手动回滚
                    for (Goods goods : goodsList){
                        if (goods.getId()==orders.getGoods().getId()){
                            int index = goodsList.indexOf(goods);
                            goods.setSaleinfo(goods.getSaleinfo()+orders.getNumber());
                            redisUtil.lUpdateIndex("商品列表",index,goods);
                        }
                    }
                }catch (Exception e) {
                    //暂不考虑这里发生异常的情况,希望大佬给出更优解决方案
                    if (goodsList!=null && goodsList.size()!=0){
                        for (Goods g : goodsList){
                            redisUtil.lSet("商品列表",g);
                        }
                    }
                }
            }else {
                List<Goods> goodsList2 = goodsMapper.selectGoods();
                if (goodsList2!=null && goodsList2.size()!=0){
                    for (Goods g : goodsList2){
                        redisUtil.lSet("商品列表",g);
                    }
                }
            }
            //若此处mysql添加发生异常,则mysql通过事务回滚,redis通过catch中的代码手动回滚
            try {
                if(ordersMapper.InsertOrders(orders)){
                    redisUtil.lSetLeft(orders.getUserid()+"订单列表",orders);
                    return true;
                }else {
                    return false;
                }
            }catch (Exception e){
                List<Goods> goodsList3 = (List<Goods>)redisUtil.lGet("商品列表",0,-1);
                if (goodsList3!=null && goodsList3.size()!=0){
                    for (Goods goods : goodsList3){
                        if (goods.getId()==orders.getGoods().getId()){
                            int index = goodsList3.indexOf(goods);
                            goods.setSaleinfo(goods.getSaleinfo()-orders.getNumber());
                            redisUtil.lUpdateIndex("商品列表",index,goods);
                        }
                    }
                }
                throw e;   //把异常抛出给外层,否则mysql不会回滚
            }
        }else {
            return false;
        }
    }

    /**
     * 查看该userid的订单
     * @param userid
     * @param goodsname
     * @param goodskind
     * @return
     */
    @Override
    public List selectmyorders(Integer userid,String goodsname,String goodskind) {

        if (redisUtil.lGetListSize(userid+"订单列表")!=0){
            List<Orders> ordersList = (List<Orders>)redisUtil.lGet(userid+"订单列表",0,-1);
            List<Map> list = new ArrayList<>();
            for (Orders o : ordersList){
                if ((o.getGoods().getGoodsname().indexOf(goodsname)>0 || goodsname.isEmpty())
                        && (o.getGoods().getGoodskind().indexOf(goodskind)>0 || goodskind.isEmpty())){
                    o.setTime(new Date(o.getTime().getTime()+(long)8*60*60*1000));
                    Map map = new HashMap();
                    map.put("订单号",o.getId());
                    map.put("商品名称",o.getGoods().getGoodsname());
                    map.put("商品类型",o.getGoods().getGoodskind());
                    map.put("商品单价",o.getGoods().getPrice());
                    map.put("商品销量",o.getGoods().getSaleinfo());
                    map.put("购买数量",o.getNumber());
                    map.put("订单总价",o.getTotalprice());
                    map.put("下单时间",o.getTime());
                    list.add(map);
                }
            }
            return list;
        }else {
            List<Orders> ordersList = ordersMapper.selectmyorders(userid,goodsname,goodskind);
            log.info("mysql起来干活");
            List<Map> list = new ArrayList<>();
            //mysql时区问题 ,加8小时
            for (Orders o : ordersList){
                o.setTime(new Date(o.getTime().getTime()+(long)8*60*60*1000));
                redisUtil.lSet(userid+"订单列表",o);
                Map map = new HashMap();
                map.put("订单号",o.getId());
                map.put("商品名称",o.getGoods().getGoodsname());
                map.put("商品类型",o.getGoods().getGoodskind());
                map.put("商品单价",o.getGoods().getPrice());
                map.put("商品销量",o.getGoods().getSaleinfo());
                map.put("购买数量",o.getNumber());
                map.put("订单总价",o.getTotalprice());
                map.put("下单时间",o.getTime());
                list.add(map);
            }
            return list;
        }
    }
}

这里对更新商品销量和创建订单做了事务管理,mysql的事务管理可以直接通过注解实现,而redis只能靠手动回滚,放在catch里,当创建订单失败时回滚redis中更新商品销量的操作,查询订单与上面相同可以使用模糊查询,相对简单不作解释。
需要在启动类上加上注解,开启事务

@EnableTransactionManagement
  • 数据库访问层
package com.lqr.learnredis.mapper;

import com.lqr.learnredis.entity.Orders;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface OrdersMapper {

    boolean InsertOrders(Orders orders);

    List<Orders> selectmyorders(Integer userid,String goodsname,String goodskind);
}

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.lqr.learnredis.mapper.OrdersMapper">
    <resultMap id="completeOrder" type="com.lqr.learnredis.entity.Orders">
        <result column="id" property="id" jdbcType="INTEGER" />
        <result column="time" property="time" />
        <result column="goodsid" property="goods.id" jdbcType="INTEGER" />
        <result column="userid" property="userid" jdbcType="INTEGER" />
        <result column="goodskind" property="goods.goodskind" jdbcType="VARCHAR" />
        <result column="goodsname" property="goods.goodsname" jdbcType="VARCHAR" />
        <result column="goodsprice" property="goods.price" jdbcType="FLOAT"/>
        <result column="goodssale" property="goods.saleinfo" jdbcType="INTEGER" />
        <result column="totalprice" property="totalprice" jdbcType="FLOAT" />
        <result column="number" property="number" jdbcType="INTEGER" />
    </resultMap>
    
    <insert id="InsertOrders" parameterType="com.lqr.learnredis.entity.Orders" useGeneratedKeys="true" keyProperty="id">
        insert into ordersinfo (userid,time,goodsid,number,totalprice) values (#{userid},#{time},#{goods.id},#{number},#{totalprice});
    </insert>

    <select id="selectmyorders" resultMap="completeOrder" >
        select oinfo.id as id,
               oinfo.time as time,
               oinfo.goodsid as goodsid,
               oinfo.userid as userid,
               ginfo.goodskind as goodskind,
               ginfo.goodsname as goodsname,
               ginfo.price as goodsprice,
               ginfo.saleinfo as goodssale,
               oinfo.number as number,
               oinfo.totalprice as totalprice
        from ordersinfo oinfo inner join goods ginfo on oinfo.goodsid=ginfo.id
        where oinfo.userid=#{param1}
        <if test="param2 != null and param2 != ''">
            and ginfo.goodsname=#{param2}
        </if>
        <if test="param3 != null and param3 != ''">
            and ginfo.goodskind=#{param3}
        </if>
    </select>
</mapper>

GoodsMapper更改商品销量部分:

boolean updatesaleinfo(Goods goods,Integer number);
<update id="updatesaleinfo" >
        update goods set saleinfo=saleinfo+#{param2} where id=(#{param1.id});
    </update>
  • 运行效果

在这里插入图片描述
如下图,订单存入mysql和redis,且商品表的销量更新
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里存的商品销量是购买订单时的,未加上本次购买的数量,我觉得加不加都有说法,暂时没加
若想加上,只需在OrdersController中加上一句

orders.setNumber(Integer.parseInt(request.getParameter("number")));
//            goods.setSaleinfo(goods.getSaleinfo()+orders.getNumber());   若想让订单中商品信息的销量加上本次订单购买数量则加上本句
            if (goods!=null){
                orders.setGoods(goods);
  1. 查看、更新公告
    使用redis的list结构存储,由于redis的list是一个双向链表,可以从头或从尾加也可以从头或从尾删,所以可以实现队列的功能,目前redis及mysql中存储的公告如下
    在这里插入图片描述
  • 控制层
package com.lqr.learnredis.controller;

import com.lqr.learnredis.service.NewsService;
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 java.util.List;

@RequestMapping("/news")
@RestController
public class NewsController {

    @Autowired
    private NewsService newsService;

    @RequestMapping("/insertNews")
    public String insertNews(@RequestParam String title, @RequestParam String content){
        try {
            if (newsService.insertNews(title,content)){
                return "插入成功";
            }else {
                return "插入失败";
            }
        }catch (Exception e){
            return "未知错误";
        }
    }

    @RequestMapping("/selectNews")
    public List selectNews(){
        return newsService.selectNews();
    }
}

  • 业务逻辑层
package com.lqr.learnredis.service.impl;

import com.lqr.learnredis.entity.News;
import com.lqr.learnredis.mapper.NewsMapper;
import com.lqr.learnredis.redis.RedisUtil;
import com.lqr.learnredis.service.NewsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Slf4j
@Service
public class NewsServiceImpl implements NewsService {

    @Autowired
    private NewsMapper newsMapper;

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 插入新的公告,存入mysql,若存储成功,则存入redis并保证redis中只有最新的n条公告
     * @param title
     * @param content
     * @return
     */
    @Override
    @Transactional
    public boolean insertNews(String title, String content) {
        try {
            if (newsMapper.insertNews(title,content)){
                while (redisUtil.lGetListSize("news")>=2){
                    redisUtil.lRightPop("news");
                }
                News news = new News();
                news.setTitle(title);
                news.setContent(content);
                if(redisUtil.lSetLeft("news",news))
                    return true;
                else
                    return false;
            }else {
                return false;
            }
        }catch (Exception e){
            return false;
        }
    }

    /**
     * 查找公告,优先在redis中查找
     * @return
     */
    @Override
    public List<News> selectNews() {
        try {
            if (redisUtil.lGetListSize("news")==0){
                log.info("mysql起来干活");
                return newsMapper.selectNews();
            }else {
                return (List<News>) redisUtil.lGet("news",0,-1);
            }
        }catch (Exception e){
            return null;
        }
    }
}

选择插入的时候从左边插入,删除的时候从右边删除,实现以时间为顺序最新的公告在最前面,只存储最新的2条公告

  • 数据库访问层
package com.lqr.learnredis.mapper;

import com.lqr.learnredis.entity.News;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface NewsMapper {

    boolean insertNews(String title, String content);

    List<News> selectNews();

}

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.lqr.learnredis.mapper.NewsMapper">
    <insert id="insertNews" parameterType="java.lang.String" >
        insert into news (title,content) values (#{param1},#{param2});
    </insert>

    <select id="selectNews" resultType="com.lqr.learnredis.entity.News">
        select * from news order by id desc limit 2;
    </select>
</mapper>

mysql中存放所有公告

  • 运行效果
    插入新公告
    在这里插入图片描述
    查看公告,只显示最新的两条公告,按时间从新到旧排序
    在这里插入图片描述
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值