第五节 唯一性与无序性的好朋友Set
一、Set典型应用场景
Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。从实际业务角度来考虑其应用场景。首先,Set它是一个集合,自然能够存放多个对象。多个对象之间各不相同,另外存放的顺序也是不固定的。
典型的应用场景就是来做唯一性校验。如果某张表有个字段是唯一的,例如用户绑定的电话号码,那么在插入这张表之前,需要校验插入的电话号码是否已经与数据库中已经有的数据出现重复。
另外一个无序性的特点的典型应用场景是随机返回一个数据。例如随机展示你感兴趣的歌曲、随机展示问卷调查数据、游戏里随机的防挂机的验证题目等等等等。只要是随机的,都可以考虑Redis的Set这种数据类型。
二、唯一性校验示例
注册的时候,要求邮箱号不能重复。下面是具体的业务逻辑。
先检查email在不在缓存里面,
如果在缓存里面,说明已经被注册,抛异常如果不在缓存里面 ,则查询数据库
如果在数据库里面,说明已经被注册,抛异常
如果不在数据库里面,插入数据库
插入成功,则加入缓存。
插入失败,则抛异常,事务回滚
public Integer registerUser(MUser user) {
//是否在缓存中
Boolean isInCache = getSetOperations().isMember(Constant.USER_REGISTER_KEY, user.getEmail());
if (isInCache) {
throw new InvalidArgumentException("email已经被注册");
}
//查询数据库
MUser userInDB = mapper.selectByEmail(user.getEmail());
if (userInDB != null && userInDB.getId() > 0) {
throw new InvalidArgumentException("email已经被注册");
} else {
//插入并返回数据库生成的主键ID,以此判断是否插入成功
mapper.insertSelective(user);
if (user.getId() > 0) {
//把新用户的email加入缓存
getSetOperations().add(Constant.USER_REGISTER_KEY, user.getEmail());
return user.getId();
} else {
throw new InvalidArgumentException("插入失败");
}
}
}
private SetOperations<String, String> getSetOperations() {
return redisTemplate.opsForSet();
}
三、随机性返回示例
上面的例子展示了校验注册用户的邮箱。
下面的例子是,随机返回一个用户数据。业务场景很简单,就是把所有用户的数据存放到Redis的Set中,每次随机返回一个,并且返回的用户数据从Set中删除。如果Set中的所有用户数据都被删除了,那么就再重新查询所有用户数据,并把它们加入到Redis的Set中。
初始化缓存数据是新增操作,返回用户数据是删除操作。这两步操作在同一个方法里,因此这个方法是可能出现线程安全问题的。所以编写方法的时候需要考虑线程安全问题,为方法加锁就能解决。
业务逻辑:
从缓存中取随机一个用户数据
如果有用户数据,则返回
如果没有用户数据,则查询所有用户数据并加入缓存中。再拿一次缓存用户数据。
public synchronized MUser randomUser() {
MUser user = null;
//取出随机一条缓存中的用户数据
user = getUserSetOperations().pop(Constant.USER_RANDOM_KEY);
if (user == null) {
//如果缓存中没有数据,则查询数据库,将所有用户数据加入缓存并返回
List<MUser> userList = this.initCache();
if (userList == null || userList.size() == 0) {
throw new IllegalArgumentException("暂无任何用户数据,请稍后再试");
}
//取出随机一条缓存中的用户数据
user = getUserSetOperations().pop(Constant.USER_RANDOM_KEY);
}
return user;
}