查詢概要
MongoDB 查詢數據的語法格式如下:
db.collection.find(query, projection)
query :可選,使用查詢操作符指定查詢條件
projection :可選,使用投影操作符指定返回的鍵。查詢時返回文檔中所有鍵值, 只需省略該參數即可(默認省略)。需要以易讀的方式來讀取數據,可以使用 pretty() 方法;
查詢選擇器
(1)in語句示例:
db.users.find({"username":{"$in":["lison","sean"]}})
(2)not語句示例:
db.users.find({"lenght":{"$not":{"$gte":1.77}}}).pretty()
因爲not語句 會把不包含查詢語句字段的文檔 也檢索出來
(3)exists語句示例:
db.users.find({"lenght":{"$exists":true}}).pretty()
判斷文檔有沒有關心的字段
查詢選擇
映射
字段選擇:db.users.find({},{'username':1})
字段排除:db.users.find({},{'username':0})
排序
sort():db.orders.find().sort({'orderTime':1,'price':1})1:升序 -1:降序
跳過和限制
skip(n):跳過n條數據
limit(n):限制n條數據
e.g:db.orders.find().sort({'orderTime':-1}).limit(5).skip(5)
查詢唯一值
distinct():查詢指定字段的唯一值,e.g:db.users.distinct(“age”)
測試數據
var user1 = {
"username" : "lison",
"country" : "china",
"address" : {
"aCode" : "411000",
"add" : "長沙"
},
"favorites" : {
"movies" : ["殺破狼2","戰狼","雷神1"],
"cites" : ["長沙","深圳","上海"]
},
"age" : 18,
"salary":NumberDecimal("18889.09"),
"lenght" :1.79,
"comments" : [
{
"author" : "lison1",
"content" : "lison評論1",
"commentTime" : ISODate("2017-01-06T04:26:18.354Z")
},
{
"author" : "lison2",
"content" : "lison評論2",
"commentTime" : ISODate("2017-02-06T04:26:18.354Z")
},
{
"author" : "lison3",
"content" : "lison評論3",
"commentTime" : ISODate("2017-03-06T04:26:18.354Z")
},
{
"author" : "lison4",
"content" : "lison評論4",
"commentTime" : ISODate("2017-04-06T04:26:18.354Z")
},
{
"author" : "lison5",
"content" : "lison是蒼老師的小迷弟",
"commentTime" : ISODate("2017-05-06T04:26:18.354Z")
},
{
"author" : "lison6",
"content" : "lison評論6",
"commentTime" : ISODate("2017-06-06T04:26:18.354Z")
},
{
"author" : "lison7",
"content" : "lison評論7",
"commentTime" : ISODate("2017-07-06T04:26:18.354Z")
},
{
"author" : "lison8",
"content" : "lison評論8",
"commentTime" : ISODate("2017-08-06T04:26:18.354Z")
},
{
"author" : "lison9",
"content" : "lison評論9",
"commentTime" : ISODate("2017-09-06T04:26:18.354Z")
}
]
};
字符串數組選擇查詢
1.數組單元素查詢
db.users.find({"favorites.movies":"蜘蛛俠"})
查詢數組中包含“蜘蛛俠”
2.數組精確查找
db.users.find({"favorites.movies":[ "殺破狼2", "戰狼", "雷神1" ]},{"favorites.movies":1})
查詢數組等於[ “殺破狼2”, “戰狼”, “雷神1” ]的文檔,嚴格按照順序;
3.數組多元素查詢
db.users.find({"favorites.movies":{"$all":[ "雷神1", "戰狼" ]}},{"favorites.movies":1})
查詢數組包含[“雷神1”, “戰狼” ]的文檔,跟順序無關
4.索引查詢
db.users.find({"favorites.movies.0":"殺破狼2"},{"favorites.movies":1})
查詢數組中第一個爲“殺破狼2”的文檔
5.返回數組子集
db.users.find({},{"favorites.movies":{"$slice":[1,2]},"favorites":1})
$slice可以取兩個元素數組,分別表示從幾條開始和顯示的條數;
對象數組選擇查詢
1. 單元素查詢
db.users.find({"comments":{"author" : "lison6","content" : "lison評論6"}})
備註:對象數組精確查找
2.查找lison1 或者 lison12評論過的user ($in查找符)
db.users.find({"comments.author":{"$in":["lison1","lison12"]}}).pretty()
備註:跟數量無關,跟順序無關;
3.查找lison1 和 lison12都評論過的user
db.users.find({"comments.author":{"$all":["lison12","lison1"]}}).pretty()
備註:跟數量有關,跟順序無關;
4.查找lison5評語爲“lison是蒼老師的小迷弟”的user($elemMatch查找符)
db.users.find({"comments":{"$elemMatch":{"author" : "lison5","content" : "lison是蒼老師的小迷弟"}}}) .pretty()
備註:數組中對象數據要符合查詢對象裏面所有的字段,$全元素匹配,和順序無關;
查詢練習
需求描述
查看一個人的信息,打開頁面只顯示三條評論
點擊評論的下一頁按鈕,新加載三條評論
默認按照評論時間降序
解決方案描述
1. 新增評論時,使用$sort運算符進行排序,插入評論後,再按照評論時間降序排序;
2. 查看人員時加載最新的三條評論;
3. 點擊評論的下一頁按鈕,新加載三條評論
4. 如果有多種排序需求怎麼處理?
實訓腳本:
(1)新增評論時,使用$sort運算符進行排序,插入評論後,再按照評論時間降序排序;
db.users.updateOne({"username":"lison",},
{
"$push": {
"comments": {
$each: [
{
"author" : "james",
"content" : "lison是個好老師!",
"commentTime" : ISODate("2018-01-06T04:26:18.354Z")
}
],
$sort: {"commentTime":-1}
}
}
}
);
注意:$sort操作符必須和$each配合使用
(2)查看人員時加載最新的三條評論;
db.users.find({"username":"lison"},{"comments":{"$slice":[0,3]}}).pretty()
(3)點擊評論的下一頁按鈕,新加載三條評論
原方法:db.users.find({"username":"lison"},{"comments":{"$slice":[3,3]},"$elemMatch":""}).pretty();
新的方法:db.users.find({"username":"lison"},{"comments":{"$slice":[3,3]},"$id":1}).pretty();
(4)如果有多種排序需求怎麼處理?使用聚合
db.users.aggregate([{"$match":{"username":"lison"}},
{"$unwind":"$comments"},
{$sort:{"comments.commentTime":-1}},
{"$project":{"comments":1}},
{"$skip":6},
{"$limit":3}])
----------------------------------------------------------------
聚合訓練:
查詢2015年4月3號之前,每個用戶每個月消費了多少錢,並按用戶名進行排序:
db.ordersTest.aggregate([
{"$match":{ "orderTime" : { "$lt" : new Date("2015-04-03T16:00:00.000Z")}}},
{"$group":{"_id":{"useCode":"$useCode","month":{"$month":"$orderTime"}},"total":{"$sum":"$price"}}},
{"$sort":{"_id":1}}
])
原生Document方式
@Configuration
public class AppConfig {
/*
* Use the standard Mongo driver API to create a com.mongodb.MongoClient instance.
*/
@Bean
public MongoClient mongoClient() {
// CodecRegistry registry = CodecRegistries.fromRegistries(MongoClient.getDefaultCodecRegistry(),
// CodecRegistries.fromProviders(PojoCodecProvider.builder().automatic(true).build()));
MongoClientOptions mco = MongoClientOptions.builder()
.writeConcern(WriteConcern.ACKNOWLEDGED)
.connectionsPerHost(100)
.threadsAllowedToBlockForConnectionMultiplier(5)
.maxWaitTime(120000).connectTimeout(10000).build();
MongoClient client = new MongoClient(new ServerAddress("120.78.154.33", 27022), mco);
return client;
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JavaDriverTest {
private static final Logger logger = LoggerFactory.getLogger(JavaDriverTest.class);
private MongoDatabase db;
private MongoCollection<Document> collection;
@Resource
private MongoClient client;
@Before
public void init(){
db = client.getDatabase("lison");
collection=db.getCollection("users");
}
@Test
//測試elemMatch操作符,數組中對象數據要符合查詢對象裏面所有的字段
//查找lison5評語爲“lison是蒼老師的小迷弟”的人
//db.users.find({"comments":{"$elemMatch":{"author" : "lison5","content" : "lison是蒼老師的小迷弟"}}}) .pretty()
public void testElemMatch(){
//定義數據的處理類
final List<Document> ret = new ArrayList<>();
Block<Document> printBlock = getBlock(ret);
//
Document filter = new Document().append("author","lison5")
.append("content","lison是蒼老師的小迷弟");
Bson elemMatch = Filters.elemMatch("comments",filter );
FindIterable<Document> find = collection.find(elemMatch);
printOperation(ret, printBlock, find);
}
/**
* db.users.updateOne({"username":"lison",},
{"$push": {
"comments": {
$each: [{
"author" : "james",
"content" : "lison是個好老師!",
"commentTime" : ISODate("2018-01-06T04:26:18.354Z")
}
],
$sort: {"commentTime":-1}
}}});
*/
@Test
//新增評論時,使用$sort運算符進行排序,插入評論後,再按照評論時間降序排序
public void demoStep1(){
Bson filter = eq("username", "lison");
Document comment = new Document().append("author","cang")
.append("content","lison是我的粉絲")
.append("commentTime", new Date());
//$sort: {"commentTime":-1}
Document sortDoc = new Document().append("commentTime", -1);
PushOptions sortDocument = new PushOptions().sortDocument(sortDoc);
// $each
Bson pushEach = Updates.pushEach("comments", Arrays.asList(comment), sortDocument);
UpdateResult updateOne = collection.updateOne(filter, pushEach);
System.out.println(updateOne.getModifiedCount());
}
@Test
//查看人員時加載最新的三條評論;
//db.users.find({"username":"lison"},{"comments":{"$slice":[0,3]}}).pretty()
public void demoStep2(){
final List<Document> ret = new ArrayList<>();
Block<Document> printBlock = getBlock(ret);
FindIterable<Document> find = collection.find(eq("username", "lison"))
.projection(slice("comments", 0, 3));
printOperation(ret, printBlock, find);
}
@Test
//點擊評論的下一頁按鈕,新加載三條評論
//db.users.find({"username":"lison"},{"comments":{"$slice":[3,3]},"$id":1}).pretty();
public void demoStep3(){
final List<Document> ret = new ArrayList<>();
Block<Document> printBlock = getBlock(ret);
//{"username":"lison"}
Bson filter = eq("username", "lison");
//"$slice":[3,3]
Bson slice = slice("comments", 3, 3);
//"$id":1
Bson includeID = include("id");
//{"comments":{"$slice":[3,3]},"$id":1})
Bson projection = fields(slice,includeID);
FindIterable<Document> find = collection.find(filter)
.projection(projection);
printOperation(ret, printBlock, find);
}
@Test
/**
* db.users.aggregate([{"$match":{"username":"lison"}},
{"$unwind":"$comments"},
{$sort:{"comments.commentTime":-1}},
{"$project":{"comments":1}},
{"$skip":6},
{"$limit":3}])
*/
//如果有多種排序需求怎麼處理,使用聚合
public void demoStep4(){
final List<Document> ret = new ArrayList<>();
Block<Document> printBlock = getBlock(ret);
List<Bson> aggregates = new ArrayList<>();
aggregates.add(match(eq("username","lison")));
aggregates.add(unwind("$comments"));
aggregates.add(sort(orderBy(ascending("comments.commentTime"))));
aggregates.add(project(fields(include("comments"))));
aggregates.add(skip(0));
aggregates.add(limit(3));
AggregateIterable<Document> aggregate = collection.aggregate(aggregates);
printOperation(ret, printBlock, aggregate);
}
//dbRef測試
//dbref其實就是關聯關係的信息載體,本身並不會去關聯數據
@Test
public void dbRefTest(){
final List<Document> ret = new ArrayList<>();
Block<Document> printBlock = getBlock(ret);
FindIterable<Document> find = collection.find(eq("username", "lison"));
printOperation(ret, printBlock, find);
}
//---------------------------------------------------------------------------
private void printOperation(List<Document> ret, Block<Document> printBlock,
AggregateIterable<Document> aggregate) {
aggregate.forEach(printBlock);
System.out.println(ret.size());
ret.removeAll(ret);
}
private void printOperation(final List<Document> ret,
Block<Document> printBlock, FindIterable<Document> find) {
find.forEach(printBlock);
System.out.println(ret.size());
ret.removeAll(ret);
}
private Block<Document> getBlock(final List<Document> ret) {
Block<Document> printBlock = new Block<Document>() {
@Override
public void apply(Document t) {
logger.info("---------------------");
// logger.info(t.toJson());
Object object = t.get("comments");
System.out.println(object);
logger.info("---------------------");
ret.add(t);
}
};
return printBlock;
}
}
Spring方式
@Document(collection="users")
public class User {
private ObjectId id;
private String username;
private String country;
private Address address;
private Favorites favorites;
private int age;
private BigDecimal salary;
private float lenght;
@DBRef
private Comments comments;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public Favorites getFavorites() {
return favorites;
}
public void setFavorites(Favorites favorites) {
this.favorites = favorites;
}
public ObjectId getId() {
return id;
}
public void setId(ObjectId id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public BigDecimal getSalary() {
return salary;
}
public void setSalary(BigDecimal salary) {
this.salary = salary;
}
public float getLenght() {
return lenght;
}
public void setLenght(float lenght) {
this.lenght = lenght;
}
public Comments getComments() {
return comments;
}
public void setComments(Comments comments) {
this.comments = comments;
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTest {
private static final Logger logger = LoggerFactory
.getLogger(SpringTest.class);
@Resource
private MongoOperations tempelate;
@Test
// db.users.find({"comments":{"$elemMatch":{"author" : "lison5","content" :
// "lison是蒼老師的小迷弟"}}}) .pretty()
public void testElemMatch() {
Query query = query(where("comments").elemMatch(where("author").is("lison5").and("content").is("lison是蒼老師的小迷弟")));
List<User> find = tempelate.find(query, User.class);
System.out.println(find.size());
}
/**
* db.users.updateOne({"username":"lison",},
{"$push": {
"comments": {
$each: [{
"author" : "james",
"content" : "lison是個好老師!",
"commentTime" : ISODate("2018-01-06T04:26:18.354Z")
}
],
$sort: {"commentTime":-1}
}}});
*/
@Test
// 新增評論時,使用$sort運算符進行排序,插入評論後,再按照評論時間降序排序
public void demoStep1() {
Query query = query(where("username").is("lison"));
Comment comment = new Comment();
comment.setAuthor("cang");
comment.setCommentTime(new Date());
comment.setContent("lison是我的粉絲");
Update update = new Update();
PushOperatorBuilder pob = update.push("comments");
pob.each(comment);
pob.sort(new Sort(new Sort.Order(Direction.DESC, "commentTime")));
System.out.println("---------------");
WriteResult updateFirst = tempelate.updateFirst(query, update,User.class);
System.out.println("---------------");
System.out.println(updateFirst.getN());
}
@Test
// 查看人員時加載最新的三條評論;
// db.users.find({"username":"lison"},{"comments":{"$slice":[0,3]}}).pretty()
public void demoStep2() {
//{"username":"lison"}
Query query = query(where("username").is("lison"));
//{"comments":{"$slice":[0,3]}
query.fields().include("comments").slice("comments", 0, 3);
System.out.println("---------------");
List<User> find = tempelate.find(query, User.class);
System.out.println("---------------");
System.out.println(find);
}
@Test
// 點擊評論的下一頁按鈕,新加載三條評論
// db.users.find({"username":"lison"},{"comments":{"$slice":[3,3]},"$id":1}).pretty();
public void demoStep3() {
Query query = query(where("username").is("lison"));
query.fields().include("comments").slice("comments", 3, 3)
.include("id");
System.out.println("---------------");
List<User> find = tempelate.find(query, User.class);
System.out.println("---------------");
System.out.println(find);
}
/**
* db.users.aggregate([{"$match":{"username":"lison"}},
{"$unwind":"$comments"},
{$sort:{"comments.commentTime":-1}},
{"$project":{"comments":1}},
{"$skip":6},
{"$limit":3}])
*/
// 如果有多種排序需求怎麼處理,使用聚合
@Test
public void demoStep4() {
Aggregation aggs = newAggregation(
match(where("username").is("lison")),
unwind("comments"),
sort(Direction.ASC, "comments.commentTime"),
project("comments"),
skip(6),
limit(3));
System.out.println("---------------");
AggregationResults<Object> aggregate = tempelate.aggregate(aggs, "users", Object.class);
System.out.println("---------------");
List<Object> mappedResults = aggregate.getMappedResults();
System.out.println(mappedResults.size());
}
@Test
//(1)注意相關的實體bean要加上註解@document,@dbRef
//(2)spring對dbRef進行了封裝,發起了兩次查詢請求
public void dbRefTest(){
System.out.println("----------------------------");
List<User> users = tempelate.findAll(User.class);
System.out.println("----------------------------");
System.out.println(users);
// System.out.println(users.get(0).getComments());
}
}
當出現對同一個屬性進行投影時,只會執行最後一個投影操作。
查看一個人的信息,打開頁面只顯示三條評論,實現的語句:
db.users.find({"username":"lison"},{"comments":{"$slice":[0,3]}}).pretty()
點擊評論的下一頁按鈕,新加載三條評論,實現的查詢語句:
不推薦:db.users.find({"username":"lison"},{"comments.lists":{"$slice":[0,3]},"comments":1}).pretty()
推薦:db.users.find({"username":"lison"},{"comments":{"$slice":[3,3]},"$elemMatch":""}).pretty()
PS
1.SPRING 寫入的數據,_class字段修改後是否會影響到實體類的生成驗證
答:修改對代碼沒有影響
2.convert代碼示例
答:spring 1.x版本沒有提供對mongo decemal128數據默認的支持,所以需要自定義轉換器進行轉換;
spring 配置示例
<!-- mongodb連接池配置 -->
<mongo:mongo-client host="192.168.225.129" port="27022">
<mongo:client-options
write-concern="ACKNOWLEDGED"
connections-per-host="100"
threads-allowed-to-block-for-connection-multiplier="5"
max-wait-time="120000"
connect-timeout="10000"/>
</mongo:mongo-client>
<!-- mongodb數據庫工廠配置 -->
<mongo:db-factory dbname="test" mongo-ref="mongo" />
<mongo:mapping-converter base-package="com.dongnao.mongodb.entity">
<mongo:custom-converters>
<mongo:converter>
<bean class="com.dongnao.mongo.convert.BigDecimalToDecimal128Converter"/>
</mongo:converter>
<mongo:converter>
<bean class="com.dongnao.mongo.convert.Decimal128ToBigDecimalConverter"/>
</mongo:converter>
</mongo:custom-converters>
</mongo:mapping-converter>
<!-- mongodb模板配置 -->
<bean id="anotherMongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
<constructor-arg name="mongoConverter" ref="mappingConverter"/>
<property name="writeResultChecking" value="EXCEPTION"></property>
</bean>
Java客戶端解析
原生java驅動
MongoClient → MongoDatabase →MongoCollection
• MongoClient被設計成線程安全、可以被多線程共享的。通常訪問數據庫集羣的應用只需要一個實例
• 如果需要使用pojo對象讀寫,需要將PojoCodecProvider注入到client中
查詢和更新的API類
查詢器:com.mongodb.client.model.Filters
投影器:com.mongodb.client.model.Projections
更新器:com.mongodb.client.model.Updates
構造器模式的理解:https://www.jianshu.com/p/e2a2fe3555b9
public class User {
private final String firstName; // 必傳參數
private final String lastName; // 必傳參數
private final int age; // 可選參數
private final String phone; // 可選參數
private final String address; // 可選參數
private User(UserBuilder builder) {
this.firstName = builder.firstName;
this.lastName = builder.lastName;
this.age = builder.age;
this.phone = builder.phone;
this.address = builder.address;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getAge() {
return age;
}
public String getPhone() {
return phone;
}
public String getAddress() {
return address;
}
public static class UserBuilder {
private final String firstName;
private final String lastName;
private int age;
private String phone;
private String address;
public UserBuilder(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public UserBuilder age(int age) {
this.age = age;
return this;
}
public UserBuilder phone(String phone) {
this.phone = phone;
return this;
}
public UserBuilder address(String address) {
this.address = address;
return this;
}
public User build() {
return new User(this);
}
}
}
作者:湫水長天
鏈接:https://www.jianshu.com/p/e2a2fe3555b9
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。
Mongodb連接池配置
數據模式設計
nosql在數據模式設計上的優勢
讀寫效率高-在IO性能上有先天獨厚的優勢;
可擴展能力強,不需要考慮關聯,數據分區分庫,水平擴展就比較簡單;
動態模式,不要求每個文檔都具有完全相同的結構。對很多異構數據場景支持非常好;
模型自然-文檔模型最接近於我們熟悉的對象模型;
mongoDB能不能實現關聯查詢?
先考慮內嵌, 直接按照你的對象模型來設計你的數據模型。如果你的對象模型數量不多,關係不是很複雜,直接一種對象對應一個集合就可以了
單個bson 文檔最大不能超過16M ;當文檔超過16M的時候,就應該考慮使用引用(DBRef)了,在主表裏存儲一個id值,指向另一個表中的 id 值。
DBRef語法:{ "$ref" : <value>, "$id" : <value>, "$db" : <value> }
$ref:引用文檔所在的集合的名稱;
$id:所在集合的_id字段值;
$db:可選,集合所在的數據庫實例;
使用dbref腳本示例:
var lison = db.users.findOne({"username":"lison"});
var dbref = lison.comments;
db[dbref.$ref].findOne({"_id":dbref.$id})
聚合的理解
聚合框架就是定義一個管道,管道里的每一步都爲下一步輸出數據數據
常用的管道操作
$project:投影,指定輸出文檔中的字段;
$match:用於過濾數據,只輸出符合條件的文檔。$match使用MongoDB的標準查詢操作
$limit:用來限制MongoDB聚合管道返回的文檔數。
$skip:在聚合管道中跳過指定數量的文檔,並返回餘下的文檔。
$unwind:將文檔中的某一個數組類型字段拆分成多條,每條包含數組中的一個值。
$group:將集合中的文檔分組,可用於統計結果。
$sort:將輸入文檔排序後輸出。
$group操作符
$group:可以分組的數據執行如下的表達式計算:
$sum:計算總和。
$avg:計算平均值。
$min:根據分組,獲取集合中所有文檔對應值得最小值。
$max:根據分組,獲取集合中所有文檔對應值得最大值。
$push:將指定的表達式的值添加到一個數組中。
$addToSet:將表達式的值添加到一個集合中(無重複值)。
$first:返回每組第一個文檔,如果有排序,按照排序,如果沒有按照默認的存儲的順序的第一個文檔。
$last:返回每組最後一個文檔,如果有排序,按照排序,如果沒有按照默認的存儲的順序的最後個文檔
聚合訓練
查詢2015年4月3號之前,每個用戶每個月消費的總金額,並按用戶名進行排序:
db.ordersTest.aggregate([
{"$match":{ "orderTime" : { "$lt" : new Date("2015-04-03T16:00:00.000Z")}}},
{"$group":{"_id":{"useCode":"$useCode","month":{"$month":"$orderTime"}},"total":{"$sum":"$price"}}},
{"$sort":{"_id":1}}
])