javascript
Spring Boot MongoDB 入门
1. 概述
可能有一些胖友對 MongoDB 不是很了解,這里我們引用一段介紹:
FROM 《分布式文檔存儲數據庫 MongoDB》
MongoDB 是一個介于關系數據庫和非關系數據庫之間的產品,是非關系數據庫當中功能最豐富,最像關系數據庫的。
他支持的數據結構非常松散,是類似 json 的 bjson 格式,因此可以存儲比較復雜的數據類型。
Mongo 最大的特點是他支持的查詢語言非常強大,其語法有點類似于面向對象的查詢語言,幾乎可以實現類似關系數據庫單表查詢的絕大部分功能,而且還支持對數據建立索引。
MongoDB 中的許多概念在 MySQL 中具有相近的類比。本表概述了每個系統中的一些常見概念。
對于不熟悉的胖友,可以先看下該表,然后開始本文的旅程。
| 庫 Database | 庫 Database |
| 表 Table | 集合 Collection |
| 行 Row | 文檔 Document |
| 列 Column | 字段 Field |
| joins | 嵌入文檔或者鏈接 |
在早期,在項目中 MongoDB 的 ORM 框架使用 Morphia 較多。隨著 Spring Data MongoDB 的日趨完善,更為主流。目前,艿艿手頭所有的項目,都從 Morphia 該用 Spring Data MongoDB 。
在 Spring Data MongoDB 中,有兩種方式進行 MongoDB 操作:
-
Spring Data Repository 方式
-
MongoTemplate
艿艿:如果胖友還沒安裝 MongoDB ,可以參考下 《芋道 MongoDB 安裝部署》 文章,先進行下安裝。
2. 快速入門
示例代碼對應倉庫:lab-16-spring-data-mongodb 。
-
MongoDB 版本號:4.2.1
本小節,我們會使用?spring-boot-starter-data-mongodb?自動化配置 Spring Data MongoDB 主要配置。同時,使用?Spring Data Repository?實現的 MongoDB 的 CRUD 操作。
2.1 引入依賴
在?pom.xml?文件中,引入相關依賴。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.3.RELEASE</version><relativePath/><!-- lookup parent from repository --></parent><modelVersion>4.0.0</modelVersion><artifactId>lab-16-spring-data-mongodb</artifactId><dependencies><!-- 自動化配置 Spring Data Mongodb --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId></dependency><!-- 方便等會寫單元測試 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies></project>具體每個依賴的作用,胖友自己認真看下艿艿添加的所有注釋噢。
2.2 Application
創建?Application.java?類,配置?@SpringBootApplication?注解即可。代碼如下:
// Application.java@SpringBootApplication(exclude = {ElasticsearchAutoConfiguration.class, ElasticsearchDataAutoConfiguration.class}) publicclass Application { }2.3 MongoDBConfig
在?cn.iocoder.springboot.lab16.springdatamongodb.config?包路徑下,創建 MongoDBConfig 配置類。代碼如下:
// MongoDBConfig.java@Configuration publicclass MongoDBConfig {@Bean// 目的,就是為了移除 _class field 。參考博客 https://blog.csdn.net/bigtree_3721/article/details/82787411public MappingMongoConverter mappingMongoConverter(MongoDbFactory factory,MongoMappingContext context,BeanFactory beanFactory) {// 創建 DbRefResolver 對象DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);// 創建 MappingMongoConverter 對象MappingMongoConverter mappingConverter = new MappingMongoConverter(dbRefResolver, context);// 設置 conversions 屬性try {mappingConverter.setCustomConversions(beanFactory.getBean(CustomConversions.class));} catch (NoSuchBeanDefinitionException ignore) {}// 設置 typeMapper 屬性,從而移除 _class field 。mappingConverter.setTypeMapper(new DefaultMongoTypeMapper(null));return mappingConverter;}}-
通過在自定義 MappingMongoConverter Bean 對象,避免實體保存到 MongoDB 中時,會多一個?_class?字段,存儲實體的全類名。
2.4 配置文件
在?application.yml?中,添加 MongoDB 配置,如下:
spring:data:# MongoDB 配置項,對應 MongoProperties 類mongodb:host:127.0.0.1port:27017database:yourdatabaseusername:test01password:password01# 上述屬性,也可以只配置 urilogging:level:org:springframework:data:mongodb:core:DEBUG# 打印 mongodb 操作的具體語句。生產環境下,不建議開啟。2.5 UserDO
在?cn.iocoder.springboot.lab16.springdatamongodb.dataobject?包路徑下,創建 UserDO 類。代碼如下:
@Document(collection = "User") publicclass UserDO {/*** 用戶信息*/publicstaticclass Profile {/*** 昵稱*/private String nickname;/*** 性別*/private Integer gender;// ... 省略 setting/getting 方法}@Idprivate Integer id;/*** 賬號*/private String username;/*** 密碼*/private String password;/*** 創建時間*/private Date createTime;/*** 用戶信息*/private Profile profile;// ... 省略 setting/getting 方法}-
在 UserDO 類中,我們內嵌了一個?profile?屬性,它是 Profile 類。這里僅僅作為示例,實際場景下,還是建議把 User 和 Profile 拆分開。
-
推薦閱讀 《你應該知道的 MongoDB 最佳實踐》 文章。對于初用 MongoDB 的開發者,往往錯誤的使用內嵌屬性,需要去理解一下。
2.6 UserRepository
在?cn.iocoder.springboot.lab16.springdatamongodb.repository?包路徑下,創建 UserRepository 接口。代碼如下:
// ProductRepository.javapublicinterface UserRepository extends MongoRepository<UserDO, Integer> { }-
繼承?org.springframework.data.mongodb.repository.MongoRepository?接口,第一個泛型設置對應的實體是 UserDO ,第二個泛型設置對應的主鍵類型是 Integer 。
-
因為實現了 MongoRepository 接口,Spring Data MongoDB 會自動生成對應的 CRUD 等等的代碼。😈 是不是很方便。
-
MongoRepository 類圖如下:
-
每個接口定義的方法,胖友可以點擊下面每個鏈接,自己瞅瞅,簡單~
-
org.springframework.data.repository.CrudRepository
-
org.springframework.data.repository.PagingAndSortingRepository
-
org.springframework.data.repository.query.QueryByExampleExecutor?:定義基于?org.springframework.data.domain.Example.Example?的查詢操作。在 「4. 基于 Example 查詢」 中,我們再來詳細介紹。
-
org.springframework.data.mongodb.repository.MongoRepository?:主要重寫 PagingAndSortingRepository 和 CrudRepository 接口,將 findXXX 方法返回的結果從 Iterable 放大成 List ,同時增加 insert 插入方法。
-
艿艿:如果胖友看過艿艿寫的 《芋道 Spring Boot JPA 入門》 文章,會發現和 Spring Data JPA 的使用方式,基本一致。這就是 Spring Data 帶給我們的好處,使用相同的 API ,統一訪問不同的數據源。o( ̄▽ ̄)d 點贊。
2.7 簡單測試
創建 UserRepositoryTest 測試類,我們來測試一下簡單的 UserRepositoryTest 的每個操作。代碼如下:
// UserRepositoryTest.java@RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class) publicclass UserRepositoryTest {@Autowiredprivate UserRepository userRepository;@Test// 插入一條記錄public void testInsert() {// 創建 UserDO 對象UserDO user = new UserDO();user.setId(1); // 這里先臨時寫死一個 ID 編號,后面演示自增 ID 的時候,在修改這塊user.setUsername("yudaoyuanma");user.setPassword("buzhidao");user.setCreateTime(new Date());// 創建 Profile 對象UserDO.Profile profile = new UserDO.Profile();profile.setNickname("芋道源碼");profile.setGender(1);user.setProfile(profile);// 存儲到 DBuserRepository.insert(user);}// 這里要注意,如果使用 save 方法來更新的話,必須是全量字段,否則其它字段會被覆蓋。// 所以,這里僅僅是作為一個示例。@Test// 更新一條記錄public void testUpdate() {// 查詢用戶Optional<UserDO> userResult = userRepository.findById(1);Assert.isTrue(userResult.isPresent(), "用戶一定要存在");// 更新UserDO updateUser = userResult.get();updateUser.setUsername("yutou");userRepository.save(updateUser);}@Test// 根據 ID 編號,刪除一條記錄public void testDelete() {userRepository.deleteById(1);}@Test// 根據 ID 編號,查詢一條記錄public void testSelectById() {Optional<UserDO> userDO = userRepository.findById(1);System.out.println(userDO.isPresent());}@Test// 根據 ID 編號數組,查詢多條記錄public void testSelectByIds() {Iterable<UserDO> users = userRepository.findAllById(Arrays.asList(1, 4));users.forEach(System.out::println);}}-
每個測試單元方法,胖友自己看看方法上的注釋。
具體的,胖友可以自己跑跑,妥妥的。
3. 基于方法名查詢
示例代碼對應倉庫:lab-16-spring-data-mongodb 。
在 《芋道 Spring Boot JPA 入門》 文章的「4. 基于方法名查詢」小節中,我們已經提到:
在 Spring Data 中,支持根據方法名作生成對應的查詢(WHERE)條件,進一步進化我們使用 JPA ,具體是方法名以?findBy、existsBy、countBy、deleteBy?開頭,后面跟具體的條件。具體的規則,在 《Spring Data JPA —— Query Creation》 文檔中,已經詳細提供。如下:
| And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
| Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
| Is,?Equals | findByFirstname,findByFirstnameIs,findByFirstnameEquals | … where x.firstname = ?1 |
| Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
| LessThan | findByAgeLessThan | … where x.age < ?1 |
| LessThanEqual | findByAgeLessThanEqual | … where x.age <= ?1 |
| GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
| GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
| After | findByStartDateAfter | … where x.startDate > ?1 |
| Before | findByStartDateBefore | … where x.startDate < ?1 |
| IsNull,?Null | findByAge(Is)Null | … where x.age is null |
| IsNotNull,?NotNull | findByAge(Is)NotNull | … where x.age not null |
| Like | findByFirstnameLike | … where x.firstname like ?1 |
| NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
| StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1?(parameter bound with appended?%) |
| EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1?(parameter bound with prepended?%) |
| Containing | findByFirstnameContaining | … where x.firstname like ?1?(parameter bound wrapped in?%) |
| OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
| Not | findByLastnameNot | … where x.lastname <> ?1 |
| In | findByAgeIn(Collection ages) | … where x.age in ?1 |
| NotIn | findByAgeNotIn(Collection ages) | … where x.age not in ?1 |
| True | findByActiveTrue() | … where x.active = true |
| False | findByActiveFalse() | … where x.active = false |
| IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstame) = UPPER(?1) |
-
注意,如果我們有排序需求,可以使用?OrderBy?關鍵字。
下面,我們來編寫一個簡單的示例。
艿艿:IDEA 牛逼,提供的插件已經能夠自動提示上述關鍵字。太強了~
3.1 UserRepository02
在?cn.iocoder.springboot.lab16.springdatamongodb.repository?包路徑下,創建 UserRepository02 接口。代碼如下:
// UserRepository02.javapublicinterface UserRepository02 extends MongoRepository<UserDO, Integer> {UserDO findByUsername(String username);Page<UserDO> findByUsernameLike(String username, Pageable pageable);}-
對于分頁操作,需要使用到 Pageable 參數,需要作為方法的最后一個參數。
-
注意噢,基于方法名查詢,不支持內嵌對象的屬性。(⊙o⊙)… 至少在網上翻了一波資料,沒有提及這塊。
3.2 簡單測試
創建 UserRepository02Test 測試類,我們來測試一下簡單的 UserRepository02Test 的每個操作。代碼如下:
// UserRepository02Test.java@RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class) publicclass UserRepository02Test {@Autowiredprivate UserRepository02 userRepository;@Test// 根據名字獲得一條記錄public void testFindByName() {UserDO user = userRepository.findByUsername("yutou");System.out.println(user);}@Test// 使用 username 模糊查詢,分頁返回結果public void testFindByNameLike() {// 創建排序條件Sort sort = new Sort(Sort.Direction.DESC, "id"); // ID 倒序// 創建分頁條件。Pageable pageable = PageRequest.of(0, 10, sort);// 執行分頁操作Page<UserDO> page = userRepository.findByUsernameLike("yu", pageable);// 打印System.out.println(page.getTotalElements());System.out.println(page.getTotalPages());}}-
每個測試單元方法,胖友自己看看方法上的注釋。
具體的,胖友可以自己跑跑,妥妥的。
4. 基于 Example 查詢
示例代碼對應倉庫:lab-16-spring-data-mongodb 。
實際場景下,我們并不會基于 Example 查詢。所以本小節,胖友可以選擇性看看即可。
對于大多數胖友,可能不了解 Spring Data Example 。我們先來一起看看官方文檔的介紹:
Query by Example (QBE) is a user-friendly querying technique with a simple interface. 使用 Example 進行查詢,是一種友好的查詢方式,可以使用便捷的 API 方法。
It allows dynamic query creation and does not require to write queries containing field names. 它允許創建動態查詢,而無需編寫包含字段名的查詢。
In fact, Query by Example does not require to write queries using store-specific query languages at all. 事實上,在使用 Example 進行查詢的時候,我們無需使用特定的存儲器(數據庫)的查詢語言。
-
請原諒艿艿蹩腳的翻譯。簡單來說,我們可以通過 Example 進行編寫動態的查詢條件,而無需使用每個不同的 Spring Data 實現類的 Query 對象。例如說:
-
Spring Data JPA 提供的?javax.persistence.criteria.Predicate
-
Spring Data MongoDB 提供的?org.springframework.data.mongodb.core.query.Query?。
-
-
相當于說,不同 Spring Data 實現框架,會將 Spring Data Example 條件,翻譯成對應的查詢對象。例如說:
-
Spring Data JPA 將 Example 轉換成 Predicate 。
-
Spring Data MongoDB 將 Example 轉換成 Query 。
-
-
當然,并不是所有 Spring Data 實現都支持 Spring Data Example 。例如說,Spring Data Elasticsearch 并不支持。一般情況下,支持 Spring Data Example 的 Spring Data 實現框架,它們的 Repository 都繼承了?org.springframework.data.repository.query.QueryByExampleExecutor?接口。例如說:
-
Spring Data JPA 的 JpaRepository 接口。
-
Spring Data MongoDB 的 MongoRepository 接口。
-
Example API 一共包含三部分:
-
Probe :含有對應字段的實體對象。通過設置該實體對象的字段,作為查詢字段。
注意,Probe 并不是一個類,而是實體對象的泛指。
-
ExampleMatcher :ExampleMatcher 可以定義特定字段的匹配模式。例如說,全模糊匹配、前綴模糊匹配等等。
簡單來說,通過實體對象的字段作為查詢條件,只能滿足相等的情況,對于?!=?、LIKE?等等情況,需要通過 ExampleMatcher 特殊指定。如果不理解,沒事,看了示例會更容易明白。
-
Example :是 Probe 和 ExampleMatcher 的組合,構成查詢對象。
當然,Example 有它的限制條件:
No support for nested/grouped property constraints like firstname = ?0 or (firstname = ?1 and lastname = ?2) 不支持嵌套或分組約束。例如說,firstname = ?0 or (firstname = ?1 and lastname = ?2)?。
Only supports starts/contains/ends/regex matching for strings and exact matching for other property types 對于字符串,只支持 tarts/contains/ends/regex 匹配方式;對于其它類型,只支持精確匹配。
4.1 UserRepository03
在?cn.iocoder.springboot.lab16.springdatamongodb.repository?包路徑下,創建 UserRepository02 接口。代碼如下:
// UserRepository03.javapublicinterface UserRepository03 extends MongoRepository<UserDO, Integer> {// 使用 username 精準匹配default UserDO findByUsername01(String username) {// 創建 Example 對象,使用 username 查詢UserDO probe = new UserDO();probe.setUsername(username); // 精準匹配 username 查詢Example<UserDO> example = Example.of(probe);// 執行查詢return findOne(example).orElse(null); // 如果為空,則返回 null}// 使用 username 模糊匹配default UserDO findByUsernameLike01(String username) {// 創建 Example 對象,使用 username 查詢UserDO probe = new UserDO();probe.setUsername(username); // 這里還需要設置ExampleMatcher matcher = ExampleMatcher.matching().withMatcher("username", ExampleMatcher.GenericPropertyMatchers.contains()); // 模糊匹配 username 查詢Example<UserDO> example = Example.of(probe, matcher);// 執行查詢return findOne(example).orElse(null); // 如果為空,則返回 null}}4.2 簡單測試
創建 UserRepository03Test 測試類,我們來測試一下簡單的 UserRepository03Test 的每個操作。代碼如下:
// UserRepository03Test.java@RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class) publicclass UserRepository03Test {@Autowiredprivate UserRepository03 userRepository;@Testpublic void testFindByUsername01() {UserDO user = userRepository.findByUsername01("yutou");System.out.println(user);}@Testpublic void testFindByUsernameLike01() {UserDO user = userRepository.findByUsernameLike01("yu");System.out.println(user);}}-
每個測試單元方法,胖友自己看看方法上的注釋。
具體的,胖友可以自己跑跑,妥妥的。更多示例,可以看看如下文章:
-
《Spring Data JPA Query by Example》
-
《Spring Data JPA 使用 Example 快速實現動態查詢》
5. MongoTemplate
示例代碼對應倉庫:lab-16-spring-data-mongodb 。
在 Spring Data MongoDB 中,有一個 MongoTemplate 類,提供了 MongoDB 操作模板,方便我們操作 MongoDB 。
5.1 UserDao
在?cn.iocoder.springboot.lab16.springdatamongodb.dao?包路徑下,創建 UserDao 類。代碼如下:
// UserDao.java@Repository publicclass UserDao {@Autowiredprivate MongoTemplate mongoTemplate;public void insert(UserDO entity) {mongoTemplate.insert(entity);}public void updateById(UserDO entity) {// 生成 Update 條件final Update update = new Update();// 反射遍歷 entity 對象,將非空字段設置到 Update 中ReflectionUtils.doWithFields(entity.getClass(), new ReflectionUtils.FieldCallback() {@Overridepublic void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {// 排除指定條件if ("id".equals(field.getName()) // 排除 id 字段,因為作為查詢主鍵|| field.getAnnotation(Transient.class) != null// 排除 @Transient 注解的字段,因為非存儲字段|| Modifier.isStatic(field.getModifiers())) { // 排除靜態字段return;}// 設置字段可反射if (!field.isAccessible()) {field.setAccessible(true);}// 排除字段為空的情況if (field.get(entity) == null) {return;}// 設置更新條件update.set(field.getName(), field.get(entity));}});// 防御,避免有業務傳遞空的 Update 對象if (update.getUpdateObject().isEmpty()) {return;}// 執行更新mongoTemplate.updateFirst(new Query(Criteria.where("_id").is(entity.getId())), update, UserDO.class);}public void deleteById(Integer id) {mongoTemplate.remove(new Query(Criteria.where("_id").is(id)), UserDO.class);}public UserDO findById(Integer id) {return mongoTemplate.findOne(new Query(Criteria.where("_id").is(id)), UserDO.class);}public UserDO findByUsername(String username) {return mongoTemplate.findOne(new Query(Criteria.where("username").is(username)), UserDO.class);}public List<UserDO> findAllById(List<Integer> ids) {return mongoTemplate.find(new Query(Criteria.where("_id").in(ids)), UserDO.class);}}-
使用 MongoTemplate 實現了 CRUD 的功能。其它 findAndModify/findAndRemove/count/upsert 等操作,胖友可以自己嘗試下。
-
對于?#updateById(UserDO entity)?方法,胖友可以自己抽象成一個通用的?#updateEntityFieldsById(Object id, Object entity)?方法,封裝在一個 BaseMongoDao 抽象類中。
友情提示:此處暫時有個問題,對于 UserDO 內嵌的?profile?對象,一旦設置了值,是整個 Profile 對象覆蓋更新。所以,使用時需要注意下。
目前艿艿自己項目里,大多數內嵌對象,全量更新不存在問題。如果存在問題的,提供了另外的方法解決。
當然,也可以進一步封裝?#updateEntityFieldsById(Object id, Object entity, String... fields)?方法,只更新指定字段。
5.2 簡單測試
創建 UserDaoTest 測試類,我們來測試一下簡單的 UserDaoTest 的每個操作。代碼如下:
// UserDaoTest.java@RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class) publicclass UserDaoTest {@Autowiredprivate UserDao userDao;@Test// 插入一條記錄public void testInsert() {// 創建 UserDO 對象UserDO user = new UserDO();user.setId(1); // 這里先臨時寫死一個 ID 編號,后面演示自增 ID 的時候,在修改這塊user.setUsername("yudaoyuanma");user.setPassword("buzhidao");user.setCreateTime(new Date());// 創建 Profile 對象UserDO.Profile profile = new UserDO.Profile();profile.setNickname("芋道源碼");profile.setGender(1);user.setProfile(profile);// 存儲到 DBuserDao.insert(user);}// 這里要注意,如果使用 save 方法來更新的話,必須是全量字段,否則其它字段會被覆蓋。// 所以,這里僅僅是作為一個示例。@Test// 更新一條記錄public void testUpdate() {// 創建 UserDO 對象UserDO updateUser = new UserDO();updateUser.setId(1);updateUser.setUsername("nicai");// 執行更新userDao.updateById(updateUser);}@Test// 根據 ID 編號,刪除一條記錄public void testDelete() {userDao.deleteById(1);}@Test// 根據 ID 編號,查詢一條記錄public void testSelectById() {UserDO userDO = userDao.findById(1);System.out.println(userDO);}@Test// 根據 ID 編號數組,查詢多條記錄public void testSelectByIds() {List<UserDO> users = userDao.findAllById(Arrays.asList(1, 4));users.forEach(System.out::println);}}-
每個測試單元方法,胖友自己看看方法上的注釋。
具體的,胖友可以自己跑跑,妥妥的。
6. 自增主鍵
示例代碼對應倉庫:lab-16-spring-data-mongodb 。
MongoDB 自帶的主鍵選擇是 ObjectId 類型,需要占用 12 字節。相比 Int 4 字節或者 Long 8 字節占用更多的內存空間。而絕大多數業務場景下,Int 或 Long 足夠使用,所以我們更加偏向使用 Int 或 Long 作為自增 ID 主鍵。
當然,我們在日志記錄上,我們還是采用 ObjectId 為主。
我們在實現 MongoDB 自增主鍵時,會創建一個名為?"sequence"?的集合。字段如下:
"sequence"?集合名,也可以取其它名字,看自己喜好。
-
_id?:String 類型,集合的實體類名。
-
value?:Long 類型,存儲每個集合的自增序列。
在程序中,每次插入實體對象到 MongoDB 之前,通過 $inc 操作,從?"sequence"?自增獲得最新的 ID ,然后將該 ID 賦值給實體對象,最終在插入到 MongoDB 之中。
當然,考慮到并不是所有實體都需要使用自增 ID ,所以我們要有方式去標記:
-
方式一:創建自定義?@AutoIncKey?注解,添加到 ID 屬性上。
-
方式二:創建 IncIdEntity 抽象基類,這樣需要自增的實體繼承它。
對于方式一,網上博客比較多,胖友可以看看 《Java 中實現 MongoDB 主鍵自增》 文章。
對于方式二,目前艿艿項目采用這種方式,主要是我們自己的 BaseMongoDao 提供的很多方法,是基于 IncIdEntity 抽象類的。例如說,#updateEntityFieldsById(final IncIdEntity entity, String... fields)?方法,這樣可以方便的直接獲取到 ID 屬性。
當然,無論方式一還是方式二,實現原理是一致的,差異僅僅在于獲取 ID 屬性不同。下面,本小節我們就來實現下方式二。
6.1 IncIdEntity
在?cn.iocoder.springboot.lab16.springdatamongodb.mongo?包路徑下,創建 IncIdEntity 類。代碼如下:
// IncIdEntity.java/*** 自增主鍵實體** @param <T> 主鍵泛型*/ publicabstractclass IncIdEntity<T extends Number> {@Idprivate T id;public T getId() {return id;}public void setId(T id) {this.id = id;}}6.2 ProductDO
在?cn.iocoder.springboot.lab16.springdatamongodb.dataobject?包路徑下,創建 ProductDO 類。代碼如下:
// ProductDO.java@Document(collection = "Product") publicclass ProductDO extends IncIdEntity<Integer> {/*** 商品名*/private String name;public String getName() {return name;}public ProductDO setName(String name) {this.name = name;returnthis;}}-
繼承 IncIdEntity 抽象類,使用 Integer 自增主鍵。
6.3 MongoInsertEventListener
在?cn.iocoder.springboot.lab16.springdatamongodb.dataobject?包路徑下,創建 MongoInsertEventListener 類。代碼如下:
// MongoInsertEventListener.java@Component publicclass MongoInsertEventListener extends AbstractMongoEventListener<IncIdEntity> {/*** sequence - 集合名*/privatestaticfinal String SEQUENCE_COLLECTION_NAME = "sequence";/*** sequence - 自增值的字段名*/privatestaticfinal String SEQUENCE_FIELD_VALUE = "value";@Autowiredprivate MongoTemplate mongoTemplate;@Overridepublic void onBeforeConvert(BeforeConvertEvent<IncIdEntity> event) {IncIdEntity entity = event.getSource();// 判斷 id 為空if (entity.getId() == null) {// 獲得下一個編號Number id = this.getNextId(entity);// 設置到實體中// noinspection uncheckedentity.setId(id);}}/*** 獲得實體的下一個主鍵 ID 編號** @param entity 實體對象* @return ID 編號*/private Number getNextId(IncIdEntity entity) {// 使用實體名的簡單類名,作為 ID 編號String id = entity.getClass().getSimpleName();// 創建 Query 對象Query query = new Query(Criteria.where("_id").is(id));// 創建 Update 對象Update update = new Update();update.inc(SEQUENCE_FIELD_VALUE, 1); // 自增值// 創建 FindAndModifyOptions 對象FindAndModifyOptions options = new FindAndModifyOptions();options.upsert(true); // 如果不存在時,則進行插入options.returnNew(true); // 返回新值// 執行操作@SuppressWarnings("unchecked")HashMap<String, Object> result = mongoTemplate.findAndModify(query, update, options,HashMap.class, SEQUENCE_COLLECTION_NAME);// 返回主鍵return (Number) result.get(SEQUENCE_FIELD_VALUE);}}-
MongoDB 實體對象在插入之前,會發布 BeforeConvertEvent 事件。所以,我們可以通過創建 MongoInsertEventListener 監聽器,監聽該事件,生成自增主鍵 ID 主鍵,設置到實體對象中。
-
如果胖友想要使用集合名作為?"sequence"?集合的?"id"?,可以使用?BeforeConvertEvent.collectionName?屬性。
6.4 ProductRepository
在?cn.iocoder.springboot.lab16.springdatamongodb.repository?包路徑下,創建 ProductRepository 接口。代碼如下:
// ProductRepository.javapublicinterface ProductRepository extends MongoRepository<ProductDO, Integer> { }6.5 簡單測試
創建 ProductRepositoryTest 測試類,我們來測試一下簡單的 ProductRepositoryTest 的每個操作。代碼如下:
// ProductRepositoryTest.java@RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class) publicclass ProductRepositoryTest {@Autowiredprivate ProductRepository productRepository;@Testpublic void testInsert() {// 創建 ProductDO 對象ProductDO product = new ProductDO();product.setName("芋頭");// 插入productRepository.insert(product);// 打印 IDSystem.out.println(product.getId());}}總結
以上是生活随笔為你收集整理的Spring Boot MongoDB 入门的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Boot 整合 Netty
- 下一篇: 网关Zuul科普