SpringBoot?基于?MongoTemplate?的工具類過程詳解
一、 什么是MongoDB
MongoDB基于分布式文件存儲的數(shù)據(jù)庫。由C++語言編寫。MongoDB是一個高性能,開源,無模式的文檔型數(shù)據(jù)庫,是當(dāng)前NoSql數(shù)據(jù)庫中比較熱門的一種。
他支持的數(shù)據(jù)結(jié)構(gòu)非常松散,是類似json的bjson格式,因此可以存儲比較復(fù)雜的數(shù)據(jù)類型。Mongo最大的特點是他支持的查詢語言非常強大,其語法有點類似于面向?qū)ο蟮牟樵冋Z言,幾乎可以實現(xiàn)類似關(guān)系數(shù)據(jù)庫單表查詢的絕大部分功能,而且還支持對數(shù)據(jù)建立索引。
傳統(tǒng)的關(guān)系數(shù)據(jù)庫一般由數(shù)據(jù)庫(database)、表(table)、記錄(record)三個層次概念組成,MongoDB是由數(shù)據(jù)庫(database)、集合(collection)、文檔對象(document)三個層次組成。MongoDB對于關(guān)系型數(shù)據(jù)庫里的表,但是集合中沒有列、行和關(guān)系概念,這體現(xiàn)了模式自由的特點。
MongoDB中的一條記錄就是一個文檔,是一個數(shù)據(jù)結(jié)構(gòu),由字段和值對組成。MongoDB文檔與JSON對象類似。字段的值有可能包括其它文檔、數(shù)組以及文檔數(shù)組。
適用場景,我們可以直接用MongoDB來存儲鍵值對類型的數(shù)據(jù),如:驗證碼、Session等;由于MongoDB的橫向擴展能力,也可以用來存儲數(shù)據(jù)規(guī)模會在未來變的非常巨大的數(shù)據(jù),如:日志、評論等;由于MongoDB存儲數(shù)據(jù)的弱類型,也可以用來存儲一些多變json數(shù)據(jù),如:與外系統(tǒng)交互時經(jīng)常變化的JSON報文。而對于一些對數(shù)據(jù)有復(fù)雜的高事務(wù)性要求的操作,如:賬戶交易等就不適合使用MongoDB來存儲。
二、MongoDB優(yōu)點
1.性能
在大數(shù)據(jù)時代中,大數(shù)據(jù)量的處理已經(jīng)成了考量一個數(shù)據(jù)庫最重要的原因之一。而MongoDB的一個主要目標就是盡可能的讓數(shù)據(jù)庫保持卓越的性能,這很大程度地決定了MongoDB的設(shè)計。在一個以傳統(tǒng)機械硬盤為主導(dǎo)的年代,硬盤很可能會成為性能的短板,而MongoDB選擇了最大程度而利用內(nèi)存資源用作緩存來換取卓越的性能,并且會自動選擇速度最快的索引來進行查詢。MongoDB盡可能精簡數(shù)據(jù)庫,將盡可能多的操作交給客戶端,這種方式也是MongoDB能夠保持卓越性能的原因之一。
2.擴展
現(xiàn)在互聯(lián)網(wǎng)的數(shù)據(jù)量已經(jīng)從過去的MB、GB變?yōu)榱爽F(xiàn)在的TB級別,單一的數(shù)據(jù)庫顯然已經(jīng)無法承受,擴展性成為重要的話題,然而現(xiàn)在的開發(fā)人員常常在選擇擴展方式的時候犯了難,到底是選擇橫向擴展還是縱向擴展呢?
橫向擴展(scale out)是以增加分區(qū)的方式將數(shù)據(jù)庫拆分成不同的區(qū)塊來分布到不同的機器中來,這樣的優(yōu)勢是擴展成本低但管理困難。
縱向擴展(scale up) 縱向擴展與橫向擴展不同的是他會將原本的服務(wù)器進行升級,讓其擁有更強大的計算能力。這樣的優(yōu)勢是易于管理無需考慮擴展帶來的眾多問題,但缺點也顯而易見,那就是成本高。一臺大型機的價格往往非常昂貴,并且這樣的升級在數(shù)據(jù)達到極限時,可能就找不到計算能力更為強大的機器了。
而MongoDB選擇的是更為經(jīng)濟的橫向擴展,他可以很容易的將數(shù)據(jù)拆分至不同的服務(wù)器中。而且在獲取數(shù)據(jù)時開發(fā)者也無需考慮多服務(wù)器帶來的問題,MongoDB可以將開發(fā)者的請求自動路由到正確的服務(wù)器中,讓開發(fā)者脫離橫向擴展帶來的弊病,更專注于程序的開發(fā)上。
3.使用
MongoDB采用的是NoSQL的設(shè)計方式,可以更加靈活的操作數(shù)據(jù)。在進行傳統(tǒng)的RDBMS中你一定遇到過幾十行甚至上百行的復(fù)雜SQL語句,傳統(tǒng)的RDBMS的SQL語句中包含著大量關(guān)聯(lián),子查詢等語句,在增加復(fù)雜性的同時還讓性能調(diào)優(yōu)變得更加困難。MongoDB的面向文檔(document-oriented)設(shè)計中采用更為靈活的文檔來作為數(shù)據(jù)模型用來取代RDBMS中的行,面向文檔的設(shè)計讓開發(fā)人員獲取數(shù)據(jù)的方式更加靈活,甚至于開發(fā)人員僅用一條語句即可查詢復(fù)雜的嵌套關(guān)系,讓開發(fā)人員不必為了獲取數(shù)據(jù)而絞盡腦汁。
3. Spring Boot中MongoDB集成方式
1> 常規(guī)集成
1、pom包配置
pom包里面添加spring-boot-starter-data-mongodb包引用
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> </dependencies>
2、在application.properties中添加配置
spring.data.mongodb.uri=mongodb://name:pass@localhost:27017/test
多個IP集群可以采用以下配置:
spring.data.mongodb.uri=mongodb://user:pwd@ip1:port1,ip2:port2/database
3、創(chuàng)建數(shù)據(jù)實體
public class UserEntity implements Serializable { private static final long serialVersionUID = -3258839839160856613L; private Long id; private String userName; private String passWord; //getter、setter省略 }
4、創(chuàng)建實體dao的增刪改查操作
dao層實現(xiàn)了UserEntity對象的增刪改查
@Component public class UserDaoImpl implements UserDao { @Autowired private MongoTemplate mongoTemplate; /** * 創(chuàng)建對象 * @param user */ @Override public void saveUser(UserEntity user) { mongoTemplate.save(user); } /** * 根據(jù)用戶名查詢對象 * @param userName * @return */ @Override public UserEntity findUserByUserName(String userName) { Query query=new Query(Criteria.where("userName").is(userName)); UserEntity user = mongoTemplate.findOne(query , UserEntity.class); return user; } /** * 更新對象 * @param user */ @Override public void updateUser(UserEntity user) { Query query=new Query(Criteria.where("id").is(user.getId())); Update update= new Update().set("userName", user.getUserName()).set("passWord", user.getPassWord()); //更新查詢返回結(jié)果集的第一條 mongoTemplate.updateFirst(query,update,UserEntity.class); //更新查詢返回結(jié)果集的所有 // mongoTemplate.updateMulti(query,update,UserEntity.class); } /** * 刪除對象 * @param id */ @Override public void deleteUserById(Long id) { Query query=new Query(Criteria.where("id").is(id)); mongoTemplate.remove(query,UserEntity.class); } }
5、開發(fā)對應(yīng)的測試方法
@RunWith(SpringRunner.class) @SpringBootTest public class UserDaoTest { @Autowired private UserDao userDao; @Test public void testSaveUser() throws Exception { UserEntity user=new UserEntity(); user.setId(2l); user.setUserName("小明"); user.setPassWord("fffooo123"); userDao.saveUser(user); } @Test public void findUserByUserName(){ UserEntity user= userDao.findUserByUserName("小明"); System.out.println("user is "+user); } @Test public void updateUser(){ UserEntity user=new UserEntity(); user.setId(2l); user.setUserName("天空"); user.setPassWord("fffxxxx"); userDao.updateUser(user); } @Test public void deleteUserById(){ userDao.deleteUserById(1l); } }
6、查看驗證結(jié)果可以使用工具mongoVUE工具來連接后直接圖形化展示查看,也可以登錄服務(wù)器用命令來查看
1.登錄mongos
bin/mongo -host localhost -port 20000
2、切換到test庫
use test
3、查詢userEntity集合數(shù)據(jù)
db.userEntity.find()
根據(jù)3查詢的結(jié)果來觀察測試用例的執(zhí)行是否正確。到此springboot對應(yīng)mongodb的增刪改查功能已經(jīng)全部實現(xiàn)。
2> 多數(shù)據(jù)源集成
1、pom包配置添加lombok和spring-boot-autoconfigure包引用
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>RELEASE</version> </dependency>
Lombok - 是一個可以通過簡單的注解形式來幫助我們簡化消除一些必須有但顯得很臃腫的Java代碼的工具,通過使用對應(yīng)的注解,可以在編譯源碼的時候生成對應(yīng)的方法。簡單試了以下這個工具還挺好玩的,加上注解我們就不用手動寫 getter\setter、構(gòu)建方式類似的代碼了。spring-boot-autoconfigure - 就是spring boot的自動化配置
2、配置文件使用YAML的形式添加兩條數(shù)據(jù)源,如下:
mongodb: primary: host: 192.168.9.60 port: 20000 database: test secondary: host: 192.168.9.60 port: 20000 database: test1
3、配置兩個庫的數(shù)據(jù)源
封裝讀取以mongodb開頭的兩個配置文件
@Data @ConfigurationProperties(prefix = "mongodb") public class MultipleMongoProperties { private MongoProperties primary = new MongoProperties(); private MongoProperties secondary = new MongoProperties(); }
配置不同包路徑下使用不同的數(shù)據(jù)源
第一個庫的封裝
@Configuration @EnableMongoRepositories(basePackages = "com.neo.model.repository.primary", mongoTemplateRef = PrimaryMongoConfig.MONGO_TEMPLATE) public class PrimaryMongoConfig { protected static final String MONGO_TEMPLATE = "primaryMongoTemplate"; }
第二個庫的封裝
@Configuration @EnableMongoRepositories(basePackages = "com.neo.model.repository.secondary", mongoTemplateRef = SecondaryMongoConfig.MONGO_TEMPLATE) public class SecondaryMongoConfig { protected static final String MONGO_TEMPLATE = "secondaryMongoTemplate"; }
讀取對應(yīng)的配置信息并且構(gòu)造對應(yīng)的MongoTemplate
@Configuration public class MultipleMongoConfig { @Autowired private MultipleMongoProperties mongoProperties; @Primary @Bean(name = PrimaryMongoConfig.MONGO_TEMPLATE) public MongoTemplate primaryMongoTemplate() throws Exception { return new MongoTemplate(primaryFactory(this.mongoProperties.getPrimary())); } @Bean @Qualifier(SecondaryMongoConfig.MONGO_TEMPLATE) public MongoTemplate secondaryMongoTemplate() throws Exception { return new MongoTemplate(secondaryFactory(this.mongoProperties.getSecondary())); } @Bean @Primary public MongoDbFactory primaryFactory(MongoProperties mongo) throws Exception { return new SimpleMongoDbFactory(new MongoClient(mongo.getHost(), mongo.getPort()), mongo.getDatabase()); } @Bean public MongoDbFactory secondaryFactory(MongoProperties mongo) throws Exception { return new SimpleMongoDbFactory(new MongoClient(mongo.getHost(), mongo.getPort()), mongo.getDatabase()); } }
兩個庫的配置信息已經(jīng)完成。
4、創(chuàng)建兩個庫分別對應(yīng)的對象和Repository借助lombok來構(gòu)建對象
@Data @AllArgsConstructor @NoArgsConstructor @Document(collection = "first_mongo") public class PrimaryMongoObject { @Id private String id; private String value; @Override public String toString() { return "PrimaryMongoObject{" + "id='" + id + '\'' + ", value='" + value + '\'' + '}'; } }
對應(yīng)的Repository
public interface PrimaryRepository extends MongoRepository<PrimaryMongoObject, String> { }
繼承了 MongoRepository 會默認實現(xiàn)很多基本的增刪改查,省了很多自己寫dao層的代碼
Secondary和上面的代碼類似就不貼出來了
5、最后測試
@RunWith(SpringRunner.class) @SpringBootTest public class MuliDatabaseTest { @Autowired private PrimaryRepository primaryRepository; @Autowired private SecondaryRepository secondaryRepository; @Test public void TestSave() { System.out.println("************************************************************"); System.out.println("測試開始"); System.out.println("************************************************************"); this.primaryRepository .save(new PrimaryMongoObject(null, "第一個庫的對象")); this.secondaryRepository .save(new SecondaryMongoObject(null, "第二個庫的對象")); List<PrimaryMongoObject> primaries = this.primaryRepository.findAll(); for (PrimaryMongoObject primary : primaries) { System.out.println(primary.toString()); } List<SecondaryMongoObject> secondaries = this.secondaryRepository.findAll(); for (SecondaryMongoObject secondary : secondaries) { System.out.println(secondary.toString()); } System.out.println("************************************************************"); System.out.println("測試完成"); System.out.println("************************************************************"); } }
到此,mongodb多數(shù)據(jù)源的使用已經(jīng)完成。
3> Spring Boot中使用MongoDB的連接池配置
Spring Boot中通過依賴spring-boot-starter-data-mongodb,來實現(xiàn)spring-data-mongodb的自動配置。但是默認情況下,Spring Boot 中,并沒有像使用MySQL或者Redis一樣,提供了連接池配置的功能。因此,我們需要自行重寫 MongoDbFactory,實現(xiàn)MongoDB客戶端連接的參數(shù)配置擴展。
需要說明的是,MongoDB的客戶端本身就是一個連接池,因此,我們只需要配置客戶端即可。
1、 配置文件為了統(tǒng)一Spring Boot的配置,我們要將重寫的配置也配置到 application.yml中,前綴為spring.data.mongodb.custom下(前綴可自己隨意配置):
spring: data: mongodb: custom: hosts: - 10.0.5.1 - 10.0.5.1 ports: - 27017 - 27018 replica-set: mgset-3590061 username: jancee password: abc123 database: jancee authentication-database: admin connections-per-host: 20 min-connections-per-host: 20
該配置例子中,配置了副本集,其中包含了主機10.0.5.1:27017和10.0.5.1:27018,其它配置與Spring Boot的標準配置類似,另外,connections-per-host為客戶端的連接數(shù),in-connections-per-host為客戶端最小連接數(shù)。
這里做個拓展,配置中的replica-set: mgset-3590061是Mongo三種集群方式中的一種。下面的集群搭建中會詳細說明。
2、 將配置包裝成類
為方便調(diào)用和可讀性,將上述配置包裝成一個配置實體類,MongoConfig.java代碼如下:
package com.feidiao.jancee.fdiot.api.config.mongo; import org.hibernate.validator.constraints.NotBlank; import org.hibernate.validator.constraints.NotEmpty; import org.springframework.stereotype.Component; import org.springframework.validation.annotation.Validated; import java.util.List; @Component @Validated public class MongoSettingsProperties { @NotBlank private String database; @NotEmpty private List<String> hosts; @NotEmpty private List<Integer> ports; private String replicaSet; private String username; private String password; private String authenticationDatabase; private Integer minConnectionsPerHost = 10; private Integer connectionsPerHost = 2; public MongoSettingsProperties() { } // get、set方法 ... }
3、 覆蓋MongoDbFactory接下來,就是覆蓋Spring Boot原有的MongoDbFactory Bean,新建文件MongoConfig.java,代碼如下:
import com.mongodb.MongoClient; import com.mongodb.MongoClientOptions; import com.mongodb.MongoCredential; import com.mongodb.ServerAddress; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.SimpleMongoDbFactory; import java.util.ArrayList; import java.util.List; @Configuration public class MongoConfig { // 注入配置實體 @Autowired private MongoSettingsProperties mongoSettingsProperties; @Bean @ConfigurationProperties( prefix = "spring.data.mongodb.custom") MongoSettingsProperties mongoSettingsProperties() { return new MongoSettingsProperties(); } // 覆蓋默認的MongoDbFactory @Bean MongoDbFactory mongoDbFactory() { //客戶端配置(連接數(shù)、副本集群驗證) MongoClientOptions.Builder builder = new MongoClientOptions.Builder(); builder.connectionsPerHost(mongoSettingsProperties.getConnectionsPerHost()); builder.minConnectionsPerHost(mongoSettingsProperties.getMinConnectionsPerHost()); if (mongoSettingsProperties.getReplicaSet() != null) { builder.requiredReplicaSetName(mongoSettingsProperties.getReplicaSet()); } MongoClientOptions mongoClientOptions = builder.build(); // MongoDB地址列表 List<ServerAddress> serverAddresses = new ArrayList<>(); for (String host : mongoSettingsProperties.getHosts()) { Integer index = mongoSettingsProperties.getHosts().indexOf(host); Integer port = mongoSettingsProperties.getPorts().get(index); ServerAddress serverAddress = new ServerAddress(host, port); serverAddresses.add(serverAddress); } System.out.println("serverAddresses:" + serverAddresses.toString()); // 連接認證 List<MongoCredential> mongoCredentialList = new ArrayList<>(); if (mongoSettingsProperties.getUsername() != null) { mongoCredentialList.add(MongoCredential.createScramSha1Credential( mongoSettingsProperties.getUsername(), mongoSettingsProperties.getAuthenticationDatabase() != null ? mongoSettingsProperties.getAuthenticationDatabase() : mongoSettingsProperties.getDatabase(), mongoSettingsProperties.getPassword().toCharArray())); } System.out.println("mongoCredentialList:" + mongoCredentialList.toString()); //創(chuàng)建客戶端和Factory MongoClient mongoClient = new MongoClient(serverAddresses, mongoCredentialList, mongoClientOptions); MongoDbFactory mongoDbFactory = new SimpleMongoDbFactory(mongoClient, mongoSettingsProperties.getDatabase()); return mongoDbFactory; } }
在這里,實現(xiàn)了MongoDB連接時,前面配置的參數(shù)的設(shè)置,按照自己的實際情況,可以在new SimpleMongoDbFactory時,增加修改自己需要的配置參數(shù)。
至此,就完成了全部配置,運行測試即可。
4. MongoDB中的基礎(chǔ)概念:Databases、Collections、Documents(這一節(jié)比較基礎(chǔ),比較基礎(chǔ),知道就好)
1> 基礎(chǔ)概念MongoDB以BSON格式的文檔(Documents)形式存儲。Databases中包含集合(Collections),集合(Collections)中存儲文檔(Documents)。
BSON是一個二進制形式的JSON文檔,它比JSON包含更多的數(shù)據(jù)類型。
Databases在MongoDB中,databases保存文檔(Documents)的集合(Collections)。
2> 基本操作命令在Mongo Shell中,通過使用use 命令來選中database,就像下面的例子:
use myDB
- 創(chuàng)建Database如果database不存在,MongoDB會在第一次為database存儲數(shù)據(jù)的時候創(chuàng)建。因此,你可以直接切換到一個不存在的數(shù)據(jù)庫,然后執(zhí)行下面的語句:
use myNewDB db.myNewCollection1.insert( { x: 1 } )
insert()操作會創(chuàng)建名為myNewDB的database和名為myNewCollection1的collection(如果他們不存在的話)。
Collections是MongoDB在collections中存儲文檔(documents)。Collections類似于關(guān)系型數(shù)據(jù)庫中的表(tables)。
- 創(chuàng)建Collection
如果collection不存在,MongoDB會在第一次為collection存儲數(shù)據(jù)的時候創(chuàng)建。
db.myNewCollection2.insert( { x: 1 } ) db.myNewCollection3.createIndex( { y: 1 } )
無論是insert()還是createIndex()操作,都會創(chuàng)建它們各自指定的收集,如果他們不存在的話。
- 顯式創(chuàng)建
MongoDB提供db.createCollection()方法來顯式創(chuàng)建一個collection,同時還能設(shè)置各種選項,例如:設(shè)置最大尺寸和文檔校驗規(guī)則。如果你沒有指定這些選項,那么你就不需要顯式創(chuàng)建collection,因為MongoDB會在你創(chuàng)建第一個數(shù)據(jù)的時候自動創(chuàng)建collection。
若要修改這些collection選擇,可查看collMod。
- Documents校驗
3.2.x版本新增內(nèi)容。默認情況下,collection不要求文檔有相同的結(jié)構(gòu);例如,在一個collection的文檔不必具有相同的fields,對于單個field在一個collection中的不同文檔中可以是不同的數(shù)據(jù)類型。
從MongoDB 3.2開始,你可以在對collection進行update和insert操作的時候執(zhí)行文檔(documents)校驗規(guī)則。具體可參見文檔驗證的詳細信息。
3> Document結(jié)構(gòu)
MongoDB的文件是由field和value對的結(jié)構(gòu)組成,例如下面這樣的結(jié)構(gòu):
{ field1: value1, field2: value2, field3: value3, ... fieldN: valueN }
value值可以是任何BSON數(shù)據(jù)類型,包括:其他document,數(shù)字,和document數(shù)組。
例如下面的document,包含各種不同類型的值:
var mydoc = { _id: ObjectId("5099803df3f4948bd2f98391"), name: { first: "Alan", last: "Turing" }, birth: new Date('Jun 23, 1912'), death: new Date('Jun 07, 1954'), contribs: [ "Turing machine", "Turing test", "Turingery" ], views : NumberLong(1250000) }
上面例子中的各fields有下列數(shù)據(jù)類型:
_id:ObjectId類型
name:一個嵌入的document,包含first和last字段
birth和death:Date類型
contribs:字符串?dāng)?shù)組
views:NumberLong類型
Field名
Field名是一個字符串。
5> Documents中的filed名有下列限制:
_id被保留用于主鍵;其值必須是集合中唯一的、不可變的、并且可以是數(shù)組以外的任何數(shù)據(jù)類型不能以美元符號$開頭不能包含點字符.不能包含空字符Field Value限制對于索引的collections,索引字段中的值有最大長度限制。詳情請參見Maximum Index Key Length。
圓點符號MongoDB中使用圓點符號.訪問數(shù)組中的元素,也可以訪問嵌入式Documents的fields。
Arrays數(shù)組通過圓點符號.來鏈接Arrays數(shù)組名字和從0開始的數(shù)字位置,來定位和訪問一個元素數(shù)組:
“.”舉例:對于下面的document:
{ ... contribs: [ "Turing machine", "Turing test", "Turingery" ], ... }
要訪問contribs數(shù)組中的第三個元素,可以這樣訪問:
“contribs.2”嵌入式Documents通過圓點符號.來鏈接嵌入式document的名字和field名,來定位和訪問嵌入式document:
“.”舉例:對于下面的document:
{ ... name: { first: "Alan", last: "Turing" }, ... }
要訪問name中的last字段,可以這樣使用:
“name.last”
6> Documents限制
Documents有下面這些屬性和限制:
Document大小限制
每個BSON文檔的最大尺寸為16兆字節(jié)。
最大文檔大小有助于確保一個單個文檔不會使用過量的內(nèi)存,或通信過程中過大的帶寬占用。
若要存儲超過最大尺寸的文檔,MongoDB提供了GridFS API。可以看mongofiles和更多有關(guān)GridFS的文檔
Document Field順序
MongoDB中field的順序默認是按照寫操作的順序來保存的,除了下面幾種情況:
_id總是document的第一個field可能會導(dǎo)致文檔中的字段的重新排序的更新,包括字段名重命名。在2.6版本起,MongoDB開始積極地嘗試保留document中field的順序。
_id字段_id字段有以下行為和限制:
默認情況下,MongoDB會在創(chuàng)建collection時創(chuàng)建一個_id字段的唯一索引_id字段總是documents中的第一個字段。如果服務(wù)器接收到一個docuement,它的第一個字段不是_id,那么服務(wù)器會將_id字段移在開頭_id字段可以是除了array數(shù)組之外的任何BSON數(shù)據(jù)格式以下是存儲_id值的常用選項:
- 使用ObjectId
- 最好使用自然的唯一標識符,可以節(jié)省空間并避免額外的索引
- 生成一個自動遞增的數(shù)字。請參閱創(chuàng)建一個自動遞增序列字段
- 在您的應(yīng)用程序代碼中生成UUID。為了更高效的在collection和_id索引中存儲UUID值,可以用BSON的BinData類型存儲UUID。
大部分MongoDB驅(qū)動客戶端會包含_id字段,并且在發(fā)送insert操作的時候生成一個ObjectId。但是如果客戶端發(fā)送一個不帶_id字段的document,mongod會添加_id字段并產(chǎn)生一個ObjectId
5. MongoDB優(yōu)化方式
- 性能與用戶量
“如何能讓軟件擁有更高的性能?”,我想這是一個大部分開發(fā)者都思考過的問題。性能往往決定了一個軟件的質(zhì)量,如果你開發(fā)的是一個互聯(lián)網(wǎng)產(chǎn)品,那么你的產(chǎn)品性能將更加受到考驗,因為你面對的是廣大的互聯(lián)網(wǎng)用戶,他們可不是那么有耐心的。嚴重點說,頁面的加載速度每增加一秒也許都會使你失去一部分用戶,也就是說,加載速度和用戶量是成反比的。那么用戶能夠接受的加載速度到底是多少呢?
如圖,如果頁面加載時間超過10s那么用戶就會離開,如果1s–10s的話就需要有提示,但如果我們的頁面沒有提示的話需要多快的加載速度呢?是的,1s 。
當(dāng)然,這是站在一個產(chǎn)品經(jīng)理的角度來說的,但如果站在一個技術(shù)人員的角度來說呢?加載速度和用戶量就是成正比的,你的用戶數(shù)量越多需要處理的數(shù)據(jù)當(dāng)然也就越多,加載速度當(dāng)然也就越慢。這是一件很有趣的事,所以如果你的產(chǎn)品如果是一件激動人心的產(chǎn)品,那么作為技術(shù)人員你需要做的事就是讓軟件的性能和用戶的數(shù)量同時增長,甚至性能增長要快于用戶量的增長。
數(shù)據(jù)庫性能對軟件整體性能的影響是不言而喻的,那么,當(dāng)我們使用MongoDB時該如何提高數(shù)據(jù)庫性能呢?
方案一:范式化與反范式化
- 什么是范式化與反范式化
- 范式化(normalization)是關(guān)系模型的發(fā)明者埃德加·科德于1970年提出這一概念,范式化會將數(shù)據(jù)分散到不同的表中,利用關(guān)系模型進行關(guān)聯(lián),由此帶來的優(yōu)點是,在后期進行修改時,不會影響到與其關(guān)聯(lián)的數(shù)據(jù),僅對自身修改即可完成。
- 反范式化(denormalization)是針對范式化提出的相反理念,反范式化會將當(dāng)前文檔的數(shù)據(jù)集中存放在本表中,而不會采用拆分的方式進行存儲。
- 范式化和反范式化之間不存在優(yōu)劣的問題,范式化的好處是可以在我們寫入、修改、刪除時的提供更高性能,而反范式化可以提高我們在查詢時的性能。當(dāng)然NoSQL中是不存在關(guān)聯(lián)查詢的,以此提高查詢性能,但我們依舊可以以在表中存儲關(guān)聯(lián)表ID的方式進行范式化。但由此可見,NoSQL的理念中反范式化的地位是大于范式化的。
在項目設(shè)計階段,明確集合的用途是對性能調(diào)優(yōu)非常重要的一步。
從性能優(yōu)化的角度來看,集合的設(shè)計我們需要考慮的是集合中數(shù)據(jù)的常用操作,例如我們需要設(shè)計一個日志(log)集合,日志的查看頻率不高,但寫入頻率卻很高,那么我們就可以得到這個集合中常用的操作是更新(增刪改)。如果我們要保存的是城市列表呢?顯而易見,這個集合是一個查看頻率很高,但寫入頻率很低的集合,那么常用的操作就是查詢。
對于頻繁更新和頻繁查詢的集合,我們最需要關(guān)注的重點是他們的范式化程度,范式化與反范式化的合理運用對于性能的提高至關(guān)重要。然而這種設(shè)計的使用非常靈活,假設(shè)現(xiàn)在我們需要存儲一篇圖書及其作者,在MongoDB中的關(guān)聯(lián)就可以體現(xiàn)為以下幾種形式:
1. 完全分離(范式化設(shè)計)
示例1:
{ "_id" : ObjectId("5124b5d86041c7dca81917"), "title" : "如何使用MongoDB", "author" : [ ObjectId("144b5d83041c7dca84416"), ObjectId("144b5d83041c7dca84418"), ObjectId("144b5d83041c7dca84420"), ] }
我們將作者(comment) 的id數(shù)組作為一個字段添加到了圖書中去。這樣的設(shè)計方式是在非關(guān)系型數(shù)據(jù)庫中常用的,也就是我們所說的范式化設(shè)計。在MongoDB中我們將與主鍵沒有直接關(guān)系的圖書單獨提取到另一個集合,用存儲主鍵的方式進行關(guān)聯(lián)查詢。當(dāng)我們要查詢文章和評論時需要先查詢到所需的文章,再從文章中獲取評論id,最后用獲得的完整的文章及其評論。在這種情況下查詢性能顯然是不理想的。但當(dāng)某位作者的信息需要修改時,范式化的維護優(yōu)勢就凸顯出來了,我們無需考慮此作者關(guān)聯(lián)的圖書,直接進行修改此作者的字段即可。
2. 完全內(nèi)嵌(反范式化設(shè)計)
示例2:
{ "_id" : ObjectId("5124b5d86041c7dca81917"), "title" : "如何使用MongoDB", "author" : [ { "name" : "丁磊" "age" : 40, "nationality" : "china", }, { "name" : "馬云" "age" : 49, "nationality" : "china", }, { "name" : "張召忠" "age" : 59, "nationality" : "china", }, ] }
在這個示例中我們將作者的字段完全嵌入到了圖書中去,在查詢的時候直接查詢圖書即可獲得所對應(yīng)作者的全部信息,但因一個作者可能有多本著作,當(dāng)修改某位作者的信息時時,我們需要遍歷所有圖書以找到該作者,將其修改。
3. 部分內(nèi)嵌(折中方案)
示例3:
{ "_id" : ObjectId("5124b5d86041c7dca81917"), "title" : "如何使用MongoDB", "author" : [ { "_id" : ObjectId("144b5d83041c7dca84416"), "name" : "丁磊" }, { "_id" : ObjectId("144b5d83041c7dca84418"), "name" : "馬云" }, { "_id" : ObjectId("144b5d83041c7dca84420"), "name" : "張召忠" }, ] }
這次我們將作者字段中的最常用的一部分提取出來。當(dāng)我們只需要獲得圖書和作者名時,無需再次進入作者集合進行查詢,僅在圖書集合查詢即可獲得。
這種方式是一種相對折中的方式,既保證了查詢效率,也保證的更新效率。但這樣的方式顯然要比前兩種較難以掌握,難點在于需要與實際業(yè)務(wù)進行結(jié)合來尋找合適的提取字段。如同示例3所述,名字顯然不是一個經(jīng)常修改的字段,這樣的字段如果提取出來是沒問題的,但如果提取出來的字段是一個經(jīng)常修改的字段(比如age)的話,我們依舊在更新這個字段時需要大范圍的尋找并依此進行更新。
在上面三個示例中,第一個示例的更新效率是最高的,但查詢效率是最低的,而第二個示例的查詢效率最高,但更新效率最低。所以在實際的工作中我們需要根據(jù)自己實際的需要來設(shè)計表中的字段,以獲得最高的效率。
方案二: 填充因子
何為填充因子?
填充因子(padding factor)是MongoDB為文檔的擴展而預(yù)留的增長空間,因為MongoDB的文檔是以順序表的方式存儲的,每個文檔之間會非常緊湊,如圖所示。
(注:圖片出處:《MongoDB The Definitive Guide》)
1.元素之間沒有多余的可增長空間。
2.當(dāng)我們對順序表中某個元素的大小進行增長的時候,就會導(dǎo)致原來分配的空間不足,只能要求其向后移動。
3.當(dāng)修改元素移動后,后續(xù)插入的文檔都會提供一定的填充因子,以便于文檔頻繁的修改,如果沒有不再有文檔因增大而移動的話,后續(xù)插入的文檔的填充因子會依此減小。
填充因子的理解之所以重要,是因為文檔的移動非常消耗性能,頻繁的移動會大大增加系統(tǒng)的負擔(dān),在實際開發(fā)中最有可能會讓文檔體積變大的因素是數(shù)組,所以如果我們的文檔會頻繁修改并增大空間的話,則一定要充分考慮填充因子。
那么如果我們的文檔是個常常會擴展的話,應(yīng)該如何提高性能?兩種方案
1.增加初始分配空間。在集合的屬性中包含一個 usePowerOf2Sizes 屬性,當(dāng)這個選項為true時,系統(tǒng)會將后續(xù)插入的文檔,初始空間都分配為2的N次方。
這種分配機制適用于一個數(shù)據(jù)會頻繁變更的集合使用,他會給每個文檔留有更大的空間,但因此空間的分配不會像原來那樣高效,如果你的集合在更新時不會頻繁的出現(xiàn)移動現(xiàn)象,這種分配方式會導(dǎo)致寫入速度相對變慢。
2.我們可以利用數(shù)據(jù)強行將初始分配空間擴大。
db.book.insert({ "name" : "MongoDB", "publishing" : "清華大學(xué)出版社", "author" : "john" "tags" : [] "stuff" : "ggggggggggggggggggggggggggggggggggggg ggggggggggggggggggggggggggggggggggggg ggggggggggggggggggggggggggggggggggggg" })
是的,這樣看起來可能不太優(yōu)雅…但有時卻很有效!當(dāng)我們對這個文檔進行增長式修改時,只要將stuff字段刪掉即可。當(dāng)然,這個stuff字段隨便你怎么起名,包括里邊的填充字符當(dāng)然也是可以隨意添加的。
方案三:利用索引
1.索引越少越好
索引可以極大地提高查詢性能,那么索引是不是越多越好?答案是否定的,并且索引并非越多越好,而是越少越好。每當(dāng)你建立一個索引時,系統(tǒng)會為你添加一個索引表,用于索引指定的列,然而當(dāng)你對已建立索引的列進行插入或修改時,數(shù)據(jù)庫則需要對原來的索引表進行重新排序,重新排序的過程非常消耗性能,但應(yīng)對少量的索引壓力并不是很大,但如果索引的數(shù)量較多的話對于性能的影響可想而知。所以在創(chuàng)建索引時需要謹慎建立索引,要把每個索引的功能都要發(fā)揮到極致,也就是說在可以滿足索引需求的情況下,索引的數(shù)量越少越好。
1> 隱式索引
//建立復(fù)合索引 db.test.ensureIndex({"age": 1,"no": 1,"name": 1 })
我們在查詢時可以迅速的將age,no字段進行排序,隱式索引指的是如果我們想要排序的字段包含在已建立的復(fù)合索引中則無需重復(fù)建立索引。
db.test.find().sort("age": 1,"no": 1) db.test.find().sort("age": 1)
如以上兩個排序查詢,均可使用上面的復(fù)合索引,而不需要重新建立索引。
2> . 翻轉(zhuǎn)索引
//建立復(fù)合索引 db.test.ensureIndex({"age": 1})
翻轉(zhuǎn)索引很好理解,就是我們在排序查詢時無需考慮索引列的方向,例如這個例子中我們在查詢時可以將排序條件寫為"{‘age’: 0}",依舊不會影響性能。
2. 索引列顆粒越小越好
什么叫顆粒越小越好?在索引列中每個數(shù)據(jù)的重復(fù)數(shù)量稱為顆粒,也叫作索引的基數(shù)。如果數(shù)據(jù)的顆粒過大,索引就無法發(fā)揮該有的性能。例如,我們擁有一個"age"列索引,如果在"age"列中,20歲占了50%,如果現(xiàn)在要查詢一個20歲,名叫"Tom"的人,我們則需要在表的50%的數(shù)據(jù)中查詢,索引的作用大大降低。所以,我們在建立索引時要盡量將數(shù)據(jù)顆粒小的列放在索引左側(cè),以保證索引發(fā)揮最大的作用。
6. MongoDB應(yīng)用之使用log4j實現(xiàn)http請求日志入mongodb
思路及解決方案:
第一步:通過aop去切web層的controller實現(xiàn),獲取每個http的內(nèi)容并通過log4j將日志內(nèi)容寫到應(yīng)用服務(wù)器的文件系統(tǒng)中。缺點是我們在集群中部署應(yīng)用之后,應(yīng)用請求的日志被分散記錄在了不同應(yīng)用服務(wù)器的文件系統(tǒng)上,這樣分散的存儲并不利于我們對日志內(nèi)容的檢索。
第二步:
log4j提供的輸出器實現(xiàn)自Appender接口,要自定義appender輸出到MongoDB,只需要繼承AppenderSkeleton類,實現(xiàn)其中append()、close()、requiresLayout()等方法
重寫append函數(shù)要實現(xiàn)的內(nèi)容是:
- 根據(jù)log4j.properties中的配置創(chuàng)建mongodb連接
- LoggingEvent提供getMessage()函數(shù)來獲取日志消息
- 往配置的記錄日志的collection中插入日志消息
重寫close函數(shù):關(guān)閉mongodb的
到此這篇關(guān)于SpringBoot 基于 MongoTemplate 的工具類的文章就介紹到這了,更多相關(guān)SpringBoot MongoTemplate工具類內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring @Transactional注解的聲明式事務(wù)簡化業(yè)務(wù)邏輯中的事務(wù)管理
這篇文章主要為大家介紹了Spring @Transactional注解的聲明式事務(wù)簡化業(yè)務(wù)邏輯中的事務(wù)管理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-10-10Java并發(fā)編程之synchronized底層實現(xiàn)原理分析
這篇文章主要介紹了Java并發(fā)編程之synchronized底層實現(xiàn)原理,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-02-02MyBatis之foreach標簽的用法及多種循環(huán)問題
這篇文章主要介紹了MyBatis之foreach標簽的用法及多種循環(huán)問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11詳談Servlet和Filter的區(qū)別以及兩者在Struts2和Springmvc中的應(yīng)用
下面小編就為大家?guī)硪黄斦凷ervlet和Filter的區(qū)別以及兩者在Struts2和Springmvc中的應(yīng)用。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08java文件復(fù)制代碼片斷(java實現(xiàn)文件拷貝)
本文介紹java實現(xiàn)文件拷貝的代碼片斷,大家可以直接放到程序里運行2014-01-01mybatis中一對一關(guān)系association標簽的使用
這篇文章主要介紹了mybatis中一對一關(guān)系association標簽的使用,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03