对spring中mongoTemplate的封装

1 场景

spring集成mongo后,可以使用mongoTemplate进行mongo操作。

1.1 当前问题

mongoTemplate写入或者更新数据库时,操作对象可以是Java bean实体类也可以是Map对象

<T> T insert(T objectToSave);
<T> T insert(T objectToSave, String collectionName);

例子如下:

  • 使用map作为存储对象
Map<String,Object> map=new HashMap<>();
map.put("userName","张三");
//年龄为字符串:18岁
map.put("age","18岁");
mongoTemplate.insert(map,"t_user_info");

Map<String,Object> map1=new HashMap<>();
map1.put("userName","张三");
//年龄为数字:18
map1.put("age",18);
mongoTemplate.insert(map1,"t_user_info");
  • 使用java对象作为存储对象
@Data
public class UserInfo{
    private String userName;
    private Integer age;
}
UserInfo userInfo=new UserInfo();
userInfo.setUserName("张三");
//年龄为数字:18
userInfo.setAge(18);
mongoTemplate.insert(userInfo,"t_user_info");

因为mongo中,同一个表中,同一个字段不同数据字段类型可以不一样。即如上示例,使用map作为存储对象,不同的记录,字段age类型,既可以是字符类型“18岁”,也可以是数字类型18,数据库中数据格式如下:

/* 1 */
{
    "_id" : ObjectId("5f8107c5f34b265ecada41ff"),
    "userName" : "张三",
    "age" : "18岁"
}

/* 2 */
{
    "_id" : ObjectId("5f8107c5f34b265ecada4200"),
    "userName" : "张三",
    "age" : 18
}

如只使用java中的bean对象作为存储对象,则同一个表中所有的数据的数据类型均一致,如下:

/* 1 */
{
    "_id" : ObjectId("5f8107c5f34b265ecada4211"),
    "userName" : "张三",
    "age" : 18
}

1.2 选型

java中使用map自定义bean都可以作为mongo的存储对象。我们对这两种方式进行对比:

1.2.1 map作为存储对象
  • 优点

(1)代码简单,定义map即可存储对象

(2)不用为每个专门的表定义java对象

  • 缺点

(1)每次操作数据时,手动定义map的键值。手写代码,容易写错。字段名容易出错,导致名称相似的字段出现;字段值的类型容易出错,导致一个同一个字段不同数据的字段类型不一致,此问题是非常致命的。

(2)mongo增加字段,无需更改表结构。需额外维护mongo表结构说明文档,人工通过文档定义字段的名称、含义和类型

(3)mongo中增加字段无需更改表结构,不需要DBA授权,对mongo字段如果无限制,如果管理不善,容易出现字段的数量暴增

(4)需要人工定义表名称的常量集合。

1.2.1 自定义bean作为存储对象
  • 优点

(1)通过mongo表的映射java对象,可以清晰知道mongo表的表结构(字段名称、字段类型、表名称)

(2)不需要人工定义表名称的常量集合。映射对象上通过注解或者默认名称,可以自动获取mongo表的名称。

  • 缺点

(1)需要手动为每个表建立专门的java对象

(2)原始的mongoTemplate中api接口无法限制所有的操作必须使用java对象进行操作。需要人工进行接口的二次封装(且只能使用封装的接口)。

经过对比,我们选择使用自定义bean作为存储对象。

2 版本

springBoot:2.2.9.RELEASE

mongodb:4.0

3 步骤

3.1 基础代码

(1)定义java bean的父类

import lombok.Data;

import java.io.Serializable;

/**
 * mongo的bean定义
 * <br>实现此接口的类,可以进行mongo数据库操作
 **/
@Data
public class MongoBean implements Serializable {
    
    /**
     * mongo主键(对应数据库中自动生成的字段:_id)
     * 此字段不可设置(mongo自己生成)
     */
    private String id;
    
    /**
     * 删除标志(false:正常;true:删除)
     */
    private Boolean delFlag;
    
    /**
     * 创建人ID
     */
    private String createUserId;
    
    /**
     * 创建人姓名
     */
    private String createUserName;
    
    /**
     * 创建时间
     */
    private Integer createDate;
    
    /**
     * 更新人ID
     */
    private String updateUserId;
    
    /**
     * 更新人姓名
     */
    private String updateUserName;
    
    /**
     * 更新时间
     */
    private Integer updateDate;
}

所有的mongo表映射的java对象,均需继承此父类。

如下:

import org.springframework.data.mongodb.core.mapping.Document;

/**
 * 用户表
 */
@Document("t_user_info")
public class TUserInfo extends MongoBean {
    
    /**
     * 用户名
     */
    private String userName;
    
    /**
     * 年龄
     */
    private Integer age;
}

(2)定义mongo游标执行器

/**
 * mongo游标执行器
 **/
public interface Executor<T> {
    
    /**
     * 执行
     * @param cModel 执行实体类
     * @return void 
     */
    void invoke(T cModel) throws Exception;
}

3.2 部分API封装

3.2.1 插入数据
/**
 * 插入数据
 * @param objectToSave 
 * @return T 
 */
public <T extends MongoBean> T insert(T objectToSave){
    return mongoTemplate.insert(objectToSave);
}

/**
 * 批量插入数据
 * @param batchToSave 
 * @return java.util.Collection<T> 
 */
public <T extends MongoBean> Collection<T> insertAll(Collection<? extends T> batchToSave){
    return mongoTemplate.insertAll(batchToSave);
}
3.2.2 条件删除
/**
 * 条件删除
 * @param query
 * @param entityClass
 * @return
 */
public <T extends MongoBean> DeleteResult remove(Query query, Class<T> entityClass){
    return mongoTemplate.remove(query,entityClass);
}
3.2.3 查询数据
/**
 * 查询满足条件记录
 * @param query
 * @param entityClass
 * @return java.util.List<T>
 */
public <T extends MongoBean> List<T> find(Query query, Class<T> entityClass){
    return mongoTemplate.find(query,entityClass);
}

/**
 * 查询第一条
 * @param query 
 * @param entityClass 
 * @return T 
 */
public <T extends MongoBean> T findOne(Query query, Class<T> entityClass){
    return mongoTemplate.findOne(query,entityClass);
}

/**
 * 根据主键查询
 * @param id 
 * @param entityClass 
 * @return T 
 */
public <T extends MongoBean> T findById(Object id, Class<T> entityClass){
    return mongoTemplate.findById(id,entityClass);
}

/**
 * 查询总数
 * @param query 
 * @param entityClass 
 * @return long 
 */
public <T extends MongoBean> long count(Query query, Class<T> entityClass){
    return mongoTemplate.count(query,entityClass);
}
3.2.4 更新数据
/**
 * 更新第一条
 * @param query 查询条件
 * @param mongoBean 要更新的实体
 * @param updateFields 
 * @return com.mongodb.client.result.UpdateResult 
 */
public <T extends MongoBean> UpdateResult extUpdateFirst(Query query, T mongoBean,String... updateFields) throws Exception{
    Update update=getUpdateFromBean(mongoBean,updateFields);
    return mongoTemplate.updateFirst(query,update,mongoBean.getClass());
}

/**
 * 批量更新
 * @param query 查询条件
 * @param mongoBean 要更新的实体
 * @param updateFields 要更新的字段(有参数时,更新指定的字段;无此参数时,更新mongoBean所有不为空的字段)
 * @return com.mongodb.client.result.UpdateResult 
 */
public <T extends MongoBean> UpdateResult extUpdateMulti(Query query, T mongoBean,String... updateFields) throws Exception{
    Update update=getUpdateFromBean(mongoBean,updateFields);
    return mongoTemplate.updateMulti(query,update,mongoBean.getClass());
}
3.2.5 游标查询
/**
 * 获取mongo游标(需要手动关闭)
 * @param query 查询对象
 * @param entityClass 查询实体
 * @param batchSize 批次大小(默认1000,需大于0)
 * @param pageNum 当前页数
 * @param pageSize 每页大小
 * @return com.mongodb.client.MongoCursor<org.bson.Document> 
 */
<T extends MongoBean> MongoCursor<Document> extGetMongoCursor(Query query, Class<T> entityClass, Integer batchSize, Integer pageNum, Integer pageSize){
    if(query==null || entityClass==null){
        return null;
    }
    MongoCollection<Document> collection=mongoTemplate.getCollection(mongoTemplate.getCollectionName(entityClass));
    FindIterable<Document> findIterable=collection.find(query.getQueryObject());
    ////----------填充游标属性----------
    //(1)游标不超时
    findIterable.noCursorTimeout(true);
    //(2)批次拉取大小(默认1000)
    if(batchSize==null || batchSize<=0){
        batchSize=DEFAULT_CURSOR_BATCH_SIZE;
    }
    findIterable.batchSize(batchSize);
    //(3)排序
    findIterable.sort(query.getSortObject());
    //(4)跳过记录数
    if(pageNum!=null && pageSize!=null){
        findIterable.skip((pageNum - 1) * pageSize);
        findIterable.limit(pageSize);
    }
    
    return findIterable.cursor();
}

/**
 * 执行游标查询
 * @param query 查询器
 * @param entityClass 查询实体
 * @param batchSize 批次大小
 * @param pageNum 当前页
 * @param pageSize 每次大小
 * @param executor 执行器
 * @return void 
 */
public <T extends MongoBean> void extCursorQueryExe(Query query, Class<T> entityClass, Integer batchSize, Integer pageNum, Integer pageSize, Executor<T> executor) throws Exception{
    if(executor==null){
        return ;
    }
    try (MongoCursor<Document> cursor = this.extGetMongoCursor(query,entityClass,batchSize,pageNum,pageSize)) {
        if(cursor==null){
            return ;
        }
        T model;
        while (cursor.hasNext()) {
            model = mongoConverter.read(entityClass, cursor.next());
            executor.invoke(model);
        }
    } catch (Exception e) {
        throw e;
    }
}

----------完整代码,可私信联系博主----------

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341