一、整合MongoDB
SpringDataMongoDB是 SpringData家族成员之一,MongoDB的持久层框架,底层封装了 mongodb-driver。mongodb-driver 是 MongoDB官方推出的 Java连接 MongoDB的驱动包,相当于JDBC驱动。
SpringBoot整合 MongoDB,引入 SpringDataMongoDB依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
1、配置MongoDB信息
在 application.yml文件中,添加 MongoDB连接信息。
spring:
data:
mongodb:
# 超级管理员是必须指定?后面的固定写法
uri: mongodb://用户名:密码@127.0.0.1:27017/ws_sb_local?authSource=admin&authMechanism=SCRAM-SHA-1
# 普通用户不能指定?后面的固定写法
# uri: mongodb://luna1:密码@127.0.0.1:27017/ws_sb_local
然后启动项目就可以使用 MongoTemplate或者 MongoDB JPA框架(SpringDataMongoDB)。
2、JPA查询规范
使用 JPA查询时,我们要遵循一定的规范。
示例如下:
3、JPA注解
JPA常用注解如下:
@Id
用于字段级别,标记这个字段是一个主键,默认生成的名称是“_id”。
@Document
用于类,以表示这个类需要映射到数据库,您也可以指定映射到数据库的集合名称。
@Field
用于字段,并描述字段的名称,因为它将在MongoDB BSON文档中表示,允许名称与该类的字段名不同。
@CreatedDate
用途: 该注解用于标记一个字段,表示这个字段将自动保存文档首次创建时的日期和时间。
行为: 当一个新的文档被插入到MongoDB集合中时,标记了@CreatedDate的字段会被自动填充为当前的日期和时间。
数据类型: 通常该字段应为java.util.Date、java.time.LocalDateTime或java.time.Instant等日期时间类型。
@LastModifiedDate
用途: 这个注解用于标记一个字段,以记录文档最后一次被修改的日期和时间。
行为: 每当文档被更新时,这个字段会自动更新为当前的日期和时间。
数据类型: 类似于@CreatedDate,它也支持java.util.Date、java.time.LocalDateTime或java.time.Instant等类型。
@Indexed
用于字段,表示该字段需要如何创建索引。
@CompoundIndex
用于类,以声明复合索引。
@GeoSpatialIndexed
用于字段,进行地理位置索引 。
@DBRef
用于字段,以表示它将使用com.mongodb.DBRef进行存储。
@TextIndexed
用于字段,标记该字段要包含在文本索引中。
@Language
用于字段,以设置文本索引的语言覆盖属性。
@Transient
默认情况下,所有私有字段都映射到文档,此注解将会去除此字段的映射
@PersistenceConstructor
标记一个给定的构造函数,即使是一个protected修饰的,在从数据库实例化对象时使用。构造函数参数通过名称映射到检索的DBObject中的键值。
@Value
这个注解是 Spring框架的一部分。在映射框架内,它可以应用于构造函数参数。这允许您使用Spring表达式语言语句来转换在数据库中检索的键值,然后再用它来构造一个域对象。为了引用给定文档的属性,必须使用以下表达式:@Value(“#root.myProperty”),root要指向给定文档的根。
@Version
用于字段锁定,保存操作时检查修改。初始值是0,每次更新时自动触发。
二、MongoDB JPA使用
这里直接上代码。
1、DO类
/**
* 设备Gps信息
*/
@Data
@Document("device_gps")
public class DeviceGpsDO implements Serializable {
private static final long serialVersionUID = -9179758476973350170L;
/**
* 主键
*/
@Id
private String id;
/**
* 创建时间
*/
@CreatedDate
@Field("create_time")
private LocalDateTime createTime;
/**
* 修改时间
*/
@LastModifiedDate
@Field("update_time")
private LocalDateTime updateTime;
/**
* 设备名称
*/
@Field("device_name")
@Indexed
private String deviceName;
/**
* 设备编号
*/
@Field("device_no")
private String deviceNo;
/**
* 设备识别VIN码
*/
@Field("vin_no")
private String vinNo;
/**
* 预警状态
*/
@Field("warn_status")
private Integer warnStatus;
/**
* 速度(千米/小时)
*/
@Field("speed")
private Float speed;
/**
* 海拔高度
*/
@Field("altitude")
private Double altitude;
/**
* 卫星定位时间
*/
@Field("satellite_time")
private Date satelliteTime;
/**
* 地理位置,用于LBS搜索
*/
@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)
private GeoJsonPoint location;
/**
* 经度
*/
@Field("longitude")
private Double longitude;
/**
* 纬度
*/
@Field("latitude")
private Double latitude;
}
2、Dao类
/**
* 设备Gps信息
* 继承 MongoRepository,指定实体和主键的类型作为泛型
*/
public interface DeviceGpsDao extends MongoRepository<DeviceGpsDO, String> {
/**
* JPA语法查询根据速度大于等于和预警状态查询
*
* @param speed 速度
* @param warnStatus 预警状态
* @return
*/
List<DeviceGpsDO> findBySpeedGreaterThanEqualAndWarnStatusEquals(Float speed, Integer warnStatus);
/**
* @Query注解查询根据速度大于等于和预警状态查询
*
* @param speed 速度
* @param warnStatus 预警状态
* @return
*/
@Query("{ speed: {$gte : ?0}, warn_status: ?1 }")
List<DeviceGpsDO> getBySpeedAndWarnStatus(Float speed, Integer warnStatus);
}
3、Service类
(1)接口
public interface DeviceGpsService {
/**
* 分页查询
*
* @param pageQueryRequest
* @return
*/
BasePageResult pageQuery(DeviceGpsPageQueryRequest pageQueryRequest);
/**
* 新增
*
* @param deviceGpsDO
*/
BaseOperateResult insert(DeviceGpsDO deviceGpsDO);
/**
* 新增或者修改
*
* @param deviceGpsDO
*/
BaseOperateResult insertOrUpdate(DeviceGpsDO deviceGpsDO);
/**
* 根据 id删除
*
* @param id
*/
BaseOperateResult deleteById(String id);
/**
* 根据 id查询
*
* @param id
* @return
*/
DeviceGpsDO findById(String id);
/**
* 查询所有
*
* @return
*/
List<DeviceGpsDO> findAll();
/**
* JPA语法查询根据速度大于等于和预警状态查询
*
* @param speed 速度
* @param warnStatus 预警状态
* @return
*/
List<DeviceGpsDO> findBySpeedGreaterThanEqualAndWarnStatusEquals(Float speed, Integer warnStatus);
/**
* @Query注解查询根据速度大于等于和预警状态查询
*
* @param speed 速度
* @param warnStatus 预警状态
* @return
*/
List<DeviceGpsDO> getBySpeedAndWarnStatus(Float speed, Integer warnStatus);
}
(2)实现类
@Slf4j
@Service
@RequiredArgsConstructor
public class DeviceGpsServiceImpl implements DeviceGpsService {
private final DeviceGpsDao deviceGpsDao;
@Override
public BasePageResult pageQuery(DeviceGpsPageQueryRequest pageQueryRequest) {
BasePageResult pageResult = new BasePageResult();
String deviceName = pageQueryRequest.getDeviceName();
Integer warnStatus = pageQueryRequest.getWarnStatus();
// 创建查询条件的实例
DeviceGpsDO queryDO = new DeviceGpsDO();
if (StringUtils.isNoneBlank(deviceName)) {
queryDO.setDeviceName(deviceName);
}
if (warnStatus != null) {
queryDO.setWarnStatus(warnStatus);
}
// 设置ExampleMatcher来定义匹配规则
ExampleMatcher matcher = ExampleMatcher.matching()
.withIgnoreCase() // 忽略大小写
.withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING); // 包含
// 使用条件对象和匹配器创建Example实例
Example<DeviceGpsDO> example = Example.of(queryDO, matcher);
// 构造分页对象, 从 0 开始
int currentPage = pageQueryRequest.getCurrentPage().intValue();
int pageSize = pageQueryRequest.getPageSize().intValue();
PageRequest pageRequest = PageRequest.of(currentPage - 1, pageSize);
// 分页查询
/**
* Example 这种方式更适合于属性匹配(如字符串的模糊匹配)。对于比较运算(如大于、小于等),使用Spring Data MongoDB提供的Criteria和Query对象来构建更复杂的查询条件。
*/
Page<DeviceGpsDO> doPage = deviceGpsDao.findAll(example, pageRequest);
log.info("doPage.getNumber() = {}", doPage.getNumber());
log.info("doPage.getNumberOfElements() = {}", doPage.getNumberOfElements());
log.info("doPage.getSize() = {}", doPage.getSize());
log.info("doPage.getTotalElements() = {}", doPage.getTotalElements());
log.info("doPage.getTotalPages() = {}", doPage.getTotalPages());
pageResult.setPaginator(new Paginator(doPage.getNumber() + 1, doPage.getSize(), doPage.getTotalElements(), doPage.getTotalPages()));
pageResult.setPageList(doPage.getContent());
pageResult.setSuccess(true);
return pageResult;
}
@Override
public BaseOperateResult insert(DeviceGpsDO deviceGpsDO) {
BaseOperateResult operateResult = new BaseOperateResult();
// 新增
Double longitude = deviceGpsDO.getLongitude();
Double latitude = deviceGpsDO.getLatitude();
if (longitude != null || latitude != null) {
// 经纬度 GeoJsonPoint
deviceGpsDO.setLocation(new GeoJsonPoint(longitude, latitude));
}
deviceGpsDao.insert(deviceGpsDO);
operateResult.setSuccess(Boolean.TRUE);
return operateResult;
}
@Override
public BaseOperateResult insertOrUpdate(DeviceGpsDO deviceGpsDO) {
BaseOperateResult operateResult = new BaseOperateResult();
// 新增或者修改
Double longitude = deviceGpsDO.getLongitude();
Double latitude = deviceGpsDO.getLatitude();
if (longitude != null || latitude != null) {
// 经纬度 GeoJsonPoint
deviceGpsDO.setLocation(new GeoJsonPoint(longitude, latitude));
}
// MongoDB会清除字段为 null的字段。
deviceGpsDao.save(deviceGpsDO);
operateResult.setSuccess(Boolean.TRUE);
return operateResult;
}
@Override
public BaseOperateResult deleteById(String id) {
BaseOperateResult operateResult = new BaseOperateResult();
deviceGpsDao.deleteById(id);
operateResult.setSuccess(Boolean.TRUE);
return operateResult;
}
@Override
public DeviceGpsDO findById(String id) {
return deviceGpsDao.findById(id).get();
}
@Override
public List<DeviceGpsDO> findAll() {
return deviceGpsDao.findAll();
}
@Override
public List<DeviceGpsDO> findBySpeedGreaterThanEqualAndWarnStatusEquals(Float speed, Integer warnStatus) {
List<DeviceGpsDO> list = deviceGpsDao.findBySpeedGreaterThanEqualAndWarnStatusEquals(speed, warnStatus);
return list;
}
@Override
public List<DeviceGpsDO> getBySpeedAndWarnStatus(Float speed, Integer warnStatus) {
List<DeviceGpsDO> list = deviceGpsDao.getBySpeedAndWarnStatus(speed, warnStatus);
return list;
}
}
然后,单元测试调用ok。
4、MongoDB的UTC时差问题
SpringDataMongoDB框架会自动处理 MongoDB的UTC时差问题。我们正常在服务端代码中保存和查询操作即可。但是,对于 Controller接口返回是不一样的,与使用 LocalDateTime和 Date类型有关。
MongoDB数据库记录:
Controller接口返回DO数据:
结论:
- 如果使用 Date类型,接口返回存在 8小时时差,请使用
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")注解
并且必须指定时区解决。 - 如果使用 LocalDateTime,接口返回不存在时差问题,序列化时也可以使用 @JsonFormat注解。
推荐使用 LocalDateTime
。
5、“_class” 字段问题
SpringBoot整合mongodb时,发现添加文档会自动添加一个_class的字段。
有或者没有_class列,对于我们使用 MongoDB进行任何操作及序列化实体类都没有任何影响。
所以,我们可以使用下面方法,禁用 “_class” 字段。
新建 MongoDB配置类。
//@EnableMongoAuditing // 开启审计功能
@Configuration
public class MongoDBConfig implements InitializingBean {
@Resource
private MappingMongoConverter mappingMongoConverter;
@Override
public void afterPropertiesSet() {
// 禁用 "_class" 字段
mappingMongoConverter.setTypeMapper(new DefaultMongoTypeMapper(null));
}
}
6、开启审计功能
使用 @EnableMongoAuditing注解开启审计功能。
我们在启动类或者 上面配置类上添加它之后,@CreatedDate和 @LastModifiedDate注解就会生效了。
注意:
@CreatedDate:id为空时生效。
三、@Query注解使用
在 Dao类中,我们可以通过使用 @Query 注解手写查询语句。
1、简单参数传参
我们需要使用 ?数字占位符
表达式来取出参数中指定位置的值,占位符从 0
开始。
@Query("{ speed: {$gte : ?0}, warn_status: ?1 }")
List<DeviceGpsDO> getBySpeedAndWarnStatus(Float speed, Integer warnStatus);
2、对象参数传参
我们需要使用 SpEL 表达式,格式:?#{[]}
括号中使用 [下标] 来取出指定位置的参数,比如:?#{[0]} 则取出第一个参数。之后直接取出参数中的指定属性即可,比如如 ?#{[1].age} 就是取出第二个参数的age属性。
@Query("{ speed: {$gte : ?#{[0].speed}}, warn_status: ?#{[0].warnStatus} }")
List<DeviceGpsDO> getBySpeedAndWarnStatus2(DeviceGpsDO deviceGpsDO);
3、投影查询
如果我们只想获取指定的字段,可以使用 @Query注解的 fields 来进行投影查询。
@Query(value= "{ speed: {$gte : ?#{[0].speed}}, warn_status: ?#{[0].warnStatus} }",
fields = "{ speed:1, warn_status:1 }")
List<DeviceGpsDO> getBySpeedAndWarnStatus3(DeviceGpsDO deviceGpsDO);
测试:
@Override
public List<DeviceGpsDO> getBySpeedAndWarnStatus(Float speed, Integer warnStatus) {
List<DeviceGpsDO> list = deviceGpsDao.getBySpeedAndWarnStatus(speed, warnStatus);
DeviceGpsDO deviceGpsDO = new DeviceGpsDO();
deviceGpsDO.setSpeed(0.0F);
deviceGpsDO.setWarnStatus(0);
List<DeviceGpsDO> list2 = deviceGpsDao.getBySpeedAndWarnStatus2(deviceGpsDO);
List<DeviceGpsDO> list3 = deviceGpsDao.getBySpeedAndWarnStatus3(deviceGpsDO);
log.info("list = {} ", list);
log.info("list2 = {} ", list2);
log.info("list3 = {} ", list3);
return list;
}
总结:
- 对于保存、修改、删除等操作,JPA语法使用非常友好,推荐使用
- 对于动态条件查询,JPA语法与@Query注解处理不够友好,不推荐使用。
- 对于动态条件查询,推荐使用 Criteria对象构建动态参数,然后使用 mongoTemplate查询。
- 项目中,通常会 JPA语法与 mongoTemplate两者结合使用。
– 求知若饥,虚心若愚。