一、业务需求

假设某学校课程系统,不同专业课程不同 (可以动态增删),但是需要根据专业不同显示该专业学生的各科课程的成绩,如下:

专业 姓名 高等数学 数据结构
计算机 张三 90 85
计算机 李四 78 87
专业 姓名 高等数学
数学 王五 86
数学 赵六 95

二、设计思路

开始的思路是根据配置的课程动态生成文档字段,使用非映射方式直接操作 MongoCollection, 有以下问题:

  1. 存取数据日期序列化问题 (亦可能是本人没有找到正确的处理方式)
  2. 返回结果集不能转换成实体对象,不方便做二次处理

所以最终使用内嵌数组的方式

三、代码示例

3.1 实体类

public class Student {

    private String name;
private String major;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@JsonIgnore
private List<Course> courseList; /* getter setter*/ } public class Course { private String name;
private Float score; /* getter setter*/
}

3.2 添加测试专业及课程

public class MajorConfig {

    private static MajorConfig computer;
private static MajorConfig math; static {
computer = new MajorConfig();
computer.setName("计算机"); List<String> courseList = new ArrayList<>();
courseList.add("高等数学");
courseList.add("数据结构");
computer.setCourseList(courseList); math = new MajorConfig();
math.setName("数学");
courseList = new ArrayList<>();
courseList.add("高等数学");
math.setCourseList(courseList); } private String name;
private List<String> courseList; /* getter setter*/
}

3.3 添加测试数据到 MongoDB

初始化数据 (createTime 在 MongoDB 存储为 ISODate):

[
{
"name": "张三",
"major": "计算机",
"createTime": "2018-01-20 08:00:00",
"courseList": [
{
"name": "高等数学",
"score": 20.283026
},
{
"name": "数据结构",
"score": 30.612194
}
]
},
{
"name": "王五",
"major": "数学",
"createTime": "2018-01-20 08:00:00",
"courseList": [
{
"name": "高等数学",
"score": 91.78229
}
]
},
{
"name": "李四",
"major": "计算机",
"createTime": "2019-10-01 07:10:50",
"courseList": [
{
"name": "高等数学",
"score": 60.488556
},
{
"name": "数据结构",
"score": 80.66098
}
]
},
{
"name": "赵六",
"major": "数学",
"createTime": "2019-10-01 07:10:50",
"courseList": [
{
"name": "高等数学",
"score": 29.595625
}
]
}
]

3.4 根据专业获取学生课程分数列表

首先查询到对应的数据,然后根据配置的课程动态添加字段:

  public Object list(String major){

      MajorConfig majorConfig = getMajorConfig(major);

      Query query = new Query(Criteria.where("major").is(major));
List<Student> studentList = mongoTemplate.find(query, Student.class); List<Object> result = new ArrayList<>(); for(Student student:studentList){ Map<String,Object> properties = Maps.newHashMap();
for(String name : majorConfig.getCourseList()){
properties.put(name,null);
for(Course course : student.getCourseList()){
if(name.equals(course.getName())){
properties.put(name,course.getScore());
break;
}
}
} result.add(ReflectUtil.getTarget(student,properties));
} return result;
}

各专业学生各科分数数据:

[
{
"name": "王五",
"major": "数学",
"createTime": "2018-01-20 08:00:00",
"高等数学": 91.78229
},
{
"name": "赵六",
"major": "数学",
"createTime": "2019-10-01 07:10:50",
"高等数学": 29.595625
}
]
[
{
"name": "张三",
"major": "计算机",
"createTime": "2018-01-20 08:00:00",
"高等数学": 20.283026,
"数据结构": 30.612194
},
{
"name": "李四",
"major": "计算机",
"createTime": "2019-10-01 07:10:50",
"高等数学": 60.488556,
"数据结构": 80.66098
}
]

ReflectUtil 代码:

public class ReflectUtil {

    public static Object getTarget(Object dest, Map<String, Object> addProperties) {
PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean();
PropertyDescriptor[] descriptors = propertyUtilsBean.getPropertyDescriptors(dest); Map<String, Class> propertyMap = Maps.newHashMap(); for (PropertyDescriptor d : descriptors) {
if (!"class".equalsIgnoreCase(d.getName())) {
propertyMap.put(d.getName(), d.getPropertyType());
} }
// add extra properties
addProperties.forEach((k, v) -> {
if(v != null){
propertyMap.put(k, v.getClass());
}else{
propertyMap.put(k, Object.class);
}
});
// new dynamic bean
DynamicBean dynamicBean = new DynamicBean(dest.getClass(), propertyMap);
// add old value
propertyMap.forEach((k, v) -> {
try {
// filter extra properties
if (!addProperties.containsKey(k)) {
dynamicBean.setValue(k, propertyUtilsBean.getNestedProperty(dest, k));
}
} catch (Exception e) {
e.printStackTrace();
}
});
// add extra value
addProperties.forEach((k, v) -> {
try {
dynamicBean.setValue(k, v);
} catch (Exception e) {
e.printStackTrace();
}
});
Object target = dynamicBean.getTarget();
return target;
}
}

DynamicBean 代码:

public class DynamicBean {

    private Object target;

    private BeanMap beanMap;

    public DynamicBean(Class superclass, Map<String, Class> propertyMap) {
this.target = generateBean(superclass, propertyMap);
this.beanMap = BeanMap.create(this.target);
} public void setValue(String property, Object value) {
beanMap.put(property, value);
} public Object getValue(String property) {
return beanMap.get(property);
} public Object getTarget() {
return this.target;
} private Object generateBean(Class superclass, Map<String, Class> propertyMap) {
BeanGenerator generator =new BeanGenerator();
if(null != superclass) {
generator.setSuperclass(superclass);
}
BeanGenerator.addProperties(generator, propertyMap);
return generator.create();
}
}

3.5 添加课程并修改数据库

public Object add(String major, String name, Boolean updateDatabase){

        MajorConfig majorConfig = getMajorConfig(major);

        List<String> courseList = majorConfig.getCourseList();

        if(!courseList.contains(name))
{
courseList.add(name);
majorConfig.setCourseList(courseList);
} if(updateDatabase){ Random random = new Random(); Course course = new Course();
course.setName(name);
course.setScore(random.nextFloat() * 100); Update update = new Update();
update.addToSet("courseList", course);
Query query = Query.query(Criteria.where("major").is(majorConfig.getName()));
mongoTemplate.updateMulti(query, update, Student.class); } return majorConfig;
}

为数学专业添加计算机基础:

[
{
"name": "王五",
"major": "数学",
"createTime": "2018-01-20 08:00:00",
"计算机基础": 9.042096,
"高等数学": 91.78229
},
{
"name": "赵六",
"major": "数学",
"createTime": "2019-10-01 07:10:50",
"计算机基础": 9.042096,
"高等数学": 29.595625
}
]

3.6 删除课程并修改数据库

public Object del(String major, String name, Boolean updateDatabase){

        MajorConfig majorConfig = getMajorConfig(major);

        List<String> courseList = majorConfig.getCourseList();

        if(courseList.contains(name)){
courseList.remove(name);
majorConfig.setCourseList(courseList);
} if(updateDatabase){
Update update = new Update();
update.pull("courseList", new BasicDBObject("name", name));
Query query = Query.query(Criteria.where("major").is(majorConfig.getName()));
mongoTemplate.updateMulti(query,update,Student.class);
} return majorConfig;
}

把高等数学从计算机专业删除:

[
{
"name": "张三",
"major": "计算机",
"createTime": "2018-01-20 08:00:00",
"数据结构": 30.612194
},
{
"name": "李四",
"major": "计算机",
"createTime": "2019-10-01 07:10:50",
"数据结构": 80.66098
}
]

3.7 修改某学生的某课程分数

public Object update(String name, String courseName, Float score){

    Query query = Query.query(Criteria.where("name").is(name).and("courseList.name").is(courseName));

    Update update = new Update();
update.set("courseList.$.score", score); mongoTemplate.updateFirst(query, update, Student.class); return null;
}

完整代码:GitHub

基于 MongoDB 动态字段设计的探索的更多相关文章

  1. 基于 MongoDB 动态字段设计的探索 (二) 聚合操作

    业务需求及设计见前文:基于 MongoDB 动态字段设计的探索 根据专业计算各科平均分 (总分.最高分.最低分) public Object avg(String major){ Aggregatio ...

  2. 如何在Spring Data MongoDB 中保存和查询动态字段

    原文: https://stackoverflow.com/questions/46466562/how-to-save-and-query-dynamic-fields-in-spring-data ...

  3. MongoDB 进阶模式设计

    原文链接:http://www.mongoing.com/mongodb-advanced-pattern-design 12月12日上午,TJ在开源中国的年终盛典会上分享了文档模型设计的进阶技巧,就 ...

  4. 适用于app.config与web.config的ConfigUtil读写工具类 基于MongoDb官方C#驱动封装MongoDbCsharpHelper类(CRUD类) 基于ASP.NET WEB API实现分布式数据访问中间层(提供对数据库的CRUD) C# 实现AOP 的几种常见方式

    适用于app.config与web.config的ConfigUtil读写工具类   之前文章:<两种读写配置文件的方案(app.config与web.config通用)>,现在重新整理一 ...

  5. SOA实践之基于服务总线的设计

    在上文中,主要介绍了SOA的概念,什么叫做“服务”,“服务”应该具备哪些特性.本篇中,我将介绍SOA的一种很常见的设计实践--基于服务总线的设计. 基于服务总线的设计 基于总线的设计,借鉴了计算机内部 ...

  6. 基于Mongodb的轻量级领域驱动框架(序)

    混园子也有些年头了,从各个大牛那儿学了很多东西.技术这东西和中国的料理一样,其中技巧和经验,代代相传(这不是舌尖上的中国广告).转身回头一望,几年来自己也积累了一些东西,五花八门涉猎到各种方向,今日开 ...

  7. 基于Apriori算法的Nginx+Lua+ELK异常流量拦截方案 郑昀 基于杨海波的设计文档(转)

    郑昀 基于杨海波的设计文档 创建于2015/8/13 最后更新于2015/8/25 关键词:异常流量.rate limiting.Nginx.Apriori.频繁项集.先验算法.Lua.ELK 本文档 ...

  8. 基于MongoDB.Driver的扩展

    由于MongoDB.Driver中的Find方法也支持表达式写法,结合[通用查询设计思想]这篇文章中的查询思想,个人基于MongoDB扩展了一些常用的方法. 首先我们从常用的查询开始,由于MongoD ...

  9. solr 通过【配置、多值字段、动态字段】来解决文本表达式查询精确到句子的问题

    一.Solr Multivalue field属性positionIncrementGap理解 分类:Lucene 2014-01-22 10:39阅读(3596)评论(0) 参考:http://ro ...

随机推荐

  1. Hill密码解密过程(Java)

    Hill密码是一种传统的密码体系.加密原理:选择一个二阶可逆整数矩阵A称为密码的加密矩阵,也就是这个加密体系的密钥.加密过程: 明文字母依次逐对分组,例如加密矩阵为二阶矩阵,明文就两个字母一组,如果最 ...

  2. 调试没有core文件的coredump

    对coredump的分析中,是依赖于core文件的,而core文件中也几乎包含了程序当前的所有状态(堆栈.内存.寄存器等).然而在实际的线上环境中,由于core文件太大.保存core文件耗时太久,出于 ...

  3. linux中几个文本文件查看命令

    Linux中,常用的文本文件查看命令介绍如下: 1. cat 用法: cat [options] filename options: -A: 显示全部. -E: 在每一行的后面加上"$&qu ...

  4. 运维和shell

    什么是运维 术语名词 IDC--(Internet Data Center)互联网数据中心,主要服务包括整机租用.服务器托管.机柜租用.机房租用.专线接入和网络管理服务等.广义上的IDC业务,实际上就 ...

  5. Python可视化界面

    可视化界面程序,本来不想写,只在console台运行就好,但是后来很多小伙伴都有这样的需求: 需要从redis中删除某个key的value,然后需要跟key去查,有些小伙伴不会用redis,就产生如下 ...

  6. 基于gin的golang web开发:中间件

    gin中间件(middleware)提供了类似于面向切面编程或路由拦截器的功能,可以在请求前和请求之后添加一些自定义逻辑.实际开发中有很多场景会用到中间件,例如:权限验证,缓存,错误处理,日志,事务等 ...

  7. 调整PG分多次调整和一次到位的迁移差别分析

    前言 这个问题来源于我们研发的一个问题,在进行pg调整的时候,是一次调整到位好,还是分多次调整比较好,分多次调整的时候会不会出现某个pg反复挪动的问题,造成整体迁移量大于一次调整的 最近自己的项目上也 ...

  8. 配置xenserver本地存储

    查询磁盘对应关系: [root@xenserver-eqtwbths ~]# ll /dev/disk/by-id/ total 0 lrwxrwxrwx 1 root root 9 Jun 5 13 ...

  9. 深度分析:那些Java中你一定遇到过的问题,一次性帮你搞定!深度分析:那些Java中你一定遇到过的问题,一次性帮你搞定!

    1.java中==和equals和hashCode的区别 基本数据类型的比较的值相等.类的比较的内存的地址,即是否是同一个对象,在不覆盖equals的情况下,同比较内存地址,原实现也为 == ,如St ...

  10. 深度分析:面试腾讯,阿里面试官都喜欢问的String源码,看完你学会了吗?

    前言 最近花了两天时间,整理了一下String的源码.这个整理并不全面但是也涵盖了大部分Spring源码中的方法.后续如果有时间还会将剩余的未整理的方法更新到这篇文章中.方便以后的复习和面试使用.如果 ...