#318 - Add support for Postgres JSON type.

We now pass-thru Postgres's JSON type that can be used in entities and simple queries.
This commit is contained in:
Mark Paluch
2020-03-31 12:37:56 +02:00
parent 020d46b34f
commit df7919a98d
3 changed files with 143 additions and 3 deletions

View File

@@ -22,8 +22,20 @@ import org.springframework.util.ClassUtils;
public class PostgresDialect extends org.springframework.data.relational.core.dialect.PostgresDialect
implements R2dbcDialect {
private static final Set<Class<?>> SIMPLE_TYPES = new HashSet<>(
Arrays.asList(UUID.class, URL.class, URI.class, InetAddress.class));
private static final Set<Class<?>> SIMPLE_TYPES;
static {
Set<Class<?>> simpleTypes = new HashSet<>(Arrays.asList(UUID.class, URL.class, URI.class, InetAddress.class));
if (ClassUtils.isPresent("io.r2dbc.postgresql.codec.Json", PostgresDialect.class.getClassLoader())) {
simpleTypes
.add(ClassUtils.resolveClassName("io.r2dbc.postgresql.codec.Json", PostgresDialect.class.getClassLoader()));
}
SIMPLE_TYPES = simpleTypes;
}
/**
* Singleton instance.

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2019-2020 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 org.springframework.data.r2dbc.convert;
import static org.assertj.core.api.Assertions.*;
import io.r2dbc.postgresql.codec.Json;
import lombok.AllArgsConstructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.r2dbc.dialect.PostgresDialect;
import org.springframework.data.r2dbc.mapping.OutboundRow;
import org.springframework.data.r2dbc.mapping.R2dbcMappingContext;
import org.springframework.data.r2dbc.mapping.SettableValue;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.sql.SqlIdentifier;
/**
* Postgres-specific unit tests for {@link MappingR2dbcConverter}.
*
* @author Mark Paluch
*/
public class PostgresMappingR2dbcConverterUnitTests {
RelationalMappingContext mappingContext = new R2dbcMappingContext();
MappingR2dbcConverter converter = new MappingR2dbcConverter(mappingContext);
@Before
public void before() {
List<Object> converters = new ArrayList<>(PostgresDialect.INSTANCE.getConverters());
converters.addAll(R2dbcCustomConversions.STORE_CONVERTERS);
CustomConversions.StoreConversions storeConversions = CustomConversions.StoreConversions
.of(PostgresDialect.INSTANCE.getSimpleTypeHolder(), converters);
R2dbcCustomConversions customConversions = new R2dbcCustomConversions(storeConversions, Collections.emptyList());
mappingContext.setSimpleTypeHolder(customConversions.getSimpleTypeHolder());
converter = new MappingR2dbcConverter(mappingContext, customConversions);
}
@Test // gh-318
public void shouldPassThruJson() {
JsonPerson person = new JsonPerson(null, Json.of("{\"hello\":\"world\"}"));
OutboundRow row = new OutboundRow();
converter.write(person, row);
assertThat(row).containsEntry(SqlIdentifier.unquoted("json_value"), SettableValue.from(person.jsonValue));
}
@AllArgsConstructor
static class JsonPerson {
@Id Long id;
Json jsonValue;
}
}

View File

@@ -15,24 +15,34 @@
*/
package org.springframework.data.r2dbc.repository;
import static org.assertj.core.api.Assertions.*;
import io.r2dbc.postgresql.codec.Json;
import io.r2dbc.spi.ConnectionFactory;
import lombok.AllArgsConstructor;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import javax.sql.DataSource;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.data.annotation.Id;
import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration;
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
import org.springframework.data.r2dbc.repository.support.R2dbcRepositoryFactory;
import org.springframework.data.r2dbc.testing.ExternalDatabase;
import org.springframework.data.r2dbc.testing.PostgresTestSupport;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@@ -47,9 +57,12 @@ public class PostgresR2dbcRepositoryIntegrationTests extends AbstractR2dbcReposi
@ClassRule public static final ExternalDatabase database = PostgresTestSupport.database();
@Autowired JsonPersonRepository jsonPersonRepository;
@Configuration
@EnableR2dbcRepositories(considerNestedRepositories = true,
includeFilters = @Filter(classes = PostgresLegoSetRepository.class, type = FilterType.ASSIGNABLE_TYPE))
includeFilters = @Filter(classes = { PostgresLegoSetRepository.class, JsonPersonRepository.class },
type = FilterType.ASSIGNABLE_TYPE))
static class IntegrationTestConfiguration extends AbstractR2dbcConfiguration {
@Bean
@@ -93,4 +106,37 @@ public class PostgresR2dbcRepositoryIntegrationTests extends AbstractR2dbcReposi
@Query("SELECT id FROM legoset")
Flux<Integer> findAllIds();
}
@Test
public void shouldSaveAndLoadJson() {
JdbcTemplate template = new JdbcTemplate(createDataSource());
template.execute("DROP TABLE IF EXISTS json_person");
template.execute("CREATE TABLE json_person (\n" //
+ " id SERIAL PRIMARY KEY,\n" //
+ " json_value JSONB NOT NULL" //
+ ");");
JsonPerson person = new JsonPerson(null, Json.of("{\"hello\": \"world\"}"));
jsonPersonRepository.save(person).as(StepVerifier::create).expectNextCount(1).verifyComplete();
jsonPersonRepository.findAll().as(StepVerifier::create).consumeNextWith(actual -> {
assertThat(actual.jsonValue).isNotNull();
assertThat(actual.jsonValue.asString()).isEqualTo("{\"hello\": \"world\"}");
}).verifyComplete();
}
@AllArgsConstructor
static class JsonPerson {
@Id Long id;
Json jsonValue;
}
interface JsonPersonRepository extends ReactiveCrudRepository<JsonPerson, Long> {
}
}