Redis(四)redis关联表的缓存问题
项⽬中,如果表与表之间没有任何关联关系,那这样使⽤缓存是没有什么问题的。那么如果表与表之间存在关联关系的情况,缓存问题该如何解决?
这⾥演⽰⼀下来说明问题,现在有student和school模块。
1、创建表
1.1 school
CREATE TABLE`school`
(
`id`varchar(255)NOT NULL COMMENT'主键',
`name`varchar(255)DEFAULT NULL COMMENT'学校名称',
PRIMARY KEY(`id`)
)
1.2 student
CREATE TABLE`student`(
`id`varchar(255)NOT NULL COMMENT'主键',
`name`varchar(255)DEFAULT NULL COMMENT'学⽣姓名',
`school_id`varchar(255)DEFAULT NULL COMMENT'学校id',
PRIMARY KEY(`id`)
)
2.创建vo
2.1 SchoolVo
@Data
public class SchoolVo {
/
** 主键 */
private String id;
/** 学校名称 */
private String name;
/** 学⽣列表 */
private List<StudentVo> studentList;
}
2.2 StudentVo
@Data
public class StudentVo {
/** 主键 */
private String id;
/** 学校名称 */
private String name;
/** 学校vo */
private SchoolVo schoolVo;
}
3.接⼝
3.1 SchoolService
@Service("schoolService")
public class SchoolServiceImpl implements ISchoolService {
@Override
@Cacheable(cacheNames ="school", key ="#schoolId")
public SchoolVo getSchoolVoById(String schoolId){
// 查询数据
School school = ById(schoolId);
List<Student> studentList = studentService.findStudentBySchoolId(schoolId);
// 构造vo
SchoolVo schoolVo = _pyProperties(school, SchoolVo.class);
schoolVo.setStudentList(_pyList(studentList, StudentVo.class));
return schoolVo;
}
}
3.2 StudentService
@Service("studentService")
public class StudentServiceImpl implements IStudentService {
@Override
@Cacheable(cacheNames ="student", key ="#studentId")
public StudentVo getStudentVoById(String studentId){
// 查询数据
Student student = ById(studentId);
School school = SchoolId());
// 构造vo
StudentVo studentVo = _pyProperties(student, StudentVo.class);
SchoolVo schoolVo = _pyProperties(school, SchoolVo.class);
studentVo.setSchoolVo(schoolVo);
return studentVo;
}
}
4.表关联的缓存更新问题
 到这⾥,⼤家就可以发现⼀个问题了:虽然getSchoolVoById和getStudentVoById的却是可以缓存,但是getSchoolVoById缓存school对象的同时,也缓存了关联的student。
 那么试想以下,如果通过schoolService.update/delete接⼝修改/删除了school;或者通过studentService.add/update/delete接⼝新增/修改/删除了student,然后在调⽤getSchoolVoById接⼝,获取的缓存数据都是有问题的。
 最简单的解决⽅案当然是在schoolService.update/delete或studentService.add/update/delete的时候,删除关联的缓存
school::id。但是这么做显然效率不⾼,因为即使我进⾏了⼀次很⼩的修改,⽐如修改了school中某⼀个学⽣的name,也要把整个school::id删掉。⽽且更难的是,随着表之间关联表的增多,要删除关联的缓存会越来越多,要维护实时删除这种关联缓存会越来越来,并且系统的效率会越来越慢。
5.解决⽅案与思路
 笔者借鉴mysql的思想,在mysql中,表跟表的关联,即使某个表被修改或者删除了,通过left join等关联查询,也可以⽴刻获取当最新的数据。因为mysql是实时通过主键和外键动态关联起来的,⽽不会把整个关联查询结果缓存起来;⽽且某个表的记录被修改或者删除,只会单单修改或删除被修改表的记录,并不会影响关联表的记录。
 根据上⾯的思路,要解决关联表缓存更新问题,要点有⼆
 ⼀是mysql是单表存储的,那redis也要⼀组key对应⼀张表
 ⼆是mysql在select的时候是实时通过主键和外键动态关联,那么我们在获取vo的时候也要实时根据主键和外键动态关联起来。但是我们怎么实现动态关联呢?显然单纯⽤redis是⽆法实现的,应该是要通过java来实现。并且可以想下数据库多对多关系会新建⼀张关联表保存多对多关系,但是上⾯的student和school是⼀对多的关系,那么怎么实现?!其实⼀对多的关系也可以新建⼀张关系表来存放关联关系!那么我们只要在redis新开⼀组key来保存student和school的关联关系,不就可以实现了吗?
通过上⾯的思路,作出下⾯的redis key-value的设计
redis的key redis的value
school::{id}{id: “”, name: “”} school对象
redis的key redis的value
student::{id}{id: “”, name: “”, schoolId:“”} student对象school::{id}::student[studentId1, studentId2, studentId3…] school中的student
{id}为数据真正的id。
本⽂举的例⼦是⼀对多的关联关系。当多对多的时候,可以拆分为双向⼀对多来解决。
7.根据缓存动态返回vo
实现代码如下,跟sql⼀样,查询关联查询结果时,并不会把关联查询结果缓存起来,⽽是动态的链接关联关系,最后拼装出关联结果@Service("schoolService")
public class SchoolServiceImpl implements ISchoolService {
@Override
public SchoolVo getSchoolVoByIdInCache(String id,boolean isNeedStudent)throws Exception {
//1.根据⼊参id,匹配"school:id",到school(⾛缓存)
ISchoolService _this = _Bean(ISchoolService.class);
School school = _SchoolById(id);
if(null== school){
return null;
}
SchoolVo schoolVo = _pyProperties(school, SchoolVo.class);
if(isNeedStudent){
/
/ 2.根据school:id:student,到关联的studentId(⾛缓存)
String schoolStudentKey = _Id());
List<String> studentIds = ObjectUtil.defaultIfNull(
stringRedisTemplate.opsForList().range(schoolStudentKey,0,-1),
new ArrayList<>(0));
// 3.根据studentId,到student(⾛缓存)
List<StudentVo> studentVoList =new ArrayList<>(studentIds.size());
for(String studentId : studentIds){
StudentVo studentVo = StudentVoByIdInCache(studentId,false);
studentVoList.add(studentVo);
}
/
/ 4.构造Vo返回值(vo不缓存起来)
schoolVo.setStudentList(studentVoList);
}
cacheablereturn schoolVo;
}
@Cacheable(cacheNames = School.cacheName, key ="#id")
@Override
public School getSchoolById(String id){
ById(id);
}
}
@Service("studentService")
public class StudentServiceImpl implements IStudentService {
@Override
public StudentVo getStudentVoByIdInCache(String id,boolean isNeedSchool)throws Exception {
// 1.根据⼊参id,匹配"student:id",到student(⾛缓存)
IStudentService _this = _Bean(IStudentService.class);
Student student = _StudentById(id);
if(null== student){
return null;
}
StudentVo studentVo = _pyProperties(student, StudentVo.class);
/
/ 2.根据schoolId, 匹配"school:schoolId",到school(⾛缓存)
if(isNeedSchool){
String schoolId = SchoolId();
if(StrUtil.isNotBlank(schoolId)){
SchoolVo schoolVo = SchoolVoByIdInCache(schoolId,false);
studentVo.setSchoolVo(schoolVo);
}
}
// 3.构造Vo返回值(vo不缓存起来)
return studentVo;
}
@Cacheable(cacheNames = Student.cacheName, key ="#id", unless ="#result == null")
@Override
public Student getStudentById(String id){
ById(id);
}
}
8.拼装缓存返回Vo的好处
在新增、修改、删除的时候不⽤在删除关联的缓存,加快效率!⽽且特别对于修改操作,只需要修改⾃⼰所在的表和⾃⼰的缓存即可,不需要操作其他的缓存。
student增删改操作
1. 当student新增的时候,只需要新增student记录、缓存student::id。在往school::{id}::student最后push当前studentId
2. 当student被修改的时候,只需要修改student记录和刷新缓存school::id即可,并不需要删除关联缓存
3. 当student被删除的时候,只需要删除student记录、删除缓存school::id、并且在school: :student删除掉当前的studentId school增删改操作
1. 当school新增的时候,只需要新增school记录、缓存school::id
2. 当school被修改的时候,只需要修改school记录和刷新缓存school::id即可,并不需要删除关联缓存
3. 当school被删除的时候,只需要删除student记录、删除缓存school::id,并且删除school: :student整个缓存
9.感谢
 希望本⽂对您有帮助和启发,也欢迎在下⽅留⾔给出更多不同的解决⽅案思路,如果本⽂对您有帮助,请给笔者点赞+关注~

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。