本篇简述springboot集成mongodb (单机版,不作分片)
mongo可以直接存取结构较复杂的json数据类型的文档,支持聚合统计等复杂计算,并具有简单的搜索功能,还提供了LBS功能。
准备工作:
搭建springboot脚手架并成功运行,可参考历史分享springboot+mybatis
启动mongodb服务(搭建配置mongodb后续会在运维章节另行讲述)
登录连接mongo,创建库test_lbs及用户admin并授权验证admin123
org.springframework.boot
spring-boot-starter-data-mongodb
org.mongodb
mongo-java-driver
org.springframework.data
spring-data-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文档,类似关系型数据库中的表,不指定名称时默认为 hotPoint
public 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
* @return
*/
T save(T t);
/**
* 根据id删除对象
* @param t
* @param
*/
void deleteById(T t);
/**
* 删除所有
* @param query
* @param entityClass
* @param
*/
void removeAll(Query query, Class entityClass);
/**
* 通过条件查询更新数据
* @param query
* @param update
* @param entityClass
* @param
* @return
*/
UpdateResult update(Query query, Update update, Class entityClass);
/**
* 更新第一条满足条件的文档
* @param query
* @param update
* @param entityClass
* @param
*/
UpdateResult updateFirst(Query query, Update update, Class entityClass);
/**
* 新增或插入
* @param query
* @param update
* @param entityClass
* @param
* @return
*/
UpdateResult upsert(Query query, Update update, Class entityClass);
/**
* 构建update
* @param t
* @param
* @return
*/
Update buildBaseUpdate(T t);
/**
* 根据id进行更新
* @param id
* @param t
* @param entityClass
* @param
* @return
*/
UpdateResult updateById(String id, T t, Class entityClass);
/**
* 根据id进行更新
* @param id
* @param t
* @param entityClass
* @param
* @return
*/
UpdateResult updateById(Integer id, T t, Class entityClass);
UpdateResult updateById(Integer id, Update update, Class entityClass);
/**
* 通过条件查询实体(集合)
* @param query
* @param entityClass
* @param
* @return
*/
List find(Query query, Class entityClass);
/**
* 通过主键查询实体
* @param id
* @param entityClass
* @param
* @return
*/
T findById(Integer id, Class entityClass);
T findById(String id, Class entityClass);
List findByIdIn(Iterable ids, Class entityClass);
/**
* 通过一定的条件查询一个实体
* @param query
* @param entityClass
* @param
* @return
*/
T findOne(Query query, Class entityClass);
/**
* 通过条件查询实体(集合)
* @param query
* @param excludeFields 排除返回字段
* @return
*/
List find(Query query, Class entityClass, String... excludeFields);
/**
* 通过条件查询实体
* @param query
* @param excludeFields 排除返回字段
* @return
*/
T findOne(Query query, Class entityClass, String... excludeFields);
/**
* 总记录数
* @param query
* @param entityClass
* @return
*/
long count(Query query, Class entityClass);
/**
* 获取分页数据
* @param currentPage
* @param pageSize
* @param query
* @return
*/
PagerResponse pageInfo(int currentPage, int pageSize, Query query, Class entityClass);
/**
* 聚合查询
* @param aggregation
* @param inputType
* @param outputType
* @param
* @return
*/
AggregationResults aggregate(Aggregation aggregation, Class> inputType, Class outputType);
/**
* 批量插入
* @param list
* @param
*/
void insertAll(List list);
/**
* 批量更新
* @param query
* @param update
* @param entityClass
* @param
* @return
*/
UpdateResult updateMulti(Query query, Update update, Class entityClass);
/**
* 去重查询
* @param key
* @param value
* @param query
* @param entityClass
* @param
* @return
*/
List queryForDistinct(String key, Object value, Query query, Class 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 save(T t) { mongoTemplate.save(t);
return t;
}
@Override
public
void deleteById(T t) { mongoTemplate.remove(t);
}
@Override
public
void removeAll(Query query, Class entityClass) { mongoTemplate.findAllAndRemove(query, entityClass);
}
@Override
public
UpdateResult update(Query query, Update update, Class entityClass) { return mongoTemplate.updateMulti(query, update, entityClass);
}
@Override
public
UpdateResult updateFirst(Query query, Update update, Class entityClass) { return mongoTemplate.updateFirst(query, update, entityClass);
}
@Override
public
UpdateResult upsert(Query query, Update update, Class entityClass) { return mongoTemplate.upsert(query, update, entityClass);
}
@Override
public
UpdateResult updateById(String id, T t, Class entityClass) { Query query = new Query();
query.addCriteria(Criteria.where("_id").is(id));
Update update = this.buildBaseUpdate(t);
return update(query, update, entityClass);
}
@Override
public
UpdateResult updateById(Integer id, T t, Class entityClass) { return this.updateById(String.valueOf(id), t, entityClass);
}
@Override
public
UpdateResult updateById(Integer id, Update update, Class entityClass) { Query query = new Query(Criteria.where("_id").is(String.valueOf(id)));
return this.updateFirst(query, update, entityClass);
}
/**
* 根据vo构建构建更新条件Update
*
* @param t
* @param
* @return
*/
@Override
public
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
List find(Query query, Class entityClass) { return mongoTemplate.find(query, entityClass);
}
@Override
public
T findById(Integer id, Class entityClass) { return this.findById(String.valueOf(id), entityClass);
}
@Override
public
T findById(String id, Class entityClass) { return mongoTemplate.findById(id, entityClass);
}
@Override
public
List findByIdIn(Iterable ids, Class entityClass) { if(ids != null){
List
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 findOne(Query query, Class entityClass) { return mongoTemplate.findOne(query, entityClass);
}
@Override
public
List find(Query query, Class 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 findOne(Query query, Class entityClass, String... excludeFields) { setExcluedFields(query, excludeFields);
return findOne(query, entityClass);
}
@Override
public
long count(Query query, Class entityClass) { return mongoTemplate.count(query, entityClass);
}
@Override
public
PagerResponse pageInfo(int currentPage, int pageSize, Query query, Class 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
list = this.find(query.skip(skip).limit(pageSize), entityClass); return new PagerResponse<>(currentPage, pageSize, (int) totalPage, count, list);
}
@Override
public
AggregationResults aggregate(Aggregation aggregation, Class> inputType, Class outputType) { return mongoTemplate.aggregate(aggregation, inputType, outputType);
}
@Override
public
void insertAll(List tList) { mongoTemplate.insertAll(tList);
}
@Override
public
UpdateResult updateMulti(Query query, Update update, Class entityClass) { return mongoTemplate.updateMulti(query, update, entityClass);
}
@Override
public
List queryForDistinct(String key, Object value, Query query, Class entityClass) { List
result = new ArrayList<>(); Bson bson = new BasicDBObject(key, value);
DistinctIterable
distinctIterable = mongoTemplate.getCollection( mongoTemplate.getCollectionName(entityClass)).distinct(key, bson, entityClass);
T first = distinctIterable.first();
result.add(first);
distinctIterable.forEach((Block
) result::add);
return result;
}
}
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
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
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
result = mongoService.aggregate(aggregation, HotPoint.class, UserPoint.class); List
hotPoints = result.getMappedResults();