本篇简述springboot集成mongodb (单机版,不作分片)

mongo可以直接存取结构较复杂的json数据类型的文档,支持聚合统计等复杂计算,并具有简单的搜索功能,还提供了LBS功能。

准备工作:

  1. 搭建springboot脚手架并成功运行,可参考历史分享springboot+mybatis

  2. 启动mongodb服务(搭建配置mongodb后续会在运维章节另行讲述)

  3. 登录连接mongo,创建库test_lbs及用户admin并授权验证admin123

1. maven添加mongodb依赖

<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-data-mongodb</artifactId></dependency><dependency>    <groupId>org.mongodb</groupId>    <artifactId>mongo-java-driver</artifactId></dependency><dependency>    <groupId>org.springframework.data</groupId>    <artifactId>spring-data-mongodb</artifactId></dependency>

2. mongodb 配置封装

2.1 yml

spring:  data:    mongodb:      database: test_lbs      host: 192.168.2.9      port: 27017      username: admin      password: admin123

2.2 mongo document

import lombok.Data;import org.springframework.data.mongodb.core.mapping.Document;import org.springframework.data.annotation.Id;import org.springframework.data.annotation.Transient;@Data@Document  // 自动创建mongo文档,类似关系型数据库中的表,不指定名称时默认为 hotPointpublic class HotPoint implements Serializable {    @Id    private String id;        @Indexed    private Integer userId;     // 自动创建2DSPHERE索引,可以用于球面地理位置计算    @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)    private Double[] pos;        private String pointName;        // 不需要被mongobd存储的字段可以加@Transient标识    @Transient    private double scope; // 距离(单位:km)}

2.3 mongoService 通用操作类封装

service接口类:

import com.mongodb.client.result.UpdateResult;import org.springframework.data.mongodb.core.aggregation.Aggregation;import org.springframework.data.mongodb.core.aggregation.AggregationResults;import org.springframework.data.mongodb.core.query.Query;import org.springframework.data.mongodb.core.query.Update;import java.util.List;public interface MongoService {    /**     * 保存一个对象到mongodb     * @param t     * @param <T>     * @return     */    <T> T save(T t);    /**     * 根据id删除对象     * @param t     * @param <T>     */    <T> void deleteById(T t);    /**     * 删除所有     * @param query     * @param entityClass     * @param <T>     */    <T> void removeAll(Query query, Class<T> entityClass);    /**     * 通过条件查询更新数据     * @param query     * @param update     * @param entityClass     * @param <T>     * @return     */    <T> UpdateResult update(Query query, Update update, Class<T> entityClass);    /**     * 更新第一条满足条件的文档     * @param query     * @param update     * @param entityClass     * @param <T>     */    <T> UpdateResult updateFirst(Query query, Update update, Class<T> entityClass);    /**     * 新增或插入     * @param query     * @param update     * @param entityClass     * @param <T>     * @return     */    <T> UpdateResult upsert(Query query, Update update, Class<T> entityClass);    /**     * 构建update     * @param t     * @param <T>     * @return     */    <T> Update buildBaseUpdate(T t);    /**     * 根据id进行更新     * @param id     * @param t     * @param entityClass     * @param <T>     * @return     */    <T> UpdateResult updateById(String id, T t, Class<T> entityClass);    /**     * 根据id进行更新     * @param id     * @param t     * @param entityClass     * @param <T>     * @return     */    <T> UpdateResult updateById(Integer id, T t, Class<T> entityClass);    <T> UpdateResult updateById(Integer id, Update update, Class<T> entityClass);    /**     * 通过条件查询实体(集合)     * @param query     * @param entityClass     * @param <T>     * @return     */    <T> List<T> find(Query query, Class<T> entityClass);    /**     * 通过主键查询实体     * @param id     * @param entityClass     * @param <T>     * @return     */    <T> T findById(Integer id, Class<T> entityClass);    <T> T findById(String id, Class<T> entityClass);    <T> List<T> findByIdIn(Iterable<Integer> ids, Class<T> entityClass);    /**     * 通过一定的条件查询一个实体     * @param query     * @param entityClass     * @param <T>     * @return     */    <T> T findOne(Query query, Class<T> entityClass);    /**     * 通过条件查询实体(集合)     * @param query     * @param excludeFields 排除返回字段     * @return     */    <T> List<T> find(Query query, Class<T> entityClass, String... excludeFields);    /**     * 通过条件查询实体     * @param query     * @param excludeFields 排除返回字段     * @return     */    <T> T findOne(Query query, Class<T> entityClass, String... excludeFields);    /**     * 总记录数     * @param query     * @param entityClass     * @return     */    <T> long count(Query query, Class<T> entityClass);    /**     * 获取分页数据     * @param currentPage     * @param pageSize     * @param query     * @return     */    <T> PagerResponse<T> pageInfo(int currentPage, int pageSize, Query query, Class<T> entityClass);    /**     * 聚合查询     * @param aggregation     * @param inputType     * @param outputType     * @param <T>     * @return     */    <T> AggregationResults<T> aggregate(Aggregation aggregation, Class<?> inputType, Class<T> outputType);    /**     * 批量插入     * @param list     * @param <T>     */    <T> void insertAll(List<T> list);    /**     * 批量更新     * @param query     * @param update     * @param entityClass     * @param <T>     * @return     */    <T> UpdateResult updateMulti(Query query, Update update, Class<T> entityClass);    /**     * 去重查询     * @param key     * @param value     * @param query     * @param entityClass     * @param <T>     * @return     */      <T> List<T> queryForDistinct(String key, Object value, Query query, Class<T> entityClass); }

service实现类:

import com.mongodb.BasicDBObject;import com.mongodb.Block;import com.mongodb.client.DistinctIterable;import com.mongodb.client.result.UpdateResult;import lombok.extern.slf4j.Slf4j;import org.apache.commons.collections.CollectionUtils;import org.bson.conversions.Bson;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.mongodb.core.MongoTemplate;import org.springframework.data.mongodb.core.aggregation.Aggregation;import org.springframework.data.mongodb.core.aggregation.AggregationResults;import org.springframework.data.mongodb.core.query.Criteria;import org.springframework.data.mongodb.core.query.Query;import org.springframework.data.mongodb.core.query.Update;import org.springframework.stereotype.Service;import java.lang.reflect.Field;import java.util.ArrayList;import java.util.Collections;import java.util.List;@Slf4j@Service // 需确保该service组件能被springboot扫描到public class MongoServiceImpl implements MongoService {    @Autowired    private MongoTemplate mongoTemplate;    @Override    public <T> T save(T t) {        mongoTemplate.save(t);        return t;    }    @Override    public <T> void deleteById(T t) {        mongoTemplate.remove(t);    }    @Override    public <T> void removeAll(Query query, Class<T> entityClass) {        mongoTemplate.findAllAndRemove(query, entityClass);    }    @Override    public <T> UpdateResult update(Query query, Update update, Class<T> entityClass) {        return mongoTemplate.updateMulti(query, update, entityClass);    }    @Override    public <T> UpdateResult updateFirst(Query query, Update update, Class<T> entityClass) {        return mongoTemplate.updateFirst(query, update, entityClass);    }    @Override    public <T> UpdateResult upsert(Query query, Update update, Class<T> entityClass) {        return mongoTemplate.upsert(query, update, entityClass);    }    @Override    public <T> UpdateResult updateById(String id, T t, Class<T> entityClass) {        Query query = new Query();        query.addCriteria(Criteria.where("_id").is(id));        Update update = this.buildBaseUpdate(t);        return update(query, update, entityClass);    }    @Override    public <T> UpdateResult updateById(Integer id, T t, Class<T> entityClass) {        return this.updateById(String.valueOf(id), t, entityClass);    }    @Override    public <T> UpdateResult updateById(Integer id, Update update, Class<T> entityClass) {        Query query = new Query(Criteria.where("_id").is(String.valueOf(id)));        return this.updateFirst(query, update, entityClass);    }    /**     * 根据vo构建构建更新条件Update     *     * @param t     * @param <T>     * @return     */    @Override    public <T> Update buildBaseUpdate(T t) {        Update update = new Update();        Field[] fields = t.getClass().getDeclaredFields();        for (Field field : fields) {            field.setAccessible(true);            try {                Object value = field.get(t);                if (value != null) {                    update.set(field.getName(), value);                }            } catch (Exception e) {                log.error("异常信息:{}", e.getMessage());            }        }        return update;    }    @Override    public <T> List<T> find(Query query, Class<T> entityClass) {        return mongoTemplate.find(query, entityClass);    }    @Override    public <T> T findById(Integer id, Class<T> entityClass) {        return this.findById(String.valueOf(id), entityClass);    }    @Override    public <T> T findById(String id, Class<T> entityClass) {        return mongoTemplate.findById(id, entityClass);    }    @Override    public <T> List<T> findByIdIn(Iterable<Integer> ids, Class<T> entityClass) {        if(ids != null){            List<String> strIds = new ArrayList<>();            ids.forEach(id -> strIds.add(String.valueOf(id)));            if(CollectionUtils.isNotEmpty(strIds)){                return mongoTemplate.find(new Query(Criteria.where("_id").in(strIds)), entityClass);            }        }        return Collections.emptyList();    }    @Override    public <T> T findOne(Query query, Class<T> entityClass) {        return mongoTemplate.findOne(query, entityClass);    }    @Override    public <T> List<T> find(Query query, Class<T> entityClass, String... excludeFields) {        setExcluedFields(query, excludeFields);        return find(query, entityClass);    }    /**     * 排除MongoDB查询返回的一些字段     *     * @param query     * @param excludeFields     */    private void setExcluedFields(Query query, String... excludeFields) {        if (null != query && null != excludeFields) {            for (String field : excludeFields) {                query.fields().exclude(field);            }        }    }    @Override    public <T> T findOne(Query query, Class<T> entityClass, String... excludeFields) {        setExcluedFields(query, excludeFields);        return findOne(query, entityClass);    }    @Override    public <T> long count(Query query, Class<T> entityClass) {        return mongoTemplate.count(query, entityClass);    }    @Override    public <T> PagerResponse<T> pageInfo(int currentPage, int pageSize, Query query, Class<T> entityClass) {        if (currentPage == 0) {            currentPage = 1;        }        if (pageSize == 0) {            pageSize = 10;        }        long count = this.count(query, entityClass);        long totalPage = count % pageSize > 0 ? count / pageSize + 1 : count / pageSize;        int skip = (currentPage - 1) * pageSize;        List<T> list = this.find(query.skip(skip).limit(pageSize), entityClass);        return new PagerResponse<>(currentPage, pageSize, (int) totalPage, count, list);    }    @Override    public <T> AggregationResults<T> aggregate(Aggregation aggregation, Class<?> inputType, Class<T> outputType) {        return mongoTemplate.aggregate(aggregation, inputType, outputType);    }    @Override    public <T> void insertAll(List<T> tList) {        mongoTemplate.insertAll(tList);    }    @Override    public <T> UpdateResult updateMulti(Query query, Update update, Class<T> entityClass) {        return mongoTemplate.updateMulti(query, update, entityClass);    }    @Override    public <T> List<T> queryForDistinct(String key, Object value, Query query, Class<T> entityClass) {        List<T> result = new ArrayList<>();        Bson bson = new BasicDBObject(key, value);        DistinctIterable<T> distinctIterable = mongoTemplate.getCollection(                mongoTemplate.getCollectionName(entityClass)).distinct(key, bson, entityClass);        T first = distinctIterable.first();        result.add(first);        distinctIterable.forEach((Block<T>) result::add);        return result;    }}

3. 使用示例

3.1 在具体需要的类中注入mongoService直接调用封装的API

@Autowired
private MongoService mongoService;

更新文档:

HotPoint point = mongoService.findById(pointId, HotPoint.class);if(point != null){        Update update = Update.update("pointName", pointName);    mongoService.updateById(pointId, update, HotPoint.class);}

LBS查询:

 /** * 球面弧长1000米对应的弧度:弧长/地球半径 * BigDecimal.valueOf(1000).divide(BigDecimal.valueOf(6378245.0), 5, BigDecimal.ROUND_HALF_UP) */public static final double ONE_KILOMETER_RADIAN = 0.00016;// 2公里距离public static final double DISTANCE_2KM = 2.0; // 构建查询条件 (注意mongo在存储id字段时,会自动加上前缀下划线)Query query = new Query(Criteria.where("_id").gte(10));// LBS定位点Point point = new Point(longitude, latitude);// 弧长转换为弧度double maxDistance = DISTANCE_2KM * ONE_KILOMETER_RADIAN;// 基于附近2公里查询query.addCriteria(Criteria.where("pos").nearSphere(point).maxDistance(maxDistance));// 排序 (注意追加排序后,会打乱LBS默认的由近及远排序)query.with(Sort.by(Sort.Direction.DESC, "id"))List<HotPoint> points = mongoService.find(query, HotPoint.class)

聚合查询:

Point point = new Point(longitude, latitude);// 弧长转换为弧度double maxDistance = DISTANCE_2KM * ONE_KILOMETER_RADIAN;NearQuery nearQuery = NearQuery.near(point).maxDistance(maxDistance);// 从id>=10开始查询Criteria criteria = Criteria.where("_id").gte(10);                    TypedAggregation<HotPoint> aggregation =    Aggregation.newAggregation(      HotPoint.class,      Aggregation.geoNear(nearQuery.spherical(true).query(new Query(criteria)), "distance"),      Aggregation.group("userId").first("userId").as("userId"),      Aggregation.skip((pageNum - 1) * pageSize),      Aggregation.limit(pageSize)   );   // 聚合查询附近热点位置(分组去重) // HotPoint为查询入参类型,UserPoint为查询结果返回映射封装类型AggregationResults<UserPoint> result = mongoService.aggregate(aggregation, HotPoint.class, UserPoint.class);List<UserPoint> hotPoints = result.getMappedResults(); 
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐