Add example showing PropertyValueConverter usage.

Closes #637
This commit is contained in:
Mark Paluch
2022-04-26 15:14:37 +02:00
committed by Greg L. Turnquist
parent 83cd9e2d5a
commit 7e50e0b612
9 changed files with 345 additions and 3 deletions

View File

@@ -2,9 +2,10 @@
This project contains examples for:
* Derived finder and geo spatial queries using a `Repository` interface.
* Derived query method and geospatial queries using a `Repository` interface.
* Usage of property-specific converters.
* `EntityCallback` API usage for before convert/save interaction.
* Result projections for DTOs and interface types.
* Query metadata.
* Result projections for DTOs and interface types.
* Query metadata.
* Unwrapping entities into the parent document.

View File

@@ -46,6 +46,10 @@
</build>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-mongodb-example-utils</artifactId>

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2014-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.converters;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import java.util.Map;
import org.springframework.data.geo.Point;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* A domain object to capture addresses.
*
* @author Mark Paluch
*/
@Data
@RequiredArgsConstructor
public class Address {
private final Point location;
private String street;
private String zipCode;
Address(@JsonProperty("location") Map<String, Double> location, @JsonProperty("street") String street,
@JsonProperty("zipCode") String zipCode) {
this.location = new Point(location.get("x"), location.get("y"));
this.street = street;
this.zipCode = zipCode;
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2022 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
*
* http://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.converters;
import org.bson.Document;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.convert.MongoConversionContext;
import org.springframework.data.mongodb.core.convert.MongoValueConverter;
/**
* {@link MongoValueConverter} to write only the {@link Address#location} into a MongoDB {@link Document}.
*
* @author Mark Paluch
*/
class AddressDocumentConverter implements MongoValueConverter<Address, Document> {
@Override
public Address read(Document value, MongoConversionContext context) {
Point point = new Point(context.read(value.get("x"), Double.class), context.read(value.get("y"), Double.class));
return new Address(point);
}
@Override
public Document write(Address value, MongoConversionContext context) {
Document document = new Document();
document.put("x", value.getLocation().getX());
document.put("y", value.getLocation().getY());
return document;
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2022 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
*
* http://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.converters;
import org.springframework.data.mongodb.core.convert.MongoConversionContext;
import org.springframework.data.mongodb.core.convert.MongoValueConverter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* {@link MongoValueConverter} to write {@link Address} into JSON through Jackson.
*
* @author Mark Paluch
*/
class AddressJsonConverter implements MongoValueConverter<Address, String> {
private final ObjectMapper mapper = new ObjectMapper();
@Override
public Address read(String value, MongoConversionContext context) {
try {
return mapper.readValue(value, Address.class);
} catch (JsonProcessingException e) {
throw new IllegalStateException("Cannot unmarshal Address JSON", e);
}
}
@Override
public String write(Address value, MongoConversionContext context) {
try {
return mapper.writeValueAsString(value);
} catch (JsonProcessingException e) {
throw new IllegalStateException("Cannot marshal Address into JSON", e);
}
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2014-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.converters;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Test configuration.
*
* @author Mark Paluch
*/
@SpringBootApplication
class ApplicationConfiguration {
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2014-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.converters;
import lombok.Data;
import org.springframework.data.convert.ValueConverter;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.util.Assert;
/**
* An entity to represent a customer.
*
* @author Mark Paluch
*/
@Data
@Document
public class Customer {
private String id, firstname, lastname;
@ValueConverter(AddressJsonConverter.class) private Address primary;
@ValueConverter(AddressDocumentConverter.class) private Address secondary;
/**
* Creates a new {@link Customer} with the given firstname and lastname.
*
* @param firstname must not be {@literal null} or empty.
* @param lastname must not be {@literal null} or empty.
*/
public Customer(String firstname, String lastname) {
Assert.hasText(firstname, "Firstname must not be null or empty!");
Assert.hasText(lastname, "Lastname must not be null or empty!");
this.firstname = firstname;
this.lastname = lastname;
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2014-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.converters;
import static org.assertj.core.api.Assertions.*;
import example.springdata.mongodb.util.MongoContainers;
import org.bson.Document;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.MongoDBContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
/**
* Integration tests for {@link Customer} conversions.
*
* @author Mark Paluch
*/
@Testcontainers
@DataMongoTest
class CustomerIntegrationTests {
@Container //
private static MongoDBContainer mongoDBContainer = MongoContainers.getDefaultContainer();
@DynamicPropertySource
static void setProperties(DynamicPropertyRegistry registry) {
registry.add("spring.data.mongodb.uri", mongoDBContainer::getReplicaSetUrl);
}
@Autowired MongoOperations operations;
@BeforeEach
void setUp() {
operations.remove(Customer.class).all();
}
/**
* Applies {@link org.springframework.data.mongodb.core.convert.MongoValueConverter} to individual properties.
* {@code primary} and {@code secondary} are configured with different converters of which the first one uses JSON
* conversion and the second converter interacts directly with a MongoDB {@link Document}.
*/
@Test
void appliesConverters() {
var primaryAddress = new Address(new Point(4.1, 5.6));
primaryAddress.setStreet("308 Negra Arroyo Lane");
primaryAddress.setZipCode("87104");
var secondaryAddress = new Address(new Point(6.1, 7.6));
secondaryAddress.setStreet("3828 Piermont Drive");
secondaryAddress.setZipCode("87111");
var customer = new Customer("Walter", "White");
customer.setPrimary(primaryAddress);
customer.setSecondary(secondaryAddress);
operations.insert(customer);
Document document = operations.findOne(new Query(), Document.class, "customer");
assertThat(document.get("primary"))
.isEqualTo("{\"location\":{\"x\":4.1,\"y\":5.6},\"street\":\"308 Negra Arroyo Lane\",\"zipCode\":\"87104\"}");
assertThat(document.get("secondary")).isEqualTo(
new Document("x", secondaryAddress.getLocation().getX()).append("y", secondaryAddress.getLocation().getY()));
Customer loadedCustomer = operations.findOne(new Query(), Customer.class, "customer");
// Primary address marshalled as JSON
assertThat(loadedCustomer.getPrimary()).isNotNull();
assertThat(loadedCustomer.getPrimary().getStreet()).isEqualTo(primaryAddress.getStreet());
assertThat(loadedCustomer.getPrimary().getZipCode()).isEqualTo(primaryAddress.getZipCode());
assertThat(loadedCustomer.getPrimary().getLocation()).isEqualTo(primaryAddress.getLocation());
// Secondary address stores only location as the converter considers only points
assertThat(loadedCustomer.getSecondary()).isNotNull();
assertThat(loadedCustomer.getSecondary().getStreet()).isNull();
assertThat(loadedCustomer.getSecondary().getLocation()).isEqualTo(secondaryAddress.getLocation());
}
}

View File

@@ -0,0 +1,4 @@
/**
* Package showing converter usage with MongoDB entities.
*/
package example.springdata.mongodb.converters;