一,环境搭建
1)引入依赖
<parent>
<artifactId>spring-boot-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.3.7.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
2)添加配置
#springboot MongoDB配置
spring.data.mongodb.username=test
spring.data.mongodb.password=oudqBFGmGY8pU6WSs
spring.data.mongodb.authentication-database=admin
spring.data.mongodb.database=test
spring.data.mongodb.port=27017
spring.data.mongodb.host=localhost
spring.data.mongodb.auto-index-creation=true
3)添加实体类
@Data
@Document("User")
public class User {
@Id
private String id;
@Indexed
private String name;
private Integer age;
private String email;
private String createDate;
}
二,基于 MongoTemplate
1.常用方法
- 查询User文档的全部数据
mongoTemplate.findAll(User.class)
- 查询User文档id为id的数据
mongoTemplate.findById(<id>, User.class)
- 根据query内的查询条件查询
mongoTemplate.find(query, User.class)
- 修改
//如果数据存在就更新,否则插入新的数据
mongoTemplate.upsert(query, update, User.class)
- 删除
mongoTemplate.remove(query, User.class)
- 新增
mongoTemplate.insert(User)
2.Query对象
- 创建一个
query
对象(用来封装所有条件对象),再创建一个criteria对象(用来构建条件) - 精准条件:
criteria.and(“key”).is(“条件”)
模糊条件:criteria.and(“key”).regex(“条件”)
- 封装条件:
query.addCriteria(criteria)
- 大于(创建新的criteria):
Criteria gt = Criteria.where(“key”).gt(“条件”)
小于(创建新的criteria):Criteria lt = Criteria.where(“key”).lt(“条件”)
Query.addCriteria(new Criteria().andOperator(gt,lt));
- 一个query中只能有一个
andOperator()
。其参数也可以是Criteria数组。 - 排序 :
query.with(new Sort(Sort.Direction.ASC, "age"). and(new Sort(Sort.Direction.DESC, "date")))
3.测试
@Autowired
private MongoTemplate mongoTemplate;
添加:
@Test
public void createUser() {
User user = new User();
user.setAge(20);
user.setName("test");
user.setEmail("4932200@qq.com");
User user1 = mongoTemplate.insert(user);
System.out.println(user1);
}
查询所有:
@Test
public void findUser() {
List<User> userList =
mongoTemplate.findAll(User.class);
System.out.println(userList);
}
根据id查询:
@Test
public void getById() {
User user =
mongoTemplate.findById("5ffbfa2ac290f356edf9b5aa",
User.class);
System.out.println(user);
}
条件查询:
@Test
public void findUserList() {
Query query = new Query(Criteria
.where("name").is("test")
.and("age").is(20));
List<User> userList = mongoTemplate.find(query, User.class);
System.out.println(userList);
}
模糊查询
@Test
public void findUsersLikeName() {
String name = "est";
String regex = String.format("%s%s%s", "^.*", name, ".*$");
// 正则大小写不敏感
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
Query query = new Query(Criteria.where("name").regex(pattern));
List<User> userList = mongoTemplate.find(query, User.class);
System.out.println(userList);
}
分页查询:
@Test
public void findUsersPage() {
String name = "est";
int pageNo = 1;
int pageSize = 10;
Query query = new Query();
String regex = String.format("%s%s%s", "^.*", name, ".*$");
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
query.addCriteria(Criteria.where("name").regex(pattern));
int totalCount = (int) mongoTemplate.count(query, User.class);
List<User> userList = mongoTemplate.find(query.skip((pageNo - 1) * pageSize).limit(pageSize), User.class);
Map<String, Object> pageMap = new HashMap<>();
pageMap.put("list", userList);
pageMap.put("totalCount",totalCount);
System.out.println(pageMap);
}
修改:
@Test
public void updateUser() {
User user = mongoTemplate.findById("5ffbfa2ac290f356edf9b5aa", User.class);
user.setName("test_1");
user.setAge(25);
user.setEmail("493220990@qq.com");
Query query = new Query(Criteria.where("_id").is(user.getId()));
Update update = new Update();
update.set("name", user.getName());
update.set("age", user.getAge());
update.set("email", user.getEmail());
UpdateResult result = mongoTemplate.upsert(query, update, User.class);
long count = result.getModifiedCount();
System.out.println(count);
}
删除:
Query query =
new Query(Criteria.where("_id").is("5ffbfa2ac290f356edf9b5aa"));
DeleteResult result = mongoTemplate.remove(query, User.class);
long count = result.getDeletedCount();
System.out.println(count);
}
聚合查询:
//1 根据医院编号 和 科室编号 查询
Criteria criteria = Criteria.where("hosCode").is(hosCode).and("depCode").is(depCode);
//2 根据工作日workDate期进行分组
Aggregation agg = Aggregation.newAggregation(
Aggregation.match(criteria),//匹配条件
Aggregation.group("workDate")//分组字段
.first("workDate").as("workDate")
//3 统计号源数量
.count().as("docCount")
.sum("reservedNumber").as("reservedNumber")
.sum("availableNumber").as("availableNumber"),
//排序
Aggregation.sort(Sort.Direction.DESC,"workDate"),
//4 实现分页
Aggregation.skip((page-1)*limit),
Aggregation.limit(limit)
);
//调用方法,最终执行
AggregationResults<BookingScheduleRuleVo> aggResults =
mongoTemplate.aggregate(agg, Schedule.class, BookingScheduleRuleVo.class);
List<BookingScheduleRuleVo> bookingScheduleRuleVoList = aggResults.getMappedResults();
//分组查询的总记录数
Aggregation totalAgg = Aggregation.newAggregation(
Aggregation.match(criteria),
Aggregation.group("workDate")
);
AggregationResults<BookingScheduleRuleVo> totalAggResults =
mongoTemplate.aggregate(totalAgg, Schedule.class, BookingScheduleRuleVo.class);
操作主要封装在Aggregation类中:
三,基于MongoRepository
1.实现
Spring Data提供了对mongodb数据访问的支持,我们只需要继承MongoRepository类,按照Spring Data规范就可以了。
注意:
- 不是随便声明的,而需要符合一定的规范
- 查询方法以find | read | get开头
- 涉及条件查询时,条件的属性用条件关键字连接
- 要注意的是:条件属性首字母需要大写
- 支持属性的级联查询,但若当前类有符合条件的属性则优先使用,而不使用级联属性,若需要使用级联属性,则属性之间使用_强制进行连接
2.添加Repository类
@Repository
public interface UserRepository extends
MongoRepository<User, String> {
}
3.添加测试类
@Autowired
private UserRepository userRepository;
添加:
@Test
public void createUser() {
User user = new User();
user.setAge(20);
user.setName("张三");
user.setEmail("3332200@qq.com");
User user1 = userRepository.save(user);
}
查询所有:
@Test
public void findUser() {
List<User> userList = userRepository.findAll();
System.out.println(userList);
}
id查询:
@Test
public void getById() {
User user = userRepository.findById("5ffbfe8197f24a07007bd6ce").get();
System.out.println(user);
}
条件查询:
@Test
public void findUserList() {
User user = new User();
user.setName("张三");
user.setAge(20);
Example<User> userExample = Example.of(user);
List<User> userList = userRepository.findAll(userExample);
System.out.println(userList);
}
模糊查询:
@Test
public void findUsersLikeName() {
//创建匹配器,即如何使用查询条件
ExampleMatcher matcher = ExampleMatcher.matching() //构建对象
.withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING) //改变默认字符串匹配方式:模糊查询
.withIgnoreCase(true); //改变默认大小写忽略方式:忽略大小写
User user = new User();
user.setName("三");
Example<User> userExample = Example.of(user, matcher);
List<User> userList = userRepository.findAll(userExample);
System.out.println(userList);
}
分页查询:
@Test
public void findUsersPage() {
Sort sort = Sort.by(Sort.Direction.DESC, "age");
//0为第一页
Pageable pageable = PageRequest.of(0, 10, sort);
//创建匹配器,即如何使用查询条件
ExampleMatcher matcher = ExampleMatcher.matching() //构建对象
.withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING) //改变默认字符串匹配方式:模糊查询
.withIgnoreCase(true); //改变默认大小写忽略方式:忽略大小写
User user = new User();
user.setName("三");
Example<User> userExample = Example.of(user, matcher);
//创建实例
Example<User> example = Example.of(user, matcher);
Page<User> pages = userRepository.findAll(example, pageable);
System.out.println(pages);
}
修改:
@Test
public void updateUser() {
User user = userRepository.findById("5ffbfe8197f24a07007bd6ce").get();
user.setName("张三_1");
user.setAge(25);
user.setEmail("883220990@qq.com");
User save = userRepository.save(user);
System.out.println(save);
}
删除:
@Test
public void delete() {
userRepository.deleteById("5ffbfe8197f24a07007bd6ce");
}
四.案例实战
1.地图找房
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "tb_house")
public class House {
@Id
private ObjectId id; //主键id
private String title; //房源标题
private Double price; //总价
@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)
private GeoJsonPoint location; //x:经度 y:纬度
@Indexed
private Integer districtCode; //所属行政区
@Indexed
private ObjectId communityId; //所属小区
@Indexed
private Integer businessCircleCode; //商圈
private Long created; //创建时间
}
@Service
public class HouseSearchService {
@Autowired
private MongoTemplate mongoTemplate;
/**
* 地图找房搜索服务
*
* @param maxLongitude 最大经度
* @param minLongitude 最小经度
* @param maxLatitude 最大纬度
* @param minLatitude 最小纬度
* @param zoom 地图缩放比例值
* @return
*/
public List<HouseResultVo> search(Double maxLongitude,
Double minLongitude,
Double maxLatitude,
Double minLatitude,
Double zoom) {
//收集聚合查询条件
List<AggregationOperation> operationList = new ArrayList<>();
//在可视范围内搜索
Box box = new Box(new double[]{maxLongitude, maxLatitude}, new double[]{minLongitude, minLatitude});
MatchOperation matchOperation = Aggregation.match(Criteria.where("location").within(box));
operationList.add(matchOperation);
int type;
GroupOperation groupOperation;
//根据地图的缩放比例进行分组
if (zoom < 13.5) { //2公里以上
//按照行政区分组
groupOperation = Aggregation.group("districtCode");
type = 1;
} else if (zoom < 15.5) { //200米以上
//按照商圈分组
groupOperation = Aggregation.group("businessCircleCode");
type = 2;
} else { //200以下
//按照小区分组
groupOperation = Aggregation.group("communityId");
type = 3;
}
groupOperation = groupOperation.count().as("total")
.avg("price").as("price");
operationList.add(groupOperation);
//生成最终的聚合条件
Aggregation aggregation = Aggregation.newAggregation(operationList);
//执行查询
AggregationResults<HouseResultVo> aggregationResults = this.mongoTemplate.aggregate(aggregation, House.class, HouseResultVo.class);
List<HouseResultVo> houseResultVoList = aggregationResults.getMappedResults();
if (CollUtil.isEmpty(houseResultVoList)) {
return Collections.emptyList();
}
//填充数据
switch (type) {
case 1: {
//查询行政区数据
for (HouseResultVo houseResultVo : houseResultVoList) {
District district = this.queryDistrictByCode(Convert.toInt(houseResultVo.getCode()));
houseResultVo.setName(district.getName());
houseResultVo.setLongitude(district.getLocation().getX());
houseResultVo.setLatitude(district.getLocation().getY());
//价格保留2位小数
houseResultVo.setPrice(NumberUtil.roundStr(houseResultVo.getPrice(), 2));
}
break;
}
case 2: {
//查询商圈数据
for (HouseResultVo houseResultVo : houseResultVoList) {
BusinessCircle businessCircle = this.queryBusinessCircleByCode(Convert.toInt(houseResultVo.getCode()));
houseResultVo.setName(businessCircle.getName());
houseResultVo.setLongitude(businessCircle.getLocation().getX());
houseResultVo.setLatitude(businessCircle.getLocation().getY());
//价格保留2位小数
houseResultVo.setPrice(NumberUtil.roundStr(houseResultVo.getPrice(), 2));
}
break;
}
case 3: {
//查询小区数据
for (HouseResultVo houseResultVo : houseResultVoList) {
Community community = this.queryCommunityById(new ObjectId(houseResultVo.getCode()));
houseResultVo.setName(community.getName());
houseResultVo.setLongitude(community.getLocation().getX());
houseResultVo.setLatitude(community.getLocation().getY());
//价格保留2位小数
houseResultVo.setPrice(NumberUtil.roundStr(houseResultVo.getPrice(), 2));
}
break;
}
default: {
return Collections.emptyList();
}
}
return houseResultVoList;
}
/**
* 根据code查询行政区数据
*
* @param code
* @return
*/
private District queryDistrictByCode(Integer code) {
Query query = Query.query(Criteria.where("code").is(code));
return this.mongoTemplate.findOne(query, District.class);
}
/**
* 根据code查询商圈数据
*
* @param code
* @return
*/
private BusinessCircle queryBusinessCircleByCode(Integer code) {
Query query = Query.query(Criteria.where("code").is(code));
return this.mongoTemplate.findOne(query, BusinessCircle.class);
}
/**
* 根据code查询小区数据
*
* @return
*/
private Community queryCommunityById(ObjectId id) {
return this.mongoTemplate.findById(id, Community.class);
}
}
2.运动健康
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "tb_route")
@JsonIgnoreProperties(ignoreUnknown = true)
public class Route {
@Id
@JsonIgnore
private ObjectId id; //主键id
private String title; //路线标题
@Indexed
private Long userId; //创建路线的用户id
private Boolean isShare; //是否投稿
private Integer size; //轨迹点数量
private Double distance; //此段轨迹的里程数,单位:米
private String time; //运动时间,格式:mm:ss
private Double speed; //平均速度,单位:km/h
@Indexed
private Long startTime; //创建路线时间戳
private Long endTime; //结束路线时间戳
private LocationPoint startPoint; //起点信息
private LocationPoint endPoint; //终点信息
private List<LocationPoint> points; //历史轨迹点列表
//路线的位置,使用起点坐标作为路线坐标
@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)
@JsonIgnore
private GeoJsonPoint location; //x:经度 y:纬度
@JsonIgnore
@Indexed
private Integer status; //状态,1:开始,0:已结束
@Transient //不存储到MongoDB,仅用于前端显示
private Double routeDistance; //路线与我的距离
}
/**
* 创建路线
*
* @return
*/
public String createRoute() {
Route route = new Route();
route.setId(ObjectId.get());
route.setUserId(UserThreadLocal.get());
route.setStatus(1);
route.setIsShare(false); //默认不投稿
route.setStartTime(System.currentTimeMillis());
//将数据保存到MongoDB
this.mongoTemplate.save(route);
String routeId = route.getId().toString();
//百度地图鹰眼服务中创建Entity
Boolean bool = this.baiduService.createEntity(routeId);
if (bool) {
//成功
return routeId;
}
//失败
return null;
}
/**
* 更新路线(结束运动)
*
* @param routeId 路线id
* @param title 路线标题
* @return
*/
public Object updateRoute(String routeId, String title) {
//判断路线是否已经结束,如果已经结束就不能再次结束
Route route = this.queryRouteById(routeId);
if (null == route) {
return ErrorResult.builder()
.errCode("500").errMessage("结束运动失败,路线不存在.").build();
}
if (route.getStatus() == 0) {
return ErrorResult.builder()
.errCode("501").errMessage("结束运动失败,该路线已经结束.").build();
}
//更新路线数据
Update update = Update.update("title", title)
.set("status", 0)
.set("endTime", System.currentTimeMillis());
UpdateResult updateResult = this.mongoTemplate.updateFirst(this.createQuery(routeId), update, Route.class);
if (updateResult.getModifiedCount() == 1) {
//查询百度地图鹰眼服务中的路线轨迹点,更新到路线数据中,异步的操作
this.routeInfoService.updateRouteInfo(routeId, UserThreadLocal.get());
//结束沿着路线骑行的关系
Update runRouteUpdate = Update.update("status", 0)
.set("updated", System.currentTimeMillis());
//条件
Query runRouteQuery = Query.query(Criteria.where("routeId").is(routeId)
.and("userId").is(UserThreadLocal.get())
.and("status").is(1));
UpdateResult runRouteUpdateResult = this.mongoTemplate.updateFirst(runRouteQuery, runRouteUpdate, RunRoute.class);
//更新成功
return runRouteUpdateResult.getModifiedCount() == 1;
}
return ErrorResult.builder()
.errCode("502").errMessage("结束运动失败.").build();
}
/**
* 更新路线数据,查询鹰眼服务中的轨迹数据,更新到Mongodb中
*
* @param routeId
* @param userId
* @return
*/
@Async //异步执行
public CompletableFuture<String> updateRouteInfo(String routeId, Long userId) {
//当前是异步执行,是在一个新的线程中,所以需要将用户id存储到当前的线程中,baiduService中才能获取到用户id
UserThreadLocal.set(userId);
//根据路线id查询路线数据
Route route = this.mongoTemplate.findById(new ObjectId(routeId), Route.class);
Long startTime = route.getStartTime() / 1000; //开始时间,精确到秒
Long endTime = route.getEndTime() / 1000; //结束时间,精确到秒
//查询轨迹数据
Route routeEntity = this.baiduService.queryEntity(routeId, startTime, endTime);
if (null == routeEntity) {
return CompletableFuture.completedFuture("error");
}
//计算运动时间
String time;
try {
time = TimeUtils.formatTime(routeEntity.getEndPoint().getLocTime() - routeEntity.getStartPoint().getLocTime());
} catch (Exception e) {
time = "00:00";
}
//计算平均速度,每个点速度总和 / 轨迹点总数
Double speed;
try {
speed = routeEntity.getPoints().stream().mapToDouble(LocationPoint::getSpeed).sum() / routeEntity.getSize();
speed = NumberUtil.round(speed, 2).doubleValue();
} catch (Exception e) {
speed = 0.00;
}
//更新数据
Update update = Update.update("size", routeEntity.getSize())
.set("distance", NumberUtil.round(routeEntity.getDistance(), 2).doubleValue())
.set("startPoint", routeEntity.getStartPoint())
.set("endPoint", routeEntity.getEndPoint())
.set("endPoint", routeEntity.getEndPoint())
.set("points", routeEntity.getPoints())
.set("location", new GeoJsonPoint(routeEntity.getStartPoint().getLongitude(),
routeEntity.getStartPoint().getLatitude()))
.set("time", time)
.set("speed", speed);
//更新数据到MongoDB
Query query = Query.query(Criteria.where("id").is(routeId));
this.mongoTemplate.updateFirst(query, update, Route.class);
return CompletableFuture.completedFuture("ok");
}
/**
* 查询附近的路线
*
* @param longitude 当前用户所在位置的经度
* @param latitude 当前用户所在位置的纬度
* @param distance 查询的距离,单位:km,默认10km
* @return
*/
public Object queryNearRoute(Double longitude, Double latitude, Double distance) {
//构造查询,附近搜索数据,条件:路线已经结束并且已经分享
NearQuery nearQuery = NearQuery.near(longitude, latitude, Metrics.KILOMETERS)
.maxDistance(distance)
.query(Query.query(Criteria.where("isShare").is(true).and("status").is(0)));
//geo的查询
GeoResults<Route> geoResults = this.mongoTemplate.geoNear(nearQuery, Route.class);
if (CollUtil.isEmpty(geoResults.getContent())) {
//没有数据
return Collections.emptyList();
}
return geoResults.getContent().stream()
.map(result -> {
Route route = result.getContent();
//数据拷贝
NearRouteVo nearRouteVo = BeanUtil.toBeanIgnoreError(route, NearRouteVo.class);
nearRouteVo.setLongitude(route.getLocation().getX());
nearRouteVo.setLatitude(route.getLocation().getY());
nearRouteVo.setRange(NumberUtil.round(result.getDistance().getValue(), 2).doubleValue());//路线与我的距离
return nearRouteVo;
}).collect(Collectors.toList());
}
/**
* 按照时间范围查询路线(包含时间边界)
*
* @param userId
* @param minDate
* @param maxDate
* @return
*/
public List<Route> queryRouteListByDate(Long userId, Long minDate, Long maxDate) {
Query query = Query.query(Criteria.where("endTime").gte(minDate)
.and("status").is(0)
.and("userId").is(userId)
.andOperator(Criteria.where("endTime").lte(maxDate)));
return this.mongoTemplate.find(query, Route.class);
}