问题来源:前端有三个值需要显示:年化收益率、平台注册总人数、平台累计投资金额、用户投资排行榜
如果每次都要去数据库库中查,很费时间
所以:放到redis缓存里面,提高用户体验,提高用户执行效率
年化收益率
要想操作redis,就要连redis,用什么?我这里用的是jedis,就好比是个驱动
第一步:maven依赖
<!-- spring-data-redis依赖的JAR配置 -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
<!-- jedis依赖的JAR配置 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
第二步:applicationContext-redis.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd ">
<!-- jedis Connection Factory -->
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:usePool="${redis.usePool}"
p:hostName="${redis.hostName}"
p:port="${redis.port}"
p:timeout="${redis.timeout}"
p:password="${redis.password}"/>
<!-- redis template definition -->
<bean id="redisTemplate"
class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory"/>
</bean>
</beans>
第三步:
先整体看一下代码:
LoanInfoServiceImpl.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Service("loanInfoServiceImpl")
public class LoanInfoServiceImpl implements LoanInfoService {
@Autowired
private LoanInfoMapper loanInfoMapper;
@Autowired
private RedisTemplate<Object,Object> redisTemplate;
//这里注入进来了redis模板对象
@Override
public Double queryHistoryAverageRate() {
//先去redis缓存中获取该,有:直接使用,没有:去数据库查询并存放到redis缓存中
//修改key值的序列化方式
redisTemplate.setKeySerializer(new StringRedisSerializer());
//获取操作key=value的数据类型的redis的操作对象,并获取指定key的value值
Double historyAverageRate = (Double) redisTemplate.opsForValue().get(Constants.HISTORY_AVERAGE_RATE);
//判断是否有值
if (null == historyAverageRate) {
//没有值:去数据库查询
historyAverageRate = loanInfoMapper.selectHistoryAverageRate();
//将该值存放到redis缓存中
redisTemplate.opsForValue().set(Constants.HISTORY_AVERAGE_RATE,historyAverageRate,15, TimeUnit.SECONDS);
}
return historyAverageRate;
}
第三步:引进jar包
import org.springframework.data.redis.core.RedisTemplate;
第四步:注入进来了redis模板对象
注入进来了redis模板对象:
@Autowired
private RedisTemplate<Object,Object> redisTemplate;
这里注入进来了redis模板对象,这个是在dataservice中的配置文件applicationContext-redis.xml中自动生成的bean,把该bean加载到spring容器中,
applicationContext-redis.xml
<!-- redis template definition -->
<bean id="redisTemplate"
class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory"/>
</bean>
第五步
先去redis缓存中获取该值,如果有就直接使用,如果没有就去数据库中查询并存放到redis缓存中
@Override
public Double queryHistoryAverageRate() {
//先去redis缓存中获取该,有:直接使用,没有:去数据库查询并存放到redis缓存中
//修改key值的序列化方式
(问:为什么要修改key值的序列化方式?因为Redis默认序列化时会使用JDK序列化器,
使得Redis中的key和value值不可读。我们可以改造RedisTemplate,配置自定义序列化器取代默认)
redisTemplate.setKeySerializer(new StringRedisSerializer());
//获取操作key=value的数据类型的redis的操作对象,并获取指定key的value值
Double historyAverageRate = (Double) redisTemplate.opsForValue().get(Constants.HISTORY_AVERAGE_RATE);
//判断是否有值
if (null == historyAverageRate) {
//没有值:去数据库查询
historyAverageRate = loanInfoMapper.selectHistoryAverageRate();
//将该值存放到redis缓存中
redisTemplate.opsForValue().set(Constants.HISTORY_AVERAGE_RATE,historyAverageRate,15, TimeUnit.SECONDS);
}
return historyAverageRate;
}
1、修改key值的序列化方式
(问:为什么要修改key值的序列化方式?因为Redis默认序列化时会使用JDK序列化器,
使得Redis中的key和value值不可读。我们可以改造RedisTemplate,配置自定义序列化器取代默认)
redisTemplate.setKeySerializer(new StringRedisSerializer());
2、获取操作key=value的数据类型的redis的操作对象,并获取指定key的value值
Double historyAverageRate = (Double) redisTemplate.opsForValue().get(Constants.HISTORY_AVERAGE_RATE);
分析:
get(Constants.HISTORY_AVERAGE_RATE) get里面放的是key,这里的key是HISTORY_AVERAGE_RATE,由于这个key经常会用到,这里的“HISTORY_AVERAGE_RATE”是一个常量,所以放到了p2p-common里面了
opsForValue() 获取key-value对象
redisTemplate 返回的是个Object
3、判断是否有值
if (null == historyAverageRate)
4、如果没有值,就去数据库中查询:
historyAverageRate = loanInfoMapper.selectHistoryAverageRate();
其中:selectHistoryAverageRate()
<!--获取平台历史平均年化收益率-->
<select id="selectHistoryAverageRate" resultType="java.lang.Double">
select cast(avg(rate) as DECIMAL(10,2)) from b_loan_info
</select>
5、从数据库中查到,将该值存放到redis缓存中
redisTemplate.opsForValue().set(Constants.HISTORY_AVERAGE_RATE,historyAverageRate,15, TimeUnit.SECONDS);
set(有三种)
- Object k , Object v:放一个key,value,不设置失效时间,俺么就在缓存中永久不失效
- Object k , Object v,long l , :这是一个毫秒数
- Object k , Object v,long l ,TimeUnit timeUnit:时间单位
我这里呢,是15分钟,
其实这里可以改进,这个15分钟其实是根据具体应用场景来设置的,后台需要有个参数来管理这个参数
其他:
1、decimal
decimal(10,2)中的“2”表示小数部分的位数,如果插入的值未指定小数部分或者小数部分不足两位则会自动补到2位小数,若插入的值小数部分超过了2为则会发生截断,截取前2位小数。
“10”指的是整数部分加小数部分的总长度,也即插入的数字整数部分不能超过“10-2”位,否则不能成功插入,会报超出范围的错误。
DECIMAL从MySQL 5.1引入,列的声明语法是DECIMAL(M,D)。在MySQL 5.1中,参量的取值范围如下:
·M是数字的最大数(精度)。其范围为1~65(在较旧的MySQL版本中,允许的范围是1~254),M 的默认值是10。
·D是小数点右侧数字的数目(标度)。其范围是0~30,但不得超过M。
阿里的 Java 手册里写着:
6. [强制] 小数类型为 decimal,禁止使用 float 和 double。
说明:float 和 double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不
正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。
我这里呢,java中用的是duble,数据库中用的是decimal,显示可能不准确 但是数据库是准确的,
前提是 所有计算发生在数据库,而不是 java计算完再保存进数据库
比如 扣手续费,那么 一定要在sql中写 计算式,而不要 java算完了 把结果存入数据库
平台注册总人数
userServiceImpl
第一步:注入redis模板对象
@Autowired
private RedisTemplate<Object,Object> redisTemplate;
第二步:
整个代码先看一下
@Service("userServiceImpl")
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private FinanceAccountMapper financeAccountMapper;
@Autowired
private RedisTemplate<Object,Object> redisTemplate;
@Override
public Long queryAllUserCount() {
//首先去redis缓存中查询,有:直接用
//修改redis中key值的序列化方式
redisTemplate.setKeySerializer(new StringRedisSerializer());
//获取指定操作某一个key的操作对象
BoundValueOperations<Object, Object> boundValueOps = redisTemplate.boundValueOps(Constants.ALL_USER_COUNT);
//获取指定key的value值
Long allUserCount = (Long) boundValueOps.get();
//判断是否有值
if (null == allUserCount) {
//去数据库查询
allUserCount = userMapper.selectAllUserCount();
//将该值存放到redis缓存中
boundValueOps.set(allUserCount,15, TimeUnit.SECONDS);
}
return allUserCount;
}
1、修改redis中key值的序列化方式
redisTemplate.setKeySerializer(new StringRedisSerializer());
2、获取指定操作某一个key的操作对象
BoundValueOperations<Object, Object> boundValueOps = redisTemplate.boundValueOps(Constants.ALL_USER_COUNT);
分析:这里换了一种方法取,方法boundValueOps(Constants.ALL_USER_COUNT);绑定某个key的value对象,这里的key就是Constants.ALL_USER_COUNT。
返回的对象boundValueOps只能操作这个指定key的值
3、获取指定key的value值
Long allUserCount = (Long) boundValueOps.get();
这个get不需要加key了,因为已经绑定好了
4、之后,就和上面同样的方法了
剩余可投金额
bidInfoServiceImpl
@Autowired
private RedisTemplate<Object,Object> redisTemplate;
@Override
public Double queryAllBidMoney() {
//获取指定key的操作对象
BoundValueOperations<Object, Object> boundValueOps = redisTemplate.boundValueOps(Constants.ALL_BID_MONEY);
//获取指定key的value值
Double allBidMoney = (Double) boundValueOps.get();
//判断是否有值
if (null == allBidMoney) {
//去数据库查询
allBidMoney = bidInfoMapper.selectAllBidMoney();
//存放到redis缓存中
boundValueOps.set(allBidMoney,15, TimeUnit.MINUTES);
}
return allBidMoney;
}
前面三个都是p2p-dataservice中的
用户投资排行榜
一、获取
p2p-web
LoanInfoController.java
//用户投资排行榜
List<BidUserTop> bidUserTopList = bidInfoService.queryBidUserTop();
model.addAttribute("bidUserTopList",bidUserTopList);
然后去方法bidInfoService.queryBidUserTop():
p2p-dataservice
bidInfoServiceImpl.java
@Override
public List<BidUserTop> queryBidUserTop() {
List<BidUserTop> bidUserTopList = new ArrayList<BidUserTop>();
Set<ZSetOperations.TypedTuple<Object>> typedTuples = redisTemplate.opsForZSet().reverseRangeWithScores(Constants.INVEST_TOP, 0, 9);
Iterator<ZSetOperations.TypedTuple<Object>> iterator = typedTuples.iterator();
while (iterator.hasNext()) {
ZSetOperations.TypedTuple<Object> next = iterator.next();
String phone = (String) next.getValue();
Double score = next.getScore();
BidUserTop bidUserTop = new BidUserTop();
bidUserTop.setPhone(phone);
bidUserTop.setScore(score);
bidUserTopList.add(bidUserTop);
}
return bidUserTopList;
}
过程:
1、获得set集合对象
Set<ZSetOperations.TypedTuple<Object>> typedTuples = redisTemplate.opsForZSet().reverseRangeWithScores(Constants.INVEST_TOP, 0, 9);
分析:opsForZSet():获得set集合对象
reverseRangeWithScores(Constants.INVEST_TOP, 0, 9):按照scores倒序来取(key,0,9);后面两个数是取的范围
例子:
//从高到低的排序集中获取分数在最小和最大值之间的元素。
Set<Object> reverseRangeByScore = opsForZSet.reverseRangeByScore("fan9", -1, 2);
2、通过set集合拿到迭代器
Iterator<ZSetOperations.TypedTuple<Object>> iterator = typedTuples.iterator();
3、遍历set集合,放到list中
二、存放
在超卖现象,投资的时候,最后投资成功了:就将用户的投资金额存放到redis缓存中
redisTemplate.opsForZSet().incrementScore(Constants.INVEST_TOP, phone,(Double) paramMap.get("bidMoney"));
分析:
ZSetOperations提供了一系列方法对有序集合进行操作
Double incrementScore(K key, V value, double delta); 通过增量增加排序集中的元素的分数
其他--zset
一、描述
redis其中一个数据结构为zset(sorted set-有序集合),其主要作用用于排行榜实现,你可以获取排名第几到第几的数据
二、数据结构
sorted set-有序集合在redis中有两种实现
1.ziplist,压缩双向链表,相关链接
2.skiplist,跳表实现
三、skiplist数据结构
score:分值,用于排序
backward:是第一层的前一个数据,即span=1
level[]:每一个层所代表的节点node
forward:该层级的下一个节点
span:到达该层级的下一个节点,实际跨越了多少个节点,也是方便用于zrange等排行榜查询的用处
四、注意点
4.1 插入数据
1.在插入数据的时候,通过level[]快速跳过不需要比较的节点,快速定位节点位置
2.在插入新的节点的时候,level[]的层级由随机决定该层级的高度
3.然后更新每一次受到影响的span,span代表该层节点到达下一节点应该要跨越的节点数量
4.2 查询排行榜,如zrange
1.先根据start,通过level[]和每一次的rank来快速定位第一层的起始位置
2.然后再根据end,来将结果输出给用户
五、参数控制
redis配置文件中用来控制zset到底是使用ziplist(压缩双向链表)还是skiplist(跳表)的参数:
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
zset-max-ziplist-entries zset使用ziplist存储的时候,最大限制存储entries的个数
zset-max-ziplist-value zset使用ziplist存储的时候,每个节点最大存储字节数
违反上述两个限制条件,均会导致zset将ziplist的数据结构切换为skiplist数据结构
而zset使用ziplist的原因,主要是出于在零散数据量少的时候,节省内容的占用