在本教程中,我們將構(gòu)建一個(gè)Spring Boot應(yīng)用程序,該應(yīng)用程序演示如何使用MongoTemplate API訪(fǎng)問(wèn)MongoDB數(shù)據(jù)庫(kù)中的數(shù)據(jù)。
對(duì)于MongoDB臭增,我們將使用mLab,它提供了MongoDB數(shù)據(jù)庫(kù)即服務(wù)平臺(tái)竹习,因此您甚至不必在計(jì)算機(jī)上安裝MongoDB數(shù)據(jù)庫(kù)誊抛。
配置
為了快速設(shè)置我們的項(xiàng)目,我們將使用一個(gè)稱(chēng)為Spring Initializr的工具整陌。使用此工具拗窃,我們可以快速提供所需的依賴(lài)項(xiàng)列表并下載引導(dǎo)程序:
使用Spring Initializr創(chuàng)建新的Spring Boot項(xiàng)目時(shí)瞎领,僅選擇兩個(gè)依賴(lài)項(xiàng):
- Web (Spring Boot Starter Web)
- MongoDB (MongoDB Starter)
Maven Dependencies
當(dāng)下載使用Spring Initializr生成的項(xiàng)目并打開(kāi)其pom.xml文件時(shí),您應(yīng)該看到添加了以下依賴(lài)項(xiàng):
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency></dependencies><build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins></build>
項(xiàng)目結(jié)構(gòu)
在繼續(xù)進(jìn)行并開(kāi)始處理該項(xiàng)目的代碼之前随夸,讓我們介紹一下完成所有代碼添加到項(xiàng)目后將擁有的項(xiàng)目結(jié)構(gòu):
該代碼以多個(gè)程序包進(jìn)行組織九默,因此遵循關(guān)注點(diǎn)分離的原則,并且代碼保持模塊化宾毒。
創(chuàng)建數(shù)據(jù)庫(kù)
對(duì)于MongoDB驼修,我們將使用mLab。您可以使用mLab創(chuàng)建一個(gè)免費(fèi)帳戶(hù)诈铛,并在云中使用MongoDB乙各,而無(wú)需在計(jì)算機(jī)上下載和安裝它。
登錄到mLab后幢竹,您將看到一個(gè)類(lèi)似的儀表板:
要?jiǎng)?chuàng)建一個(gè)新的MongoDB數(shù)據(jù)庫(kù)耳峦,請(qǐng)單擊創(chuàng)建新按鈕:
輸入所有詳細(xì)信息后,我們可以確認(rèn):
完成此操作后妨退,我們將看到一個(gè)連接字符串妇萄,如下圖所示:
在開(kāi)始使用此數(shù)據(jù)庫(kù)之前,我們還需要?jiǎng)?chuàng)建一個(gè)用戶(hù)咬荷。讓我們?cè)谏蠄D所示的“用戶(hù)”標(biāo)簽上執(zhí)行此操作:
現(xiàn)在,我們已經(jīng)準(zhǔn)備好繼續(xù)寫(xiě)些Java代碼轻掩,因?yàn)槲覀兊膍Lab數(shù)據(jù)庫(kù)已經(jīng)完全準(zhǔn)備就緒幸乒。
應(yīng)用配置
使用Spring Boot,僅使用MongoDB連接String的單個(gè)必需屬性即可輕松配置我們的應(yīng)用程序:
# application propertiesserver.port=8090# MongoDB propertiesspring.data.mongodb.uri=mongodb://cicoding.cn:password@ds915721.mlab.com:29670/cicoding_db
我們只是提供了一個(gè)MongoDB連接字符串唇牧,該字符串將由Spring Boot讀取罕扎,并且將使用內(nèi)部API建立連接。
建立模型
我們將創(chuàng)建一個(gè)簡(jiǎn)單的Person實(shí)體丐重,其中包含一些字段腔召,我們將使用它們來(lái)演示簡(jiǎn)單的MongoDB查詢(xún):
package com.cicoding.mongotemplatedemo.model;import org.springframework.data.annotation.Id;import org.springframework.data.mongodb.core.mapping.Document;import java.util.Calendar;import java.util.Date;import java.util.List;import java.util.Locale;import static java.util.Calendar.DATE;import static java.util.Calendar.MONTH;import static java.util.Calendar.YEAR;@Document(collection = "person")public class Person { @Id private String personId; private String name; private long age; private List<String> favoriteBooks; private Date dateOfBirth; public Person() { } public Person(String name, List<String> childrenName, Date dateOfBirth) { this.name = name; this.favoriteBooks = childrenName; this.dateOfBirth = dateOfBirth; this.age = getDiffYears(dateOfBirth, new Date()); } // standard getters and setters private int getDiffYears(Date first, Date last) { Calendar a = getCalendar(first); Calendar b = getCalendar(last); int diff = b.get(YEAR) - a.get(YEAR); if (a.get(MONTH) > b.get(MONTH) || (a.get(MONTH) == b.get(MONTH) && a.get(DATE) > b.get(DATE))) { diff--; } return diff; } private Calendar getCalendar(Date date) { Calendar cal = Calendar.getInstance(Locale.US); cal.setTime(date); return cal; } @Override public String toString() { return String.format("Person{personId='%s', name='%s', age=%d, dateOfBirth=%s}\n", personId, name, age, dateOfBirth); }}
除了簡(jiǎn)單的字段外,我們還添加了一些幫助程序功能扮惦,可以在保存用戶(hù)的出生日期時(shí)計(jì)算該用戶(hù)的年齡臀蛛。這使我們不必計(jì)算用戶(hù)的年齡。
定義數(shù)據(jù)訪(fǎng)問(wèn)層接口
讓我們定義一個(gè)數(shù)據(jù)層接口崖蜜,該接口將通知我們?cè)趹?yīng)用程序中將演示多少操作浊仆。這是界面:
public interface PersonDAL { Person savePerson(Person person); List<Person> getAllPerson(); List<Person> getAllPersonPaginated( int pageNumber, int pageSize); Person findOneByName(String name); List<Person> findByName(String name); List<Person> findByBirthDateAfter(Date date); List<Person> findByAgeRange(int lowerBound, int upperBound); List<Person> findByFavoriteBooks(String favoriteBook); void updateMultiplePersonAge(); Person updateOnePerson(Person person); void deletePerson(Person person);}
這些是相當(dāng)多的操作。真正的樂(lè)趣是當(dāng)我們執(zhí)行這些操作時(shí)豫领,接下來(lái)將要做的事情抡柿。
實(shí)施數(shù)據(jù)訪(fǎng)問(wèn)層
我們將使用MongoTemplate bean,它是由Spring Boot使用上面在application.properties中定義的屬性初始化的等恐。讓我們看看如何定義所需的bean:
@Repositorypublic class PersonDALImpl implements PersonDAL { private final MongoTemplate mongoTemplate; @Autowired public PersonDALImpl(MongoTemplate mongoTemplate) { this.mongoTemplate = mongoTemplate; } ...}
我們將開(kāi)始使用簡(jiǎn)單的方法來(lái)理解查詢(xún)洲劣,首先是要保存并從數(shù)據(jù)庫(kù)中獲取所有人員:
@Overridepublic Person savePerson(Person person) { mongoTemplate.save(person); return person;}@Overridepublic List<Person> getAllPerson() { return mongoTemplate.findAll(Person.class);}
MongoTemplate為我們提供了一些抽象方法备蚓,通過(guò)這些方法我們可以將對(duì)象保存到數(shù)據(jù)庫(kù)中,也可以從數(shù)據(jù)庫(kù)中獲取所有數(shù)據(jù)囱稽。
使用分頁(yè)查詢(xún)
上述從數(shù)據(jù)庫(kù)中獲取所有人的方法的問(wèn)題在于星著,數(shù)據(jù)庫(kù)中可能有數(shù)千個(gè)對(duì)象。 我們應(yīng)該始終在查詢(xún)中實(shí)現(xiàn)分頁(yè)粗悯,以便可以確保僅從數(shù)據(jù)庫(kù)中提取有限的數(shù)據(jù):
@Overridepublic List<Person> getAllPersonPaginated(int pageNumber, int pageSize) { Query query = new Query(); query.skip(pageNumber * pageSize); query.limit(pageSize); return mongoTemplate.find(query, Person.class);}
這樣虚循,一次將僅從數(shù)據(jù)庫(kù)中獲取pageSize個(gè)對(duì)象。
通過(guò)精確值獲取對(duì)象
我們也可以通過(guò)匹配數(shù)據(jù)庫(kù)中的精確值來(lái)提取對(duì)象:
@Overridepublic Person findOneByName(String name) { Query query = new Query(); query.addCriteria(Criteria.where("name").is(name)); return mongoTemplate.findOne(query, Person.class);}@Overridepublic List<Person> findByName(String name) { Query query = new Query(); query.addCriteria(Criteria.where("name").is(name)); return mongoTemplate.find(query, Person.class);}
我們展示了兩種方法样傍。第一種方法是從數(shù)據(jù)庫(kù)中獲取單個(gè)對(duì)象横缔,而第二種方法是從數(shù)據(jù)庫(kù)中獲取具有匹配條件的所有對(duì)象。
按范圍和數(shù)據(jù)列表查找
我們還可以找到具有指定范圍內(nèi)的字段值的對(duì)象衫哥【ジ眨或具有特定日期之后的日期數(shù)據(jù)的對(duì)象。讓我們看看如何做到這一點(diǎn)撤逢,以及如何構(gòu)造相同的查詢(xún):
@Overridepublic List<Person> findByBirthDateAfter(Date date) { Query query = new Query(); query.addCriteria(Criteria.where("dateOfBirth").gt(date)); return mongoTemplate.find(query, Person.class);}@Overridepublic List<Person> findByAgeRange(int lowerBound, int upperBound) { Query query = new Query(); query.addCriteria(Criteria.where("age").gt(lowerBound) .andOperator(Criteria.where("age").lt(upperBound))); return mongoTemplate.find(query, Person.class);}@Overridepublic List<Person> findByFavoriteBooks(String favoriteBook) { Query query = new Query(); query.addCriteria(Criteria.where("favoriteBooks").in(favoriteBook)); return mongoTemplate.find(query, Person.class);}
更新對(duì)象
我們可以使用Update查詢(xún)更新MongoDB中的數(shù)據(jù)膛锭。我們可以找到一個(gè)對(duì)象,然后自己更新提供的字段:
@Overridepublic void updateMultiplePersonAge() { Query query = new Query(); Update update = new Update().inc("age", 1); mongoTemplate.findAndModify(query, update, Person.class);;}@Overridepublic Person updateOnePerson(Person person) { mongoTemplate.save(person); return person;}
在第一個(gè)查詢(xún)中蚊荣,由于沒(méi)有向查詢(xún)添加任何條件初狰,因此我們收到了所有對(duì)象。接下來(lái)互例,我們提供了一個(gè)Update子句奢入,其中所有用戶(hù)的年齡都增加了一個(gè)。
刪除對(duì)象
刪除對(duì)象也與單個(gè)方法調(diào)用有關(guān):
@Overridepublic void deletePerson(Person person) { mongoTemplate.remove(person);}
我們也可以簡(jiǎn)單地傳遞Query對(duì)象以及要?jiǎng)h除的人的ID媳叨。
制作命令行運(yùn)行器
我們將在適當(dāng)?shù)奈恢檬褂妹钚羞\(yùn)行程序來(lái)運(yùn)行我們的應(yīng)用程序腥光,該命令行運(yùn)行程序?qū)⑻峁┪覀冊(cè)谏厦娴臄?shù)據(jù)訪(fǎng)問(wèn)層實(shí)現(xiàn)中定義的一些功能。這是命令行運(yùn)行器:
package com.cicoding.mongotemplatedemo;import com.cicoding.mongotemplatedemo.dal.PersonDAL;import com.cicoding.mongotemplatedemo.model.Person;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.CommandLineRunner;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import java.util.Arrays;import java.util.Date;@SpringBootApplicationpublic class MongoTemplateApp implements CommandLineRunner { private static final Logger LOG = LoggerFactory.getLogger("cicoding"); private final PersonDAL personDAL; @Autowired public MongoTemplateApp(PersonDAL personDAL) { this.personDAL = personDAL; } public static void main(String[] args) { SpringApplication.run(MongoTemplateApp.class, args); } @Override public void run(String... args) { personDAL.savePerson(new Person( "Shubham", Arrays.asList("Harry potter", "Waking Up"), new Date(769372200000L))); personDAL.savePerson(new Person( "Sergey", Arrays.asList("Startup Guides", "Java"), new Date(664309800000L))); personDAL.savePerson(new Person( "David", Arrays.asList("Harry potter", "Success"), new Date(695845800000L))); personDAL.savePerson(new Person( "Ivan", Arrays.asList("Secrets of Butene", "Meeting Success"), new Date(569615400000L))); personDAL.savePerson(new Person( "Sergey", Arrays.asList("Harry potter", "Startup Guides"), new Date(348777000000L))); LOG.info("Getting all data from MongoDB: \n{}", personDAL.getAllPerson()); LOG.info("Getting paginated data from MongoDB: \n{}", personDAL.getAllPersonPaginated(0, 2)); LOG.info("Getting person By name 'Sergey': {}", personDAL.findByName("Sergey")); LOG.info("Getting all person By name 'Sergey': {}", personDAL.findOneByName("Sergey")); LOG.info("Getting people between age 22 & 26: {}", personDAL.findByAgeRange(22, 26)); }}
我們可以使用一個(gè)簡(jiǎn)單的命令來(lái)運(yùn)行我們的應(yīng)用程序:
mvn spring-boot:run
運(yùn)行應(yīng)用程序后糊秆,我們將能夠在終端中看到一個(gè)簡(jiǎn)單的輸出:
結(jié)論
如果我們將MongoTemplate與簡(jiǎn)單的Spring Data JPA進(jìn)行比較武福,它看起來(lái)可能很復(fù)雜,但是它也使我們對(duì)如何構(gòu)造查詢(xún)有更多的控制痘番。
Spring Data JPA過(guò)多地提取了有關(guān)構(gòu)造什么查詢(xún)捉片,將哪些條件傳遞給查詢(xún)等的詳細(xì)信息。 使用MongoTemplate夫偶,我們可以對(duì)查詢(xún)組成進(jìn)行更細(xì)粒度的控制界睁,Spring Boot為我們提供了易于開(kāi)發(fā)的應(yīng)用程序。