摘要:在本章中,我们将讨论 NoSQL 数据库,特别是 MongoDB 和 Redis。目前有许多 NoSQL 数据库可供选择,但 MongoDB 和 Redis 是 IT 行业中最常用的。Spring 框架的数据访问和 Spring Data 项目支持多种 NoS
在本章中,我们将讨论 NoSQL 数据库,特别是 MongoDB 和 Redis。目前有许多 NoSQL 数据库可供选择,但 MongoDB 和 Redis 是 IT 行业中最常用的。Spring 框架的数据访问和 Spring Data 项目支持多种 NoSQL 数据库,您可以在 Spring Data 网站上找到每种技术的专用页面,包括 SQL 和 NoSQL:https://spring.io/projects/spring-data。
Spring Data MongoDB 实现了 MongoDB 文档类型数据库的 Spring 编程模型。Spring Data MongoDB 的一个重要特性是它提供了 POJO(普通旧 Java 对象)模型,使得通过众所周知的仓库接口与集合进行交互变得更加简单。
以下是 Spring Data MongoDB 的一些主要功能:
通过 javaConfig 类或 XML 配置文件进行全面配置众所周知的 Spring 数据访问中的数据异常管理翻译生命周期中的回调和事件存储库、CrudRepository 和 MongoRepository 接口的实现方式自定义查询方法与 Querydsl 的集成MapReduce 的集成。基于注解的映射元数据(使用@Document 注解指定您的领域类,使用@Id 注解指定标识符或键)MongoTemplate 和 MongoOperations 类,帮助处理连接 MongoDB 和执行操作所需的所有样板代码通过 MongoReader 和 MongoWriter 抽象实现低级映射如果您希望在 Spring Boot 中使用 MongoDB,您需要添加 spring-boot-starter-data-mongodb 启动依赖。Spring Boot 的自动配置会为连接 MongoDB 数据库设置所有必要的默认值,并识别您应用程序所需的所有仓库和领域类。
此自动配置将使用默认 URI mongodb://localhost/test 设置 MongoDatabaseFactory。您可以使用注入的 MongoTemplate 和 MongoOperations 类,如果您想更改这些默认设置,可以通过使用 spring.data.mongodb.* 属性来修改此行为。
基于 Spring Data MongoDB 和 Spring Boot 的用户应用程序
如果你在跟着做,可以重用一些之前版本的代码。或者,你可以从头开始,访问 Spring Initializr (https://start.spring.io) 生成一个基础项目(不带依赖),但请确保将 Group 字段设置为 com.apress,将 Artifact 和 Name 字段设置为 users。下载项目,解压缩后导入到你喜欢的 IDE 中。在本节结束时,你应该能够看到如图 6-1 所示的结构。
图 6-1 使用 Spring Data MongoDB 和 Spring Boot 的用户应用程序
打开 build.gradle 文件,并将其内容替换为列表 6-1 中的内容。
plugins {id 'java'id 'org.springframework.boot' version '3.2.3'id 'io.spring.dependency-management' version '1.1.4'}group = 'com.apress'version = '0.0.1-SNAPSHOT'sourceCompatibility = '17'repositories {mavenCentral}dependencies {implementation 'org.springframework.boot:spring-boot-starter-web'implementation 'org.springframework.boot:spring-boot-starter-validation'implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'developmentOnly 'org.springframework.boot:spring-boot-docker-compose'compileOnly 'org.projectlombok:lombok'annotationProcessor 'org.projectlombok:lombok'// Webimplementation 'org.webjars:bootstrap:5.2.3'testImplementation 'org.springframework.boot:spring-boot-starter-test'}tasks.named('test') {usejunitPlatform}test {testLogging {events "passed", "skipped", "failed"showexceptions trueExceptionFormat "full"showCauses trueshowStackTraces trueshowStandardStreams = false}}6-1 build.gradle
请注意,我们包含了 spring-boot-starter-data-mongodb 和 spring-boot-docker-compose 启动依赖项。Spring Boot 的自动配置功能将使用默认设置来配置所有内容,以便连接到 MongoDB 引擎并执行所需的操作和交互。
接下来,创建或打开用户类;它应该与清单 6-2 相似。
package com.apress.users;import jakarta.validation.constraints.NotBlank;import jakarta.validation.constraints.Pattern;import lombok.*;import org.springframework.data.annotation.Id;import org.springframework.data.mongodb.core.mapping.Document;import java.util.Collection;@Builder@AllArgsConstructor@NoArgsConstructor@Data@Documentpublic class User {@Id@NotBlank(message = "Email can not be empty")private String email;@NotBlank(message = "Name can not be empty")private String name;private String gravatarUrl;@Pattern(message = "Password must be at least 8 characters long and contain at least one number, one uppercase, one lowercase and one special character",regexp = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=!])(?=\\S+$).{8,}$")private String password;@Singular("role")private Collection userRole;private boolean active;}6-2 src/main/java/apress/com/users/User.java
你知道与用户类的前一个版本相比发生了什么变化吗(在 JPA 中,如清单 5-19 所示)?我们现在使用的是属于 org.springframework.data.mongo.*包的@Document 注解(而不是@Entity)。@Document 注解将该类标识为域对象,以便可以将其持久化到 MongoDB 数据库中。实际上就是这样。
接下来,创建或打开 UserRepository 接口。请查看清单 6-3。
package com.apress.users;import org.springframework.data.repository.CrudRepository;public interface UserRepository extends CrudRepository{}6-3 src/main/java/apress/com/users/UserRepository.java
如你所见,这个类没有任何变化,我们依然继承自 CrudRepository 接口。虽然有 MongoRepository,但我们将在 My Retro App 项目中使用它。这里的目的是向你展示,当从一种技术迁移到另一种技术时,你仍然可以轻松使用你的基础。
接下来,创建或打开 UsersController 类。请参阅清单 6-4。
package com.apress.users;import lombok.AllArgsConstructor;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.validation.FieldError;import org.springframework.web.bind.MethodArgumentNotValidException;import org.springframework.web.bind.annotation.*;import org.springframework.web.servlet.support.ServletUriComponentsBuilder;import java.net.URI;import java.util.HashMap;import java.util.Map;@AllArgsConstructor@RestController@RequestMapping("/users")public class UsersController {private UserRepository userRepository;@GetMappingpublic ResponseEntity> getAll{return ResponseEntity.ok(this.userRepository.findAll);}@GetMapping("/{email}")public ResponseEntity findUserById(@PathVariable String email) throws Throwable {return ResponseEntity.of(this.userRepository.findById(email));}@RequestMapping(method = {RequestMethod.POST,RequestMethod.PUT})public ResponseEntity save(@RequestBody User user){this.userRepository.save(user);URI location = ServletUriComponentsBuilder.fromCurrentRequest.path("/{email}").buildAndExpand(user.getEmail).toUri;return ResponseEntity.created(location).body(user);}@DeleteMapping("/{email}")@ResponseStatus(HttpStatus.NO_CONTENT)public void save(@PathVariable String email){this.userRepository.deleteById(email);}@ExceptionHandler(MethodArgumentNotValidException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)public Map handleValidationExceptions(MethodArgumentNotValidException ex) {Map errors = new HashMap;ex.getBindingResult.getAllErrors.forEach((error) -> {String fieldName = ((FieldError) error).getField;String errorMessage = error.getDefaultMessage;errors.put(fieldName, errorMessage);});return errors;}}6-4 src/main/java/apress/com/users/UsersController.java
UsersController 类没有变化,依然和之前的版本一样。不过请注意,我们可以直接使用由 Spring 注入的 UserRepository(作为构造函数的一部分,这要感谢 Lombok 的 @AllArgsConstructor 注解)。
接下来,创建或打开 UserConfiguration 类。请参阅清单 6-5。
package com.apress.users;import org.springframework.boot.context.event.ApplicationReadyEvent;import org.springframework.context.ApplicationListener;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.mongodb.core.mapping.event.BeforeConvertCallback;import java.util.Arrays;@Configurationpublic class UserConfiguration implements BeforeConvertCallback {@BeanApplicationListener init(UserRepository userRepository){return applicationReadyEvent -> {userRepository.save(User.builder.email("ximena@email.com").name("Ximena").password("aw2s0meR!").role(UserRole.USER).active(true).build);userRepository.save(User.builder.email("norma@email.com").name("Norma").password("aw2s0meR!").role(UserRole.USER).role(UserRole.ADMIN).active(true).build);};}@Overridepublic User onBeforeConvert(User entity, String collection) {if (entity.getGravatarUrl==null)entity.setGravatarUrl(UserGravatar.getGravatarUrlFromEmail(entity.getEmail));if (entity.getUserRole == null)entity.setUserRole(Arrays.asList(UserRole.INFO));return entity;}}6-5 src/main/java/apress/com/users/UserConfiguration.java
UserConfiguration 类实现了 BeforeConvertCallback 接口,您可以在实体保存到 MongoDB 数据库之前添加自定义逻辑。您需要实现 onBeforeConvert 方法。请注意,您可以直接在配置类中添加必要的实现,而无需为回调事件创建另一个类。此外,该类使用 ApplicationReadyEvent 初始化了一些文档(与之前的版本相同),并使用 UserRepository 接口。
接下来,创建或打开 docker-compose.yaml 文件。请参阅清单 6-6。
version: "3.1"services:mongo:image: mongorestart: alwaysenvironment:MONGO_INITDB_DATABASE: retrodbports:- "27017:27017"6-6 docker-compose.yaml
docker-compose.yaml 文件确保在运行应用程序时,您的 MongoDB 数据库能够正常工作。请注意,此文件仅用于开发环境(而非测试环境)。
我们在这里已经完成了。UserGravatar 和 UserRole 类没有任何更改。application.properties 文件必须保持空白;我们不需要在其中指定任何内容。
用户应用测试
要测试用户应用程序,请创建或打开 UsersHttpRequestTests 类。请参见第 6-7 号列表。
package com.apress.users;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.boot.test.web.client.TestRestTemplate;import java.util.Collection;import static org.assertj.core.api.Assertions.assertThat;@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,properties = {"spring.data.mongodb.database=retrodb"})public class UsersHttpRequestTests {@Value("${local.server.port}")private int port;private final String BASE_URL = "http://localhost:";private final String USERS_PATH = "/users";@Autowiredprivate TestRestTemplate restTemplate;@Testpublic void indexPageShouldReturnHeaderOneContent{assertThat(this.restTemplate.getForObject(BASE_URL + port,String.class)).contains("Simple Users Rest Application");}@Testpublic void usersEndPointShouldReturnCollectionWithTwoUsers {Collection response = this.restTemplate.getForObject(BASE_URL + port + USERS_PATH, Collection.class);assertThat(response.size).isEqualTo(2);}@Testpublic void userEndPointPostNewUserShouldReturnUser{User user = User.builder.email("dummy@email.com").name("Dummy").gravatarUrl("https://www.gravatar.com/avatar/23bb62a7d0ca63c9a804908e57bf6bd4?d=wavatar").password("aw2s0meR!").role(UserRole.USER).active(true).build;User response = this.restTemplate.postForObject(BASE_URL + port + USERS_PATH,user,User.class);assertThat(response).isNotNull;assertThat(response.getEmail).isEqualTo(user.getEmail);Collection users = this.restTemplate.getForObject(BASE_URL + port + USERS_PATH, Collection.class);assertThat(users.size).isGreaterThanOrEqualTo(2);}@Testpublic void userEndPointDeleteUserShouldReturnVoid {this.restTemplate.delete(BASE_URL + port + USERS_PATH + "/norma@email.com");Collection users = this.restTemplate.getForObject(BASE_URL + port + USERS_PATH, Collection.class);assertThat(users.size).isLessThanOrEqualTo(2);}@Testpublic void userEndPointFindUserShouldReturnUser {User user = this.restTemplate.getForObject(BASE_URL + port + USERS_PATH + "/ximena@email.com",User.class);assertThat(user).isNotNull;assertThat(user.getEmail).isEqualTo("ximena@email.com");}}6-7 src/test/java/apress/com/users/UsersHttpRequestTests.java
UsersHttpRequestTests 类与之前的版本相同,但这里有一个有趣的地方需要注意。在前面的部分,我们将 application.properties 文件留空。实际上,我们添加的 spring-boot-docker-compose 依赖仅适用于开发,因此要使用 Spring Boot 的大多数默认设置,最低要求是添加我们将要使用的数据库名称(retrodb);否则,名称将默认为 test。
要运行测试,您首先需要启动 MongoDB,您可以通过 IDE(VS Code 和 IntelliJ 都有运行 docker-compose 文件的插件)或命令行来完成此操作:
docker compose up -d该命令将使用 docker-compose 依赖项在后台启动 mongodb 服务。接下来,您可以进行测试。
./gradlew clean test你应该得到如下输出:
UsersHttpRequestTests > userEndPointFindUserShouldReturnUser PASSEDUsersHttpRequestTests > userEndPointDeleteUserShouldReturnVoid PASSEDUsersHttpRequestTests > indexPageShouldReturnHeaderOneContent PASSEDUsersHttpRequestTests > userEndPointPostNewUserShouldReturnUser PASSEDUsersHttpRequestTests > usersEndPointShouldReturnCollectionWithTwoUsers PASSED你现在可以停止 Docker Compose 了
docker compose down有这个 Docker Compose 功能作为测试支持会很不错,对吧?你将在测试章节(第 8 章)中学习如何使用测试容器来实现这一点。
启动用户应用程序
要运行用户应用,请确保您没有启动 docker compose——用户应用会为您处理这个。请使用以下命令:
./gradlew clean bootRun......输出显示容器已成功启动。现在,请打开另一个终端并使用以下命令:
curl -s http://localhost:8080/users | jq .{"email": "ximena@email.com","name": "Ximena","gravatarUrl": "https://www.gravatar.com/avatar/f07f7e553264c9710105edebe6c465e7?d=wavatar","password": "aw2s0meR!","userRole": ["USER"],"active": true},{"email": "dummy@email.com","name": "Dummy","gravatarUrl": "https://www.gravatar.com/avatar/23bb62a7d0ca63c9a804908e57bf6bd4?d=wavatar","password": "aw2s0meR!","userRole": ["USER"],"active": true},{"email": "norma@email.com","name": "Norma","gravatarUrl": "https://www.gravatar.com/avatar/23bb62a7d0ca63c9a804908e57bf6bd4?d=wavatar","password": "aw2s0meR!","userRole": ["USER","ADMIN"],"active": true}]# MongoDBspring.data.mongodb.uri=mongodb://retroadmin:aw2s0me@other-server:27017/retrodb?directConnection=true&serverSelectionTimeoutMS=2000&authSource=admin&appName=mongosh+1.7.1spring.data.mongodb.database=retrodbmongodb URI 的格式如下:
mongodb://:@:
/[?...]
在连接 URL 中,您必须将用户名和密码作为参数之一进行指定,因为授权源的默认值是 admin。请与您的管理员核对您的 URI 详细信息。
来源:启辰8