剛接觸mongodb不久。踩到許多坑,記錄下一些基於spring-data-mongodb的東西吧
首先。應該瞭解下什麼情況下使用mongodb,什麼情況下用mysql:
- 業務需要事物,使用mysql,因為mongodb不支援事物
- 資料量大,但是資料本身價值不大,使用mongodb
- 資料是非結構化的,且資料量大,使用mongodb
- 業務未來走向不明確,使用mongodb,方便擴充套件
簡單使用
下面開始接入spring-data-mongodb
使用maven和spring-boot,pom檔案裡引入依賴即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
在java程式碼裡,向集合的實體類上面加上@Document,表示這個類被spring-data-mongodb標識了,可以對映成mongodb裡的一個集合,預設名字就是類名,也可以取別名@Document(collection=”alias”),示例如下:
@Document
public class Example{
private String id;
private String picture;
}
注意,String型別的id屬性不可缺少,因為mongodb的每個文件都要有一個id(也可以是其他型別的Id,詳情可以去官方文件看看),setget方法我就不貼了,用lombok更方便。
這裡就可以直接new 一個Example儲存了,所以現在建個Repositry用於儲存資料
非常簡單隻要繼承一個介面就可以 public interface ExampleRepository extends CrudRepository<Example, String> {}
這裡第一個泛型是上面的文件物件實體,第二個泛型是文件的id型別
然後就可以直接用exampleRepositry.save(example)儲存文件了。簡單的介紹就到這裡
自定義實現複雜查詢,修改等操作
定義一個Repository類,並加上@Repository註解,然後新增自己的方法就可以了,下面上程式碼
@Repository
public class ExampleAdvancedRepository {
@Autowired
private MongoOperations mongoOperations;
public boolean updateAlias(String id, String alias) {
return mongoOperations.updateFirst(
query(where("id").is(id)),
new Update().set("alias", alias),
Example.class).isUpdateOfExisting();
}
}
簡單的一個例項,根據Id修改別名,這裡一點要說的就是這個id屬性我們傳入進去是String型別,但是monogdbTemplate會將這個id轉成ObjectId,但是只侷限於這個文件屬性名叫id的,如果是其他屬性比如userId,雖然也是ObjectId,但是用where("userId").is(userId)
這樣查詢是失敗的,因為這裡的userId並沒有轉成ObjectId,正確的做法是where("userId").is(new ObjectId(userId))
陣列操作:
增:只要push進陣列裡就行
public boolean createReply(Reply reply) {
return mongoOperations.updateFirst(
query(where("id").is(reply.getCommentId())),
new Update()
.inc("total_replies", 1)
.push("replies", reply),
Comment.class).isUpdateOfExisting();
}
刪:刪除操作構建一個帶id的DB物件,然後丟進pull方法裡就行了,程式碼如下
public boolean deleteReply(String commentId, String replyId) {
BasicDBObject object = new BasicDBObject();
object.put("id", replyId);
return mongoOperations.updateFirst(
query(where("id").is(commentId)),
new Update()
.inc("total_replies", -1)
.pull("replies", object),
Comment.class).isUpdateOfExisting();
}
這個示例刪除了陣列裡某個物件,所以要使用傳入一個刪除物件的id,如果陣列裡存的就是數字,或者字串就可以直接pull("users","張三")
改:
- 陣列裡的物件修改,用佔位符即可,程式碼如下
public boolean createReplyVote(String commentId, String replyId, String userId) {
return mongoOperations.updateFirst(
query(where("id").is(commentId).andOperator(
where("replies.id").is(new ObjectId(replyId)))),
new Update()
.addToSet("replies.$.voter_ids", userId)
.inc("replies.$.total_voters", 1),
Comment.class).isUpdateOfExisting();
}
用佔位符$表示陣列裡的某個匹配的元素
- 陣列裡巢狀陣列,更新裡面陣列裡的某個物件的屬性
根據上面的方法,發現要用2個佔位符,比如set("replies.$.voters.$.name","李四")
但是這種寫法是錯誤的。這種情況在mongodb3.6之前是沒辦法解決的,只能將陣列巢狀陣列的結構改變,變成Map的形式,不能出現兩個佔位符的情況,或者修改設計,將裡邊的陣列拿出來作為獨立的文件
索引
建立索引可以使用mongodb的指令碼去建立,也可以用spring-data-mongo提供的註解建立索引
下面弄個簡單的索引,程式碼如下:
@Document
@CompoundIndexes({
@CompoundIndex(def = "{`userId`: 1, `createdDate`: -1}")
})
public class Example {
private String id;
private String userId;
private Date createdDate;
}
這裡建立 了一個簡單的聚合索引,根據userId和createdDate建立的索引。
檢視索引是否起效果,可以使用explain方法檢視執行計劃,示例如下
collection結構
{
"_id" : ObjectId("5b0cc0f2fde29f2ea0641b16"),
"userId" : "1",
"createdDate" : ISODate("1995-09-30T16:00:00.000+0000"),
"gender" : "MALE"
}
執行語句db.Example.find({"userId":"1"}).explain();
可以檢視到結果
{
"queryPlanner": {
"plannerVersion": 1.0,
...
"winningPlan": {
"stage": "FETCH",
"inputStage": {
"stage": "IXSCAN",
"keyPattern": {
"user_id": 1.0,
"created_date": -1.0
},
"indexName": "user_id_1_created_date_-1",
"isMultiKey": false,
"isUnique": false,
"isSparse": false,
"isPartial": false,
"indexVersion": 1.0,
"direction": "forward",
"indexBounds": {
"user_id": ["["22", "22"]"],
"created_date": ["[MaxKey, MinKey]"]
}
}
},
"rejectedPlans": []
}
...
}
可以看到關鍵的資訊,winningPlan 表示走了索引,然後是具體的資訊,rejectedPlans表示沒有走的索引
外來鍵引用@DBRef
在使用mongdb的時候經常會遇到一個集合巢狀了另一個集合的情況,如果不用引用的話,就得自己手動寫程式碼冗餘資料,修改的時候就比較麻煩,要同時修改多處。用mongodb提供的$ref 能很好的解決這個問題
首先寫個例子
@Document
public class Clazz{
private String id;
private String name;
@DBRef
private People leader;
}
@Document
public class People{
private String id;
private String name;
private Integer gender;
private Integer age;
}
每個班級都需要個班主任leader,這個時候用外來鍵非常合適使用很簡單,只要在要用到外來鍵的欄位上,加上@DBRef就可以了。先建立個People集合,儲存寫people資訊,然後再建立Clazz集合,可以發現Clazz儲存的時候leader變成了一個引用型別。
{
"_id" : ObjectId("5b10a741628e881fc8848d21"),
"name" : "王大錘",
"leader" : DBRef("People", ObjectId("5b0cf065fde29f25486bf532")),
}
那麼查詢的時候,如果要根據leader查詢班級怎麼辦,@DBRef的查詢也非常簡單。
public Clazz findByLeaderId(String leaderId) {
return mongoOperations.findOne(
query(where("leader.$id").is(new ObjectId(leaderId))),
GhostFriend.class);
}
這裡注意2個地方,第一個就是引用的時候要加上$id
,這樣才能表示此id是引用,第二個地方就是new ObjectId()
在自定義複雜查詢哪裡有提到為什麼要new ObjectId()
陣列操作裡有些特殊的場景
1.佇列(先進先出)
比如我要保留最新的3個瀏覽記錄,那麼就應該建立一個陣列,先進的先刪除,永遠保留3個元素
public boolean updateHistory(String id,History history) {
return mongoOperations.updateFirst(
query(where("id").is(id)),
new Update()
.push("histories").slice(-3).each(history)
People.class).isUpdateOfExisting();
}
這裡利用了slice接收int型別的值,表示擷取多少個元素,正值則是從前到後保留,負值表示從後到前保留,each則表示加入陣列的元素
這裡只是做一個簡單的介紹,網上中文資料不多。詳情可以檢視mongodb官方文件slice用法