diff --git a/mongodb/example/README.md b/mongodb/example/README.md index cd4c9e44..7447a93b 100644 --- a/mongodb/example/README.md +++ b/mongodb/example/README.md @@ -6,4 +6,5 @@ This project contains examples for: * `EntityCallback` API usage for before convert/save interaction. * Result projections for DTOs and interface types. * Query metadata. +* Unwrapping entities into the parent document. diff --git a/mongodb/example/src/main/java/example/springdata/mongodb/unwrapping/ApplicationConfiguration.java b/mongodb/example/src/main/java/example/springdata/mongodb/unwrapping/ApplicationConfiguration.java new file mode 100644 index 00000000..ff004cfe --- /dev/null +++ b/mongodb/example/src/main/java/example/springdata/mongodb/unwrapping/ApplicationConfiguration.java @@ -0,0 +1,26 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.mongodb.unwrapping; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author Christoph Strobl + */ +@SpringBootApplication +public class ApplicationConfiguration { + +} diff --git a/mongodb/example/src/main/java/example/springdata/mongodb/unwrapping/Email.java b/mongodb/example/src/main/java/example/springdata/mongodb/unwrapping/Email.java new file mode 100644 index 00000000..548fc6f7 --- /dev/null +++ b/mongodb/example/src/main/java/example/springdata/mongodb/unwrapping/Email.java @@ -0,0 +1,52 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.mongodb.unwrapping; + +import java.util.Objects; + +/** + * @author Christoph Strobl + */ +public class Email { // might as well be a record type in more recent java versions + + private final String email; + + public Email(String email) { + this.email = email; + } + + public String getEmail() { + return email; + } + + @Override + public String toString() { + return email; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Email email1 = (Email) o; + return Objects.equals(email, email1.email); + } + + @Override + public int hashCode() { + return Objects.hash(email); + } +} diff --git a/mongodb/example/src/main/java/example/springdata/mongodb/unwrapping/User.java b/mongodb/example/src/main/java/example/springdata/mongodb/unwrapping/User.java new file mode 100644 index 00000000..1a453403 --- /dev/null +++ b/mongodb/example/src/main/java/example/springdata/mongodb/unwrapping/User.java @@ -0,0 +1,105 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.mongodb.unwrapping; + +import java.util.Objects; + +import org.springframework.data.mongodb.core.mapping.Unwrapped; +import org.springframework.data.mongodb.core.mapping.Unwrapped.OnEmpty; +import org.springframework.lang.Nullable; + +/** + * @author Christoph Strobl + */ +public class User { + + @Nullable + private String id; + + @Nullable + @Unwrapped(onEmpty = OnEmpty.USE_NULL) + private UserName userName; + + /* + * The prefix allows to unwrap multiple properties of the same type assigning them a unique prefix. + * In this case the `Email.email` will be stored as `primary_email`. + */ + @Nullable + @Unwrapped(onEmpty = OnEmpty.USE_NULL, prefix = "primary_") + private Email email; + + User() { + } + + public User(String id, UserName userName, Email email) { + + this.id = id; + this.userName = userName; + this.email = email; + } + + User(String id, String userName, String email) { + this(id, new UserName(userName), new Email(email)); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public UserName getUserName() { + return userName; + } + + public Email getEmail() { + return email; + } + + public void setUserName(@Nullable UserName userName) { + this.userName = userName; + } + + public void setEmail(@Nullable Email email) { + this.email = email; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + User user = (User) o; + return Objects.equals(id, user.id) && + Objects.equals(userName, user.userName) && + Objects.equals(email, user.email); + } + + @Override + public int hashCode() { + return Objects.hash(id, userName, email); + } + + @Override + public String toString() { + return "User{" + + "id='" + id + '\'' + + ", userName=" + userName + + ", email=" + email + + '}'; + } +} diff --git a/mongodb/example/src/main/java/example/springdata/mongodb/unwrapping/UserName.java b/mongodb/example/src/main/java/example/springdata/mongodb/unwrapping/UserName.java new file mode 100644 index 00000000..8ea20911 --- /dev/null +++ b/mongodb/example/src/main/java/example/springdata/mongodb/unwrapping/UserName.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.mongodb.unwrapping; + +import java.util.Objects; + +/** + * @author Christoph Strobl + */ +public class UserName { // might as well be a record type in more recent java versions + + private final String username; + + public UserName(String username) { + this.username = username; + } + + public String getUsername() { + return username; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UserName userName = (UserName) o; + return Objects.equals(username, userName.username); + } + + @Override + public int hashCode() { + return Objects.hash(username); + } +} diff --git a/mongodb/example/src/main/java/example/springdata/mongodb/unwrapping/UserRepository.java b/mongodb/example/src/main/java/example/springdata/mongodb/unwrapping/UserRepository.java new file mode 100644 index 00000000..992be590 --- /dev/null +++ b/mongodb/example/src/main/java/example/springdata/mongodb/unwrapping/UserRepository.java @@ -0,0 +1,36 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.mongodb.unwrapping; + +import org.springframework.data.repository.CrudRepository; + +/** + * @author Christoph Strobl + */ +public interface UserRepository extends CrudRepository { + + /** + * Use the value type directly. + * This will unwrap the values into the target query object. + */ + User findByUserName(UserName userName); + + /** + * Use one of the unwrapped properties. + * This will drill into the unwrapped using the value for the target query. + */ + User findByEmailEmail(String email); +} diff --git a/mongodb/example/src/test/java/example/springdata/mongodb/unwrapping/UnwrappingIntegrationTests.java b/mongodb/example/src/test/java/example/springdata/mongodb/unwrapping/UnwrappingIntegrationTests.java new file mode 100644 index 00000000..ec924366 --- /dev/null +++ b/mongodb/example/src/test/java/example/springdata/mongodb/unwrapping/UnwrappingIntegrationTests.java @@ -0,0 +1,81 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.mongodb.unwrapping; + +import static org.assertj.core.api.Assertions.*; + +import java.util.Arrays; + +import com.mongodb.client.model.Filters; +import org.bson.Document; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * @author Christoph Strobl + */ +@SpringBootTest +@ExtendWith(SpringExtension.class) +class UnwrappingIntegrationTests { + + @Autowired UserRepository repository; + @Autowired MongoOperations operations; + + User jane = new User("jane-01", new UserName("jane-villanueva"), new Email("jane@home.com")); + User rogelio = new User("rogelio-01", new UserName("rogelio-de-la-vega"), new Email("me@RogelioDeLaVega.com")); + + @BeforeEach + void beforeEach() { + + operations.dropCollection(User.class); + repository.saveAll(Arrays.asList(jane, rogelio)); + } + + @Test + void documentStructure() { + + org.bson.Document stored = operations.execute(User.class, collection -> { + return collection.find(Filters.eq("_id", rogelio.getId())).first(); + }); + + assertThat(stored).containsAllEntriesOf(new Document("_id", rogelio.getId()) + .append("username", rogelio.getUserName().getUsername()) + .append("primary_email", rogelio.getEmail().getEmail()) + ); + } + + + /** + * Query using the value type. + */ + @Test + void queryViaValueType() { + assertThat(repository.findByUserName(jane.getUserName())).isEqualTo(jane); + } + + /** + * Query using a property of the embedded value type + */ + @Test + void queryViaPropertyOfValueType() { + assertThat(repository.findByEmailEmail(jane.getEmail().getEmail())).isEqualTo(jane); + } +}