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