之前写过一篇关于MongoDB的总结
其中就已经包含了SpringBoot操作MongoDB之MongoRepository了
但是因为觉得单单与Springboot内容就比较多,而且开发时还会进行多次补充总结,
觉得独立出来一篇是非常必要的,所以就有这篇的出现。
NoSQL_MongoDB
Spring data Mongodb Query以分页
20200825-完成初稿
关于springboot操作mongodb,使用spring-data其实有两种方式:MongoRepository 和 MongoTemplate
但是日常使用中,与MongoRepository相比,MongoTemplate需要进行相关配置,而MongoRepository能够满足一般的需求开发,而且拿来即用即可。非常方便
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
#### 链接数据库
# 备注:user=root,password=123456,host=192.168.1.100,port=27017,db=admin
# url配置方式,如果有设置密码的话,这种方式非常简洁
# 但找不到资料,当没有设置密码时,如何处理
spring.data.mongodb.uri=mongodb://root:123456@192.168.1.100:27017/admin
# 逐一配置,适合未设置密码的MongoDB
spring.data.mongodb.database=test
spring.data.mongodb.host=host
spring.data.mongodb.port=27017
# mongodb开启Debug模式
# MongoRepository
logging.level.org.springframework.data.mongodb.repository=DEBUG
# MongoTemplate
logging.level.org .springframework.data.mongodb.core= DEBUG
对于一门框架的使用,其使用顺序是非常关键的,因为有了大概的使用顺序,才能够处理好许多问题
这里的设计好指的是对一个实体类有大致的想法,而且能够满足需求,特别是一些细节的需求。
例如需要根据时间排序,那么这个collections就需要有一个时间戳Field
例如以下内容:
{
"id":1,
"name":"zhj",
"age":18,
"detail":"a good man",
"timeStamp":"123"
}
//其中id是主键
实体类是非常关键的,Java作为一门面向对象语言,可以说哪里需要就需要new一个对象出来
这个实体类可以根据上一步的collections格式进行编写
@Document("user")
public class User implements Serializable {
@Indexed(unique = true)
@Field("id")
private int id;
@Field("name")
private String name;
@Field("age")
private int age;
@Field("detail")
private String detail;
//setter && getter
//toString
}
关于这部分,有2个地方需要注意
其中有3个注解,分别是:
@Document表示集合的名称,即集合user
@Indexed表示主键,上面是以id为主键
@Field](mailto:Indexed@Field)表示集合中的字段的键名,而private int id的值将会是字段id的值
"_id"和"id"的区别
如果在实体类重定义了Field"_id"的话,如下:
@Document("user") public class User implements Serializable { //不需要这个了:@Indexed(unique = true) @Field("_id") private int id; //setter && getter //toString }
如果没有重定义Field“_id”的话,则每一次执行增加操作时,会自动生成一个字段,且其值是根据MongoDB的生成规则,如下:
"_id" : ObjectId("5f2ce4d3ad90594600115535")
这里不建议使用MongoDB的生成规则,因为有一些操作是需要主键的,例如删除,更新操作,如果使用MongoDB的生成规则,将无法进行获取并使用
所以建议选择第一种方式——先入为主
而且建议使用随机字符串+时间戳作为_id的值
public interface UserRepository extends MongoRepository<User, String>{
}
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
}
//repository层
//不用实现//使用自带的
//service层
// 增
public User saveOne(User user){
try {
return userRepository.save(user);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
//test层
@Test
void save() {
User user = new User();
user.setId(1);
user.setName("zhj");
user.setDetail("我是好人");
user.setAge(21);
User result = userService.saveOne(user);
if(result!=null){
System.out.println(result.toString());
} else{
System.out.println("插入失败");
}
}
//结果//数据库会产生以下记录
{
"_id" : ObjectId("5f2eb009dd289611253d8cf8"),
"id" : 1,
"name" : "zhj",
"age" : 21,
"detail" : "我是好人",
"_class" : "pro.zhj.model.User"
}
//注意以下返回值是int类型
//repository层
public int deleteOneById(int id);
//service层
// 删
public int deleteOne(int id){
return userRepository.deleteOneById(id);
}
//test层
@Test
void delete() {
int flag;
flag = userService.deleteOne(2);
System.out.println(flag);
}
//结果
1
//repository层
//无需实现
//service层
public User updateOne(User user){
return userRepository.save(user);
}
//test层
@Test
void update() {
User user = new User();
user.setId(2);
user.setName("小丑");
user.setDetail("其实我也不是个坏人");
user.setAge(25);
User result = userService.updateOne(user);
if(result!=null){
System.out.println(result.toString());
}else{
System.out.println("更改失败");
}
}
//结果
//原
{ "_id" : ObjectId("5f2eb24e1c3e41794ecbd89a"), "id" : 2, "name" : "小丑", "age" : 25, "detail" : "我是个坏人", "_class"
: "pro.zhj.model.User" }
//更改后
{ "_id" : ObjectId("5f2eb3829591964c8d347f51"), "id" : 2, "name" : "小丑", "age" : 25, "detail" : "其实我也不是个坏人",
"_class" : "pro.zhj.model.User" }
//repository层
public User findOneById(int id);
//service层
// 查
public User findOne(int id){
return userRepository.findOneById(id);
}
//test层
@Test
void find() {
User result = userService.findOne(1);
if(result!= null){
System.out.println(result.toString());
}else{
System.out.println("查找失败");
}
}
//结果//控制台
//User{id=1, name='zhj', age=21, detail='我是好人'}
基本上一些简单的增删改查就是上面的部分了。
当然实际开发中并不会这么简单,例如根据某个Field排序或者模糊查询等等。
或者分页的概念,那么就很需要下面的内容了。
MongoRepository实现分页非常简单,使用Pageable即可
@Query (...)
public Page<ItemLost> findAllUsers(Pageable pageable);
public List<ItemLost> findAllUsers(int page){
int pageSize = 10;//每页10个元素
Pageable pageable = PageRequest.of(page,pageSize);
return userRepository.findAllUsers(pageable).getContent();
}
//注意:方法的返回类型
@Query注解可以说使用频繁了,一些复杂查询都可以使用@Query实现
例如上面说的模糊查询,比较查询,排序显示等等。
那么@Query注解下面有6个属性,其中3个:value、field和sort可以说是常用的
当然剩下3个也有用,但没有使用过,以后再进行补充。
属性value可以实现复杂查询,例如匹配查询、模糊查询、条件查询等等
@Query (value = "{'_id':?0}",fields = "{}")
public User findUserById(String id);
//以上内容表示根据 _id 进行查询
//其中?0表示的是第0个参数,就是方法内的第1个参数
//如果不设置,即表示查询全部
@Query (value = "{}",fields = "{}")
public User findUserById(String id);
@Query (value = "{'fileHead':?#{{$regex:[0]}}}",fields = "{}",sort = "{}")
public Page<FileDocument> findFileListByFileNameLike(String fileName,Pageable pageable);
//以上内容表示 根据fileHead进行模糊查询
//原本需求是:根据传入的文件名参数对collections的字段fileHead进行匹配
//其中关键词是:$regex
//为什么会这么写呢:是因为也是通过查询资料知道可以这样实现模糊查询,
//也即是参考链接中的@Query的使用,其中用到了EL表达式
以上可以说是 只要知道需求,就可以根据上面的例子,直接进行书写
但是,有时一些需求就需要进行整理
例如 什么时候使用交集和并集的概念,即and 或者 or
//一个and
@Query (value="{'$and': [{'id':?0},{ 'timeStamp':{'$gte':?1}},{'iseffect':{'$lte':'2'}}]}")
public Page<User> findUserById(String id,String timeStamp,Pageable pageable);
//上面的内容表示的是:
//and的概念,即必须满足以下内容:
//1.id==参数id
//2.timeStamp >= 参数timeStamp//gte大于等于
//3.iseffect <= 2//lte表示小于等于
//and 和 or 同时使用
@Query (value = "{'$and': [{'$or': [{ 'lostName':?#{{$regex:[0]}}},{ 'lostAdress':?#{{$regex:[0]}}}]},{ 'timeStamp':{'$gte':?1}},{'iseffect':'3'}]}")
public Page<ItemLost> findItemsByKeyWord(String keyWord,String timeStamp,Pageable pageable);
//其实只要掌握了其中的一种之后,复杂的就是组合,就是所谓的组装。
//首先完成 一个or 完成之后,将or看成是一个条件 然后和另一个条件完成and
为什么会提到这个呢,是因为Java是一门面向对象语言,往往有时是定义collections不仅仅只是使用到一些基本的数据类型,还会可以使用到自定义的对象,例如:
//联系 class Call{ private String type;//联系方式 private String call;//联系号码 } @Document("user") public class User{ @Field("_id") private String id; @Field('call') private Call call; ... }
那么如果有一个需求:获取联系方式为callType的所有用户,则可以这样写
@Query (value = "{'call.type':?0}",fields = "{}") public Page<User> findUserByCallType(String callType); //即使用call.type表示call对象的type属性
如果参数是一个对象,则可以这么写
@Query (value = "{'call.type':?0.call.type}",fields = "{}") public Page<User> findUserByCallType(User user); //即使用call.type表示call对象的type属性 //即找到跟参数user对象相同的联系方式的所有用户
当然以后可能会遇到更加复杂的需求,那么关键在于value属性值的添加。
一般而言,可以根据上面的例子进行组装,当然如果无法获取解决方案,
那么如何知道呢,其实可以去看MongoDB的一些查询语句,就可以得到启发。
可以到这里进行学习:菜鸟教程-MongoDB查询
相比属性value,fields的功能性就比较单一了——控制字段的显示
@Query (value = "{'_id':?0}",fields = "{'username':1,'passwd':0}") public User findUserById(String id); //以上的fields表示的是显示username,不显示passwd
这里的不显示不是说直接整个字段不显示,而是将该字段的值设置为null;
{ "id":1, "userName":"zhj", "passwd":null, "timeStamp":"123" }
题外话:
这有什么用呢,怎么说呢,我觉得有一个作用——安全和资源
其一:安全
一般而言,后端开发总是前后端分离,那么接口肯定会有可能暴露,
如果不控制字段的显示,显示全部字段内容,那么将会对安全有非常大的危险。
例如恶意删除,恶意调用等等,所以可以对于一些未使用到的敏感字段进行隐藏。
特别是:微信小程序项目开发。
其二:资源:
如果每一个请求都显示大量的无用字段,即业务对于该字段并没有进行利用,那么如果请求返回太多的话,也是对资源的一种浪费。
一般而言sort都是根据时间进行排序,所以非常推荐设计一个字段存储时间戳
当进行查询的时候再根据这个字段进行排序
@Query (value = "{}",fields = "{}",sort = "{timeStamp:-1}") public Page<User> findUserSortByCreateTimeDESC(Pageable pageable); //以上表示根据创建时间进行倒序排列 //-1 表示 desc //1 表示 asc
sort的功能其实也可以像value的一样的,
例如根据某个字段进行排序,如果当某个字段相同时,再根据某个字段排序
以后如果有遇到,再进行记录
以上内容都是对于MongoDB的使用经历,基本可以满足一般的开发需求了。
如果以后还遇到一些另外的需求,再进行补充。
另外,后面会做一篇关于MongoTemplate的。