Spring in action 4 剑指Spring - (九)使用NoSQL数据库 - MongoDB

mac2025-08-01  1

使用NoSql数据库

NoSQL,指的是非关系型的数据库。NoSQL有时也称作Not Only SQL的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称。

NoSQL用于超大规模数据的存储。(例如谷歌或Facebook每天为他们的用户收集万亿比特的数据)。这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展。

MongoDB:

MongoDB支持的数据类型:

数字:
// 默认使用64位浮点型数值如下: db.sang_collec.insert({x:3.1415926}) db.sang_collec.insert({x:3}) // 对于整型值,我们可以使用NumberInt或者NumberLong表示 db.sang_collec.insert({x:NumberInt(10)}) db.sang_collec.insert({x:NumberLong(12)}) -------------------------------------------------------------------------- db.sang_collec.find() { "_id" : ObjectId("5daff48e0c442201f697ab56"), "x" : 3.1415926 } { "_id" : ObjectId("5daff48e0c442201f697ab57"), "x" : 3 } { "_id" : ObjectId("5daff48e0c442201f697ab58"), "x" : 10 } { "_id" : ObjectId("5daff48e0c442201f697ab59"), "x" : NumberLong(12) }
字符串:
db.sang_collec.insert({x:"hello MongoDB!"}) -------------------------------------------------------------------------- db.sang_collec.find() { "_id" : ObjectId("5daff58e8a3e00661f6f2871"), "x" : "hello MongoDB!" }
正则表达式:
db.sang_collec.find({x:/^(hello)(.[a-zA-Z0-9])+/i}) -------------------------------------------------------------------------- db.sang_collec.find() { "_id" : ObjectId("5daff58e8a3e00661f6f2871"), "x" : "hello MongoDB!" }
数组:
db.sang_collec.insert({x:[1,2,3,4,new Date()]}) -------------------------------------------------------------------------- db.sang_collec.find() { "_id" : ObjectId("5daff6198a3e00661f6f2872"), "x" : [ 1, 2, 3, 4, ISODate("2019-10-23T06:41:29.220Z") ] }
日期:
db.sang_collec.insert({x:new Date()}) -------------------------------------------------------------------------- db.sang_collec.find() { "_id" : ObjectId("5daff6198a3e00661f6f2872"), "x" : ISODate("2019-10-23T06:41:29.220Z")}
内嵌文档:
db.sang_collec.insert({name:"三国演义",author:{name:"罗贯中",age:99}}); -------------------------------------------------------------------------- db.sang_collec.find() { "_id" : ObjectId("5daff6ab8a3e00661f6f2874"), "name" : "三国演义", "author" : { "name" : "罗贯中", "age" : 99 } }
ObjectId:

我们每次插入一条数据系统都会自动帮我们插入一个_id键,这个键的值不可以重复,它可以是任何类型的,我们也可以手动的插入,默认情况下它的数据类型是ObjectId,由于MongoDB在设计之初就是用作分布式数据库,所以使用ObjectId可以避免不同数据库中_id的重复(如果使用自增的方式在分布式系统中就会出现重复的_id的值),这个特点有点类似于Git中的版本号和Svn中版本号的区别。

ObjectId使用12字节的存储空间,每个字节可以存储两个十六进制数字,所以一共可以存储24个十六进制数字组成的字符串,在这24个字符串中,前8位表示时间戳,接下来6位是一个机器码,接下来4位表示进程id,最后6位表示计数器。

MongoDB文档更新操作:

文档替换:
将下边的文档①转化成② ------------------------------------------------ { "_id" : ObjectId("59f005402844ff254a1b68f6"), "name" : "红楼梦", "authName" : "曹雪芹", "authAge" : 99.0 } ------------------------------------------------- { "_id" : ObjectId("59f005402844ff254a1b68f6"), "name" : "红楼梦", "author" : { "authNames" : "曹雪芹", "authAges" : 99.0 } } ------------------------------------------------- db.book.insert({name:'红楼梦',authName:'曹雪芹',authAge:99})//插入文档① db.book.find() var book=db.book.findOne({name:'红楼梦'})// 获取这个文档,赋值给变量book book.auth={authNames:book.authName,authAges:book.authAge}// 在book变量中添加新的文档auth delete book.authName// 删除book中的authName delete book.authAge// 删除book中的authAge db.book.update({name:'红楼梦'},book)// 更新 db.book.find()
使用修改器:
db.collections.update({x:1},{$set:{x:99}},false,true)

第一个参数是查询的条件,第二个参数是修改器。第一个false表示如果不存在update记录,是否将我们要更新的文档作为一个新文档插入,true表示插入,false表示不插入,默认为false,第二个true表示是否更新全部查到的文档,false表示只更新第一条记录,true表示更新所有查到的文档。

$set: 设置将某key设置为某值

db.sang_collec.update({x:3},{$set:{x:1}})// 将所有的x = 3的改成1

$unset: 删除当前Key

db.sang_collec.update({x:1},{$unset:{x:1}})// 将所有的x = 1的x(field)删除

$inc: 对文档的某个值为数字型(只能为满足要求的数字)的键进行增减的操作

db.sang_collec.update({},{$inc:{y:1}})// 所有文档中的y + 1,但是只会更新第一条。 db.sang_collec.update({},{$inc:{y:1}},false,true)// 设置属性以后。所有文档y + 1

$push : 数组修改器

db.sang_collec.update({x:4},{$push:{name:"xxxx"}})// 添加一个 db.sang_collec.update({x:4},{$push:{name:{$each:["111","222","333"]}}})// 添加多个 db.sang_collec.update({x:4},{$push:{name:{$each:["qqq","www","eee"],$slice:-4}}})// 添加多个,保留最新的4个 注意$slice的值为负数 db.sang_collec.update({x:4},{$push:{name:{$each:[{score:1},{score:3},{score:5},{score:1},{score:2},{score:6}],$slice:4,$sort:{score:-1}}}})// 根据score排序(-1表示降序,1表示升序)保存前4个。sort不能只和each

$addToSet: 数组判断插入

db.sang_collec.update({x:4},{$addToSet:{names:"yyyy"}})// addToSet如果names:"yyyy"存在则不插入。不存在插入

$pop: 数组中删除数据根据首尾

db.sang_collec.update({x:4},{$pop:{names:1}})//pop 删除数据中数据。1表示从数组的末尾删除一条数据,-1表示从数组的开头删除一条数据。

$pull: 数组删除中的指定数据

db.sang_collec.update({x:4},{$pull:{names:"xxxx"}})// pull指定删除数组中的数据

MongoDB文档查询操作:

比较运算符:
符号含义$lt<$lte<=$gt>$gte>=$ne!= /* 2 */ { "_id" : ObjectId("59f0b17249fc5c9c2412a666"), "name" : "zs", "score" : 100.0 } /* 3 */ { "_id" : ObjectId("59f0b17249fc5c9c2412a667"), "name" : "ls", "score" : 90.0 } /* 4 */ { "_id" : ObjectId("59f0b17249fc5c9c2412a668"), "name" : "ww", "score" : 70.0 } /* 5 */ { "_id" : ObjectId("59f0b17249fc5c9c2412a669"), "name" : "zl", "score" : 80.0 }

查询 成绩在[90,100]之间的学生 :

db.sang_collect.find({score:{$lte:100,$gte:90}}) $in 有点类似于SQL中的in关键字 如我想查询x为1或者2的所有文档 : db.sang_collect.find({x:{$in:[1,2]}}) $nin 恰好相反,表示查询某一个字段不在某一个范围内的文档, 如查询**x不为1或者2(不为1且不为2)**的所有文档 : db.sang_collect.find({x:{$nin:[1,2]}})

$or 有点类似于SQL中的or关键字,表示多个查询条件之间是或的关系 :

db.sang_collect.find({$or:[{x:1},{y:99}]})

$type 可以用来根据数据类型查找数据,比如我想要查找x类型为数字的文档 :

db.sang_collect.find({x:{$type:1}}) 类型对应数字别名说明Double11doubleString2stringObject3objectArray4arrayBinary data5binDataUndefined6undefined弃用ObjectId7objectIdBoolean8boolDate9dateNull10nullRegular Expression11regexDBPointer12dbPointerJavaScript13javascriptSymbol14symbolJavaScript(with scope)15javascriptWithScope32-bit integer16intTimestamp17timestamp64-bit integer18longMin key-1minKeyMax key127maxKey

$not 用来执行取反操作,比如我想要查询所有x的类型不为数字的文档 :

db.sang_collect.find({x:{$not:{$type:1}}})

$and 类似于SQL中的and,比如我想查询y大于98并且小于100的数据 :

db.sang_collect.find({$and:[{y:{$gt:98}},{y:{$lt:100}}]})

null

null的查询稍微有点不同,假如我想查询z为null的数据,如下:

db.sang_collect.find({z:null})

这样不仅会查出z为null的文档,也会查出所有没有z字段的文档,如果只想查询z为null的字段,那就再多加一个条件,判断一下z这个字段存在不,如下:

db.sang_collect.find({z:{$in:[null],$exists:true}})

索引:

查看执行计划explain():
参数含义plannerVersion查询计划版本namespace要查询的集合indexFilterSet是否使用索引parsedQuery查询条件,此处为x=1winningPlan最佳执行计划stage查询方式,常见的有COLLSCAN/全表扫描、IXSCAN/索引扫描、FETCH/根据索引去检索文档、SHARD_MERGE/合并分片结果、IDHACK/针对_id进行查询filter过滤条件direction搜索方向rejectedPlans拒绝的执行计划serverInfoMongoDB服务器信息
查看索引getIndexes()
db.sang_collect.getIndexes()
创建索引ensureIndex()
db.sang_collect.ensureIndex({x:1})// 1表示升序,-1表示降序
创建索引时的参数ensureIndex()
db.sang_collect.ensureIndex({x:1},{name:"myfirstindex",dropDups:true,background:true,unique:true,sparse:true,v:1,weights:99999})

1.name表示索引的名称 2.dropDups表示创建唯一性索引时如果出现重复,则将重复的删除,只保留第一个 3.background是否在后台创建索引,在后台创建索引不影响数据库当前的操作,默认为false 4.unique是否创建唯一索引,默认false 5.sparse对文档中不存在的字段是否不起用索引,默认false 6.v表示索引的版本号,默认为2 7.weights表示索引的权重

删除索引dropIndex()
db.sang_collect.dropIndex("xIndex") db.sang_collect.dropIndexes()

管道操作符:

$match: 获取集合中所有指定字段的指定值的文档

db.sang_collect.aggregate({$match:{x:1}}) ----------------------------------------- { "_id" : ObjectId("5db006610c442201f697ab65"), "x" : 1, "y" : 12 } { "_id" : ObjectId("5db006610c442201f697ab66"), "x" : 1, "y" : 12 } { "_id" : ObjectId("5db022d30c442201f697ab6d"), "x" : 1, "y" : 11 } { "_id" : ObjectId("5db022d30c442201f697ab6e"), "x" : 1, "y" : 11 } { "_id" : ObjectId("5db022d30c442201f697ab6f"), "x" : 1, "y" : 11 }

$project: 可以用来提取想要的字段

db.sang_collec.aggregate({$project:{x:1,_id:0}}) ------------------------------------------------ { "x" : 5 } { "x" : 123132 } { "x" : 123132 } { "x" : 123132 } { "x" : 123132 } { "x" : 1 } { "x" : 1 } { "x" : 1 } { "x" : 4 } { "x" : 5 }

数字表达式:

{ "_id" : ObjectId("59f841f5b998d8acc7d08863"), "orderAddressL" : "ShenZhen", // 商品价格 "prodMoney" : 45.0, // 运费 "freight" : 13.0, // 折扣金额 "discounts" : 3.0, "orderDate" : ISODate("2017-10-31T09:27:17.342Z"), "prods" : [ "可乐", "奶茶" ] } 订单的总费用为商品费用加上运费,查询如下: db.sang_collect.aggregate({$project:{totalMoney:{$add:["$prodMoney","$freight"]}}}) 实际付款的费用是总费用减去折扣,如下: db.sang_collect.aggregate({$project:{totalPay:{$subtract:[{$add:["$prodMoney","$freight"]},"$discounts"]}}}) 比如计算prodMoney和freight和discounts的乘积: db.sang_collect.aggregate({$project:{test1:{$multiply:["$prodMoney","$freight","$discounts"]}}}) 再比如求freight的商,如下: db.sang_collect.aggregate({$project:{test1:{$divide:["$prodMoney","$freight"]}}}) 再比如用prodMoney取模,如下: db.sang_collect.aggregate({$project:{test1:{$mod:["$prodMoney","$freight"]}}})

日期表达式:

db.sang_collect.aggregate({$project:{"年份":{$year:"$orderDate"},"月份":{$month:"$orderDate"},"一年中第几周":{$week:"$orderDate"},"日期":{$dayOfMonth:"$orderDate"},"星期":{$dayOfWeek:"$orderDate"},"一年中第几天":{$dayOfYear:"$orderDate"},"时":{$hour:"$orderDate"},"分":{$minute:"$orderDate"},"秒":{$second:"$orderDate"},"毫秒":{$millisecond:"$orderDate"},"自定义格式化时间":{$dateToString:{format:"%Y年%m月%d %H:%M:%S",date:"$orderDate"}}}}) ------------------------------------------------------------------------------------------------ { "_id" : ObjectId("59f841f5b998d8acc7d08861"), "年份" : 2017, "月份" : 10, "一年中第几周" : 44, "日期" : 31, "星期" : 3, "一年中第几天" : 304, "时" : 9, "分" : 27, "秒" : 17, "毫秒" : 342, "自定义格式化时间" : "2017年10月31 09:27:17" }

字符串表达式:

$substr 截取0-2(前两个字符)

db.sang_collect.aggregate({$project:{addr:{$substr:["$orderAddressL",0,2]}}})

$concat 拼接字符串

db.sang_collect.aggregate({$project:{addr:{$concat:["$orderAddressL",{$dateToString:{format:"--%Y年%m月%d",date:"$orderDate"}}]}}})

$toLower 转小写

db.sang_collect.aggregate({$project:{addr:{$toLower:"$orderAddressL"}}})

$toUpper 转大写

db.sang_collect.aggregate({$project:{addr:{$toUpper:"$orderAddressL"}}})

$group分组操作:

基本操作:

//根据地址分类。统计每个地址的数量 db.sang_collect.aggregate({$group:{_id:"$orderAddressL",count:{$sum:1}}}) //根据地址分类。统计每个地址的freight的平均数 db.sang_collect.aggregate({$group:{_id:"$orderAddressL",avgFreight:{$avg:"$freight"}}})

极值操作

//根据地址分类。展示最大的freight的值 db.sang_collect.aggregate({$group:{_id:"$orderAddressL",maxFreight:{$max:"$freight"}}}) // 根据地址分类。展示该城市第一个运费单: db.sang_collect.aggregate({$group:{_id:"$orderAddressL",firstFreight:{$first:"$freight"}}})

mapReduce: MapReduce可以用来实现更复杂的聚合命令,使用MapReduce主要实现两个函数:map函数和reduce函数,map函数用来生成键值对序列,map函数的结果作为reduce函数的参数,reduce函数中再做进一步的统计,比如我的数据集如下:

{"_id" : ObjectId("59fa71d71fd59c3b2cd908d7"),"name" : "鲁迅","book" : "呐喊","price" : 38.0,"publisher" : "人民文学出版社"} {"_id" : ObjectId("59fa71d71fd59c3b2cd908d8"),"name" : "曹雪芹","book" : "红楼梦","price" : 22.0,"publisher" : "人民文学出版社"} {"_id" : ObjectId("59fa71d71fd59c3b2cd908d9"),"name" : "钱钟书","book" : "宋诗选注","price" : 99.0,"publisher" : "人民文学出版社"} {"_id" : ObjectId("59fa71d71fd59c3b2cd908da"),"name" : "钱钟书","book" : "谈艺录","price" : 66.0,"publisher" : "三联书店"} {"_id" : ObjectId("59fa71d71fd59c3b2cd908db"),"name" : "鲁迅","book" : "彷徨","price" : 55.0,"publisher" : "花城出版社"}

假如我想查询每位作者所出的书的总价,操作如下:

var map=function(){emit(this.name,this.price)} var reduce=function(key,value){return Array.sum(value)} var options={out:"totalPrice"} db.sang_books.mapReduce(map,reduce,options); db.totalPrice.find() ---------------------------------------------------------- { "_id" : "曹雪芹", "value" : 22.0 } { "_id" : "钱钟书", "value" : 165.0 } { "_id" : "鲁迅", "value" : 93.0 }

emit函数主要用来实现分组,接收两个参数,第一个参数表示分组的字段,第二个参数表示要统计的数据,reduce来做具体的数据处理操作,接收两个参数,对应emit方法的两个参数,这里使用了Array中的sum函数对price字段进行自加处理,options中定义了将结果输出的集合,届时我们将在这个集合中去查询数据

将每位作者的书列出来,如下:

var map=function(){emit(this.name,this.book)} var reduce=function(key,value){return value.join(',')} var options={out:"books"} db.sang_books.mapReduce(map,reduce,options); db.books.find() ----------------------------------------------------------- { "_id" : "曹雪芹", "value" : "红楼梦" } { "_id" : "钱钟书", "value" : "宋诗选注,谈艺录" } { "_id" : "鲁迅", "value" : "呐喊,彷徨" }

Spring Data MongoDB:

pom.xml:

<!--mongodb--> <dependency> <groupId>org.mongodb</groupId> <artifactId>mongo-java-driver</artifactId> <version>3.11.1</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-mongodb</artifactId> <version>2.1.6.RELEASE</version> </dependency>

MongoDBConfig.java:

@Configuration @EnableMongoRepositories("chendongdong.spring.test.bean.Test9.dao") public class MongoDBConfig extends AbstractMongoConfiguration{ @Override protected String getDatabaseName() { return "mymongo"; } @Override public MongoClient mongoClient() { return new MongoClient("119.23.xxx.130",27017); } }

Userdao.java:

public interface UserDao extends MongoRepository<User, String> { }

测试代码:

@Autowired private UserDao userDao; @RequestMapping("/Print6") @ResponseBody public Object Print6(){ Iterable<User> all = userDao.findAll(); return "ok"; }

我们来看看为什么没有提供任何方法就可以使用其中的很多方法,那么这些方法究竟是怎么来的呢?

我们只写了接口,但是没有写实现类,这个实现类就是Spring在运行的时候,注入的代理对象。

Spring怎么知道生成的那个dao的实现类?因为在配置文件中指定了dao接口所在的包

@EnableMongoRepositories("chendongdong.spring.test.bean.Test9.dao")

通过JdkDynamicAopProxy对象的invoke生成了代理对象SimpleJpaRepository,因为JdkDynamicAopProxy实现了InvocationHandler接口,所以这个类有invoke方法

在JdkDynamicAopProxy的invoke方法中有个target对象,这个对象就是真正干活的对象( SimpleJpaRepository )

SimpleJpaRepository实现了我们dao接口继承的那两个接口,所以这个类中肯定有接口的所有方法

SimpleMongoRepository源码:

public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> { private final MongoOperations mongoOperations; private final MongoEntityInformation<T, ID> entityInformation; public SimpleMongoRepository(MongoEntityInformation<T, ID> metadata, MongoOperations mongoOperations) { Assert.notNull(metadata, "MongoEntityInformation must not be null!"); Assert.notNull(mongoOperations, "MongoOperations must not be null!"); this.entityInformation = metadata; this.mongoOperations = mongoOperations; } @Override public <S extends T> S save(S entity) { Assert.notNull(entity, "Entity must not be null!"); if (entityInformation.isNew(entity)) { return mongoOperations.insert(entity, entityInformation.getCollectionName()); } return mongoOperations.save(entity, entityInformation.getCollectionName()); } ... ... 剩下的不copy了 }
最新回复(0)