2 Star 2 Fork 0

王进潮 / wangic-mongo

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
README.md 28.54 KB
一键复制 编辑 原始数据 按行查看 历史
王进潮 提交于 2020-07-15 09:55 . update README.md.

wangic-mongo

介绍

一套对于mongoDB操作的半彻底封装

这是一款能够在关键时刻分离数据,给MySQL减压的应用

1.为什么要分离

  1. 我们在日常开发过程中,数据表的文本字段习惯性的定义为varchar,长度不够就改为text,blog,继续不够再改为longtext(这里强烈建议一些status,is_delete……之类取值范围明确的状态字段用tinyint,具体细节可查看《阿里巴巴java开发规范》)。方便且简洁,无可厚非。但是有一个潜在的隐患就是,当业务表的数据量达到一定的高度,且表中还存在longtext这种字段时,那么读写时长文本字段抢占了性能,效率将会变得很慢。

2.分离的方式

  1. 这时,一般的做法是,将这种字段给分离出来单独一张表,采用id的形式来关联。
  2. 那么这里又有了一个问题,如果你的MySQL应用做了读写分离,或者是有集群管理的话,到也不要紧。可如果是单节点应用,所有相关的请求操作一瞬间全部到达MySQL的话,将会给系统带来极大的负担,根据“木桶效应”来分析,系统的短板决定着瓶颈,应用资源要适当的均分和权重,所以我这里将富文本/长文本全部分离到mongoDB,采用mongoId来与MySQL关联,所有的图片也摒弃了臃肿的base64码,采用文件存储的形式,反馈的链接与MySQL关联。单台服务器(2核8G/50GSSD/5M),似乎这种做法才能从明面上缓解下数据存储的压力。

3.MongoDB的安装

1.下载地址:http://www.mongodb.org/downloads (我下载的版本是mongodb-linux-x86_64-4.0.11) 2.安装如下:

//进入到指定的安装目录(我一般安装在/usr/local下),上传,解压,重命名
rz mongodb-linux-x86_64-4.0.11.tgz
tar -zxvf mongodb-linux-x86_64-4.0.11.tgz
mv mongodb-linux-x86_64-4.0.11 mongodb

//配置环境变量
vim /etc/profile
export PATH=/usr/local/mongodb/bin:$PATH

//保存退出,重新加载
source /etc/profile

//创建MongoDB数据存放文件夹和日志记录文件夹,为后面的配置文件使用:
mkdir -pv /usr/local/mongodb_file/data
mkdir -pv /usr/local/mongodb_file/logs

//创建配置文件,进入到MongoDB的bin目录下
vim mongodb.conf(直接是new file)

//编辑输入
dbpath = /usr/local/mongodb_file/data #数据文件存放目录
logpath = /usr/local/mongodb_file/logs/mongodb.log #日志文件存放目录
port = 27017 #端口,强烈建议更改
fork = true#以守护程序的方式启用,即在后台运行
auth=true #需要认证。如果放开注释,就必须创建MongoDB的账号,使用账号与密码才可远程访问,第一次安装建议注释,后面放开
bind_ip=127.0.0.1 #允许远程访问(0.0.0.0),或者直接注释掉,127.0.0.1是只允许本地访问

//启动MongoDB服务,进入bin目录下
./mongod  -f  mongodb.conf

//连接mongo,进入bin目录下
mongo
//如果这里改了端口,则连接为:
mongo localhost:port

//关闭mongo,查出pid,使用kill杀死对应的pid
ps -aux|grep mongo
kill -9 pid

3.用户密码权限的配置 如果你的应用部署在线上,切记一定要重视网络安全管理,改端口,添加用户密码,权限分配,再严谨一点就设置ip白名单。

//连接mongo
mongo localhost:****//如果没改端口,直接mongo,如果改了端口就指定端口连接

use admin
db.createUser({ user: "user", pwd: "password", roles: [{ role: "userAdminAnyDatabase",db: "admin" }] })
//userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限

//验证权限
db.auth("user","password")

//修改权限
db.updateUser("user",{roles: [ "role","readWriteAnyDatabase",db: "admin"]})
//readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限

//其他的权限请自行百度或查询官方文档,我这里为方便,直接用readWriteAnyDatabase

4.代码的设计与封装

  1. 这里采用hutool将mongoDB相关的业务操作做了一次比较彻底的封装,接入几乎无需做什么调整,直接传入DBName和tableName即可。

  2. 此处的代码设计和思路采用了前任开发组组长的封装,这是他的CSDN文章地址,点击可查看

  3. 我新增了用户密码的设定和权限分配 hutool其实也封装的不是很完整,它采用连接池的方式来提供mongoClie1. nt,并未有具体的增删改查,而且相关文档也给的很简洁,需要我们自己进去阅读源码才能看懂代码贡献者的思路,然后在此基础上补充

    架构

    maven项目,分两个模块,一个是base,一个是测试

模块名称 作用
wangjc-mongo-base 对于mongoDB的封装,config,实体的基类,service的基类(提供了很多方法),util…………
wangjc-mongo-web-test 测试,基本的增删改查
  1. base的核心代码展示

配置类,加载setting配置文件
/**
 * mongoDB的配置
 * @author wangjc
 * @title: MongoConfig
 * @projectName wangjc-mongo
 * @description: TODO
 */
public class MongoConfig {

    private static final Log logger = LogFactory.get(MongoConfig.class);
    /**
     * MongoDB实例连接列表
     */
    private static MongoDS DS;

    /** 初始化MongoDS */
    static {
        try {
            /** 配置文件的各个配置项 */
            Setting setting = new Setting(MongoDS.MONGO_CONFIG_PATH);
            GroupedMap groupsMap = setting.getGroupedMap();
            List<String> groups = new ArrayList<String>();
            Set<Map.Entry<String, LinkedHashMap<String, String>>> set = groupsMap.entrySet();
            for(Map.Entry<String,LinkedHashMap<String,String>> entry:set){
                if(entry.getKey() == null || entry.getKey() == ""){
                    continue;
                }
                String host = entry.getValue().get(MongoDBConstant.HOST);
                if(host == null || host == ""){
                    continue;
                }
                logger.info("MongoDB初始化数据源[{}]信息",entry.getKey());
                logger.info("MongoDB初始化数据源host[{}]信息",host);
                groups.add(entry.getKey());
            }
            if(groups.size() > 0){
                DS = MongoFactory.getDS(groups);
            }else{
                DS = MongoFactory.getDS(MongoDBConstant.DS_GROUP);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 获取实例连接
     * @return
     */
    public static MongoDS getMongoDS(){
        return DS;
    }
}
基础的实体
/**
 * @author wangjc
 * @title: MongoBaseEntity
 * @projectName wangjc-mongo
 * @description: TODO
 */
public class MongoBaseEntity implements Serializable {

    private static final long serialVersionUID = 7923869056556509811L;

    /**
     * 在MongoDB中,存储于集合中的每一个文档都需要一个唯一的 _id 字段作为 primary_key。如果一个插入文档操作遗漏了``_id``
     * 字段,MongoDB驱动会自动为``_id``字段生成一个 ObjectId,
     */
    private ObjectId mongoId;

    public ObjectId getMongoId() {
        return mongoId;
    }

    public void setMongoId(ObjectId mongoId) {
        this.mongoId = mongoId;
    }
}
封装的抽象类,预留三个抽象方法来设置DBName,tableName,泛型约束
/**
 * @author wangjc
 * @title: MongoBaseService
 * @projectName wangjc-mongo
 * @description: TODO
 */
public abstract class MongoBaseService<T extends MongoBaseEntity> {

    private static final Log logger = LogFactory.get(MongoBaseService.class);

    /**
     * 数据库名称
     * @return
     */
    public abstract String getDbName();

    /**
     * 表(集合)名称
     * @return
     */
    public abstract String getTableName();

    /**
     * 对应实体
     * @return
     */
    public abstract Class<T> getClazz();
    /**
     * 获取连接
     * @return
     */
    protected MongoCollection<Document> getCollection(){
        return MongoConfig.getMongoDS().getCollection(getDbName(),getTableName());
    }

    /**
     * 根据mongoId查询出对应的bean
     * @param mongoId
     * @return
     */
    public T selectByMongoId(String mongoId){
        MongoCollection<Document> table = getCollection();
        Document query = getIdQuery(mongoId);
        Document document = table.find(query).first();
        if(document == null){
            return null;
        }else{
            return documentToEntity(document);
        }
    }

    /**
     * 根据mongoId查询
     * @param mongoId
     * @return
     */
    public T selectByMongoId(ObjectId mongoId){
        return selectByMongoId(mongoId.toHexString());
    }

    /**
     * 根据实体对象字段值,获取单条
     * @param entity
     * @return
     */
    public T selectOne(T entity){
        List<T> list = listByEntity(entity);
        if(list.size() > 0){
            return list.get(0);
        }else {
            return null;
        }
    }

    /**
     * 根据实体对象字段查询
     * @param entity:为空时查全部,
     * @return
     */
    public List<T> listByEntity(T entity){
        return listByEntity(entity,null);
    }
    /**
     * 根据实体对象字段查询,并排序
     * @param entity:为null时,默认查询全部
     * @param sort 为null时默认不排序,设置排序字段为1升序,-1降序
     * @return
     */
    public List<T> listByEntity(T entity,T sort){
        try {
            MongoCollection<Document> table = getCollection();
            Document query = getDocumentByEntity(entity);
            logger.info("==============================listByEntity get query document is:[{}]",query.toJson().toString());
            final List<T> ret = new ArrayList<T>();

            Block<Document> processBlock = new Block<Document>() {
                public void apply(final Document document) {
                    ret.add(documentToEntity(document));
                }
            };

            if(sort == null){
                table.find(query).forEach(processBlock);
            }else{
                Document document = getDocumentByEntity(sort);
                table.find(query).sort(document).forEach(processBlock);
            }
            return ret;
        }catch (Exception e){
            logger.error("listByEntity error:[{}]",e.getMessage());
        }
        return new ArrayList<T>();
    }

    /**
     * 查询所有
     * @return
     */
    public List<T> listAll(){
        return listAll(null);
    }

    /**
     * 查询所有并排序
     * @param sort
     * @return
     */
    public List<T> listAll(T sort){
        return listByEntity(null,sort);
    }

    /**
     * 插入单条记录
     * @param one
     * @return:返回mongoId
     */
    public String insertOne(T one){
        if(one == null){
            return null;
        }
        MongoCollection<Document> table = getCollection();

        Document docine = Document.parse(JSON.toJSONString(one));//转为json格式
        docine.remove(MongoDBConstant.MongoId);
        docine.put(MongoDBConstant.MongoId,new ObjectId());
        table.insertOne(docine);
        return docine.get(MongoDBConstant.MongoId).toString();
    }

    /**
     * 更新数据,多余的字段,会在集合的这条记录上追加
     * @param one
     * @return
     */
    public Long updateOne(T one){
        Long num = 0L;
        try {
            MongoCollection<Document> table = getCollection();
            Document updateSet = getUpdateSet(one);
            logger.info("=======================updateOne mongoId is :[{}],get update set document is :[{}]",one.getMongoId().toString(),updateSet.toJson().toString());

            UpdateResult result = table.updateMany(getIdQuery(one.getMongoId()), updateSet);
            num = result.getModifiedCount();
            logger.info("updateOne:[{}]",num);
        }catch (Exception e){
            logger.info("updateOne error [{}]",e.getMessage());
        }
        return num;
    }

    /**
     * 批量更新
     * @param query:用于查询条件的实体对象(字段)
     * @param upset:用于更新的实体对象(字段)
     * @return
     */
    public Long updateBatch(T query,T upset){
        Long num = 0L;
        try {
            Document qfilter = getDocumentByEntity(query);
            logger.info("=======================updateBatch get query document is :[{}]",qfilter.toJson().toString());
            Document updateSet = getUpdateSet(upset);
            logger.info("=======================updateBatch get update set document is :[{}]",updateSet.toJson().toString());
            UpdateResult result = getCollection().updateMany(qfilter, updateSet);
            num = result.getModifiedCount();
            logger.info("updateBatch ModifiedCount;[{}]",num);
        }catch (Exception e){
            logger.info("updateBatch error [{}]",e.getMessage());
        }
        return num;
    }

    /**
     * 删除,根据mongoId
     * @param mongoId
     * @return
     */
    public Long deleteOne(ObjectId mongoId){
        return deleteOne(mongoId.toHexString());
    }
    /**
     * 删除,根据mongoId
     * @param mongoId
     * @return
     */
    public Long deleteOne(String mongoId){
        Document query = getIdQuery(mongoId);
        MongoCollection<Document> table = getCollection();
        DeleteResult result = table.deleteOne(query);
        return result.getDeletedCount();
    }
    /**
     * 批量删除,根据实体类的字段值
     * @param entity
     * @return
     */
    public Long deleteByEntity(T entity){
        if(entity == null){
            logger.info("deleteByEntity error: update entity is null");
            return null;
        }
        try {
            Document document = getDocumentByEntity(entity);
            logger.info("===========================deleteByEntity get query document is : [{}]",document.toJson().toString());
            if(document.size() <= 0){
                logger.error("deleteByEntity error:entity doesn't have any properties");
            }
            DeleteResult result = getCollection().deleteMany(document);
            return result.getDeletedCount();
        }catch (Exception e){
            logger.info("deleteByEntity error [{}] class:[{}]",e.getMessage(),entity.getClass().getName());
            return null;
        }
    }

    /**
     * 根据实体来获取Document
     * @param entity
     * @return
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    private Document getDocumentByEntity(T entity) throws IllegalAccessException,InvocationTargetException{
        Document query = new Document();
        if(entity == null){
            return query;
        }
        Field[] fields = MongoUtil.getFields(getClazz());
        for(Field f:fields){
            if(MongoDBConstant.SerialVersionUID.equals(f.getName())){//过滤掉序列化id
                continue;
            }
            f.setAccessible(true);
            Object obj = f.get(entity);
            if(obj == null){
                continue;
            }
            query.append(f.getName(),obj);
        }
        return query;
    }

    /**
     * 根据Document获取实体
     * @param document
     * @return
     */
    private T documentToEntity(Document document){
        try {
            T t = getClazz().newInstance();
            Field[] fields = MongoUtil.getFields(getClazz());
            for (Field f:fields){
                if(document.get(f.getName()) == null){
                    continue;
                }
                Object value = document.get(f.getName());
                if(value == null){
                    continue;
                }
                if(f.getName().equals(MongoDBConstant.MongoId)){
                    value = new ObjectId(String.valueOf(value));
                }
                setFieldValue(t,f,value);
            }
            return t;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 给字段设置值
     * @param t
     * @param field
     * @param value
     */
    private void setFieldValue(T t,Field field,Object value){
        try {
            field.setAccessible(true);//设置可见
            field.set(t,value);
        } catch (IllegalAccessException e) {
            logger.error("IllegalAccess异常 [{}],[{}]",t.getClass(),field.getName());
            e.printStackTrace();
        }
    }

    /**
     * 获取更新后的document
     * @param entity
     * @return
     */
    private Document getUpdateSet(T entity){
        Document set = null;
        try {
            set  = getDocumentByEntity(entity);
            set.remove(MongoDBConstant.MongoId);
            set = new Document("$set",set);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return set;
    }

    /**
     * 通过id获取document
     * @param mongoId
     * @return
     */
    private Document getIdQuery(String mongoId){
        return getIdQuery(new ObjectId(mongoId));
    }
    private Document getIdQuery(Object id){
        Document query = new Document();
        query.append(MongoDBConstant.MongoId,id);
        return query;
    }
}
工具类
/**
 * mongo的工具类
 * @author wangjc
 * @title: MongoUtil
 * @projectName wangjc-mongo
 * @description: TODO
 */
public class MongoUtil {

    /**
     * 线程安全的map
     */
    private static final Map<String, Field[]> FIELDS_CACHE = new ConcurrentHashMap<String, Field[]>();

    public static Field[] getFields(Class clazz){
        Field[] fields = FIELDS_CACHE.get(clazz.toString());
        if(null != fields){
            return fields;
        }else{
            fields = getFieldsDirectly(true,clazz);
            FIELDS_CACHE.put(clazz.toString(),fields);
            return fields;
        }
    }

    /**
     * 获取类的字段数组
     * @param withSuperClassFields:(是否直接带有父类)
     * @param claz
     * @return
     */
    public static Field[] getFieldsDirectly(boolean withSuperClassFields,Class<?> claz){
        Field[] allFields = null;
        Class<?> searchType = claz;
        Field[] declaredFields;
        while (searchType != null){
            declaredFields = searchType.getDeclaredFields();
            if(allFields == null){
                allFields = declaredFields;
            }else{
                allFields = append(allFields,declaredFields);
            }
            searchType = withSuperClassFields ? searchType.getSuperclass():null;
        }
        return allFields;
    }
    /**
     * 追加字段
     * @param buffer
     * @param newElements
     * @return
     */
    private static Field[] append(Field[] buffer,Field... newElements){
        if(isEmpty(buffer)){
            return newElements;
        }else{
            return insert(buffer,buffer.length,newElements);
        }
    }
    /**
     * 字段添加
     * @param array
     * @param index
     * @param newFieldlements
     * @return
     */
    private static Field[] insert(Field[] array,int index,Field... newFieldlements){
        if(isEmpty(newFieldlements)){
            return array;
        }
        if(isEmpty(array)){
            return newFieldlements;
        }
        final int len = length(array);
        if(index < 0){
            index = (index % len) + len;
        }

        Field[] result = newArray(array.getClass().getComponentType(),Math.max(len,index)+newFieldlements.length);
        System.arraycopy(array,0,result,0,Math.min(len,index));
        System.arraycopy(newFieldlements,0,result,index,newFieldlements.length);
        if(index < len){
            System.arraycopy(array,index,result,index+newFieldlements.length,len - index);
        }
        return result;
    }
    /**
     * 获取新的字段数组
     * @param componentType
     * @param newSize
     * @return
     */
    private static Field[] newArray(Class<?> componentType,int newSize){
        return (Field[]) Array.newInstance(componentType,newSize);
    }
    /**
     * 判断类的字段非空
     * @param array
     * @return
     */
    private static boolean isEmpty(Field... array){
        return array == null || array.length == 0;
    }
    /**
     * 获取指定数组类长度,私有
     * @param array
     * @return
     */
    private static int length(Object array) throws IllegalArgumentException{
        if(null == array){
            return 0;
        }else {
            return Array.getLength(array);
        }
    }
}
  1. 应用

实体,就是简单的java bean
/**
 * 测试实体
 * @author wangjc
 * @title: MongText
 * @projectName wangjc-mongo
 * @description: TODO
 */
public class MongoTextInfo extends MongoBaseEntity{

    private static final long serialVersionUID = 3742429841077146407L;

    /**
     * 内容
     */
    private String content;

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}
service,继承base中的抽象类,重写三个方法注入值
/**
 * 这里的dbName,tableName建议以枚举或者常量的方式统一存放,以便全局更改,
 * 此处是为了方便快捷省事
 * @author wangjc
 * @title: MongoText
 * @projectName wangjc-mongo
 * @description: TODO
 */
@Component
public class MongoTextService extends MongoBaseService<MongoTextInfo> {

    @Override
    public String getDbName() {
        return "text";
    }

    @Override
    public String getTableName() {
        return "text_info";
    }

    @Override
    public Class<MongoTextInfo> getClazz() {
        return MongoTextInfo.class;
    }
}
controller测试,增删改查
/**
 * @author wangjc
 * @title: TestController
 * @projectName wangjc-mongo
 * @description: TODO
 */
@RestController
public class TestController {

    @Autowired
    private MongoTextService mongoTextService;

    /**
     * 新增
     * @return
     */
    @RequestMapping(value = "/insert")
    public String insert(){
        String info = "这是一段长达250KB的富文本信息";
        MongoTextInfo textInfo = new MongoTextInfo(){{
            setContent(info);
        }};
        String mongoId = mongoTextService.insertOne(textInfo);
        return "存储成功,mongoId:"+mongoId;
    }

    /**
     * 修改
     * @param mongoId
     * @return
     */
    @RequestMapping(value = "/update",method = {RequestMethod.GET,RequestMethod.POST})
    public String update(String mongoId){
        MongoTextInfo textInfo = new MongoTextInfo(){{
            setContent("我也可以用来存储任何长文本信息,减轻关系型数据库的读写压力");
            setMongoId(new ObjectId(mongoId));
        }};
        Long update = mongoTextService.updateOne(textInfo);
        return "修改成功,更新条目:"+update;
    }

    /**
     * 删除
     * @param mongoId
     * @return
     */
    @RequestMapping(value = "/delete",method = {RequestMethod.GET,RequestMethod.POST})
    public String delete(String mongoId){
        Long delete = mongoTextService.deleteOne(mongoId);
        return "删除成功,删除条目:"+delete;
    }

    /**
     * 查询全部,你也可以条件查询,排序查询,具体看MongoBaseService中封装的方法
     * @return
     */
    @RequestMapping(value = "/selectAll")
    public List<MongoTextInfo> selectAll(){
        List<MongoTextInfo> list = mongoTextService.listAll();
        return list;
    }
}
  1. 配置文件(在resources目录下,新建config/mongo.setting)

该文本被Markdown格式化了

  1. 测试

启动wangjc-mongo-web-test,然后分别测试给定的增删改查接口, 注意:因为这里是自定义的封装,需要摒弃spring Boot自带的mongoDB组件,不然会启动报错

@SpringBootApplication(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})
public class WangjcMongoWebTestApplication {

    public static void main(String[] args) {
        SpringApplication.run(WangjcMongoWebTestApplication.class, args);
    }
}

参与贡献

  1. Fork 本仓库
  2. 新建 Feat_xxx 分支
  3. 提交代码
  4. 新建 Pull Request

码云特技

  1. 使用 Readme_XXX.md 来支持不同的语言,例如 Readme_en.md, Readme_zh.md
  2. 码云官方博客 blog.gitee.com
  3. 你可以 https://gitee.com/explore 这个地址来了解码云上的优秀开源项目
  4. GVP 全称是码云最有价值开源项目,是码云综合评定出的优秀开源项目
  5. 码云官方提供的使用手册 https://gitee.com/help
  6. 码云封面人物是一档用来展示码云会员风采的栏目 https://gitee.com/gitee-stars/
1
https://gitee.com/helloxiaochao/wangic-mongo.git
git@gitee.com:helloxiaochao/wangic-mongo.git
helloxiaochao
wangic-mongo
wangic-mongo
master

搜索帮助