DATAJDBC-147 - Support registration von converters via ConversionCustomizers.

This commit is contained in:
Jens Schauder
2017-11-01 14:37:40 +01:00
committed by Greg Turnquist
parent 207278c891
commit 3c9765c9b8
17 changed files with 127 additions and 47 deletions

View File

@@ -21,13 +21,9 @@ import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.NonTransientDataAccessException;
import org.springframework.data.convert.Jsr310Converters;
import org.springframework.data.jdbc.mapping.model.BasicJdbcPersistentEntityInformation;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
@@ -58,7 +54,6 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
private final SqlGeneratorSource sqlGeneratorSource;
private final NamedParameterJdbcOperations operations;
private final JdbcMappingContext context;
private final ConversionService conversions = getDefaultConversionService();
private final DataAccessStrategy accessStrategy;
public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, NamedParameterJdbcOperations operations,
@@ -222,14 +217,6 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
return operations.queryForObject(existsSql, parameter, Boolean.class);
}
private static GenericConversionService getDefaultConversionService() {
DefaultConversionService conversionService = new DefaultConversionService();
Jsr310Converters.getConvertersToRegister().forEach(conversionService::addConverter);
return conversionService;
}
private <S> MapSqlParameterSource getPropertyMap(final S instance, JdbcPersistentEntity<S> persistentEntity) {
MapSqlParameterSource parameters = new MapSqlParameterSource();
@@ -295,7 +282,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
}
private <T> EntityRowMapper<T> getEntityRowMapper(Class<T> domainType) {
return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), conversions, context, accessStrategy);
return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), context.getConversions(), context, accessStrategy);
}
private RowMapper getMapEntityRowMapper(JdbcPersistentProperty property) {
@@ -323,7 +310,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
Object id = persistentEntity == null ? null : persistentEntity.getIdentifierAccessor(from).getIdentifier();
return conversions.convert(id == null ? from : id, to);
return context.getConversions().convert(id == null ? from : id, to);
}
private SqlGenerator sql(Class<?> domainType) {

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2017 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 org.springframework.data.jdbc.mapping.model;
import org.springframework.core.convert.support.GenericConversionService;
/**
* @author Jens Schauder
*/
public interface ConversionCustomizer {
void customize(GenericConversionService conversions);
}

View File

@@ -27,6 +27,10 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.convert.Jsr310Converters;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.mapping.context.AbstractMappingContext;
import org.springframework.data.mapping.context.MappingContext;
@@ -50,13 +54,21 @@ public class JdbcMappingContext extends AbstractMappingContext<JdbcPersistentEnt
));
private final @Getter NamingStrategy namingStrategy;
private GenericConversionService conversions = getDefaultConversionService();
public JdbcMappingContext(NamingStrategy namingStrategy) {
public JdbcMappingContext(NamingStrategy namingStrategy, ConversionCustomizer customizer) {
this.namingStrategy = namingStrategy;
customizer.customize(conversions);
setSimpleTypeHolder(new SimpleTypeHolder(CUSTOM_SIMPLE_TYPES, true));
}
public JdbcMappingContext() {
this(new DefaultNamingStrategy(), __ -> {});
}
public List<PropertyPath> referencedEntities(Class<?> rootType, PropertyPath path) {
List<PropertyPath> paths = new ArrayList<>();
@@ -103,4 +115,16 @@ public class JdbcMappingContext extends AbstractMappingContext<JdbcPersistentEnt
return new BasicJdbcPersistentEntityInformation<>((JdbcPersistentEntity<T>) getRequiredPersistentEntity(type));
}
public ConversionService getConversions() {
return conversions;
}
private static GenericConversionService getDefaultConversionService() {
DefaultConversionService conversionService = new DefaultConversionService();
Jsr310Converters.getConvertersToRegister().forEach(conversionService::addConverter);
return conversionService;
}
}

View File

@@ -32,6 +32,7 @@ import org.springframework.data.jdbc.core.DataAccessStrategy;
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
import org.springframework.data.jdbc.core.DelegatingDataAccessStrategy;
import org.springframework.data.jdbc.core.SqlGeneratorSource;
import org.springframework.data.jdbc.mapping.model.ConversionCustomizer;
import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.mapping.model.NamingStrategy;
@@ -65,22 +66,23 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extend
private static final String DATA_SOURCE_BEAN_NAME = "dataSource";
private static final String NAMING_STRATEGY_BEAN_NAME = "namingStrategy";
private static final String SQL_SESSION_FACTORY_BEAN_NAME = "sqlSessionFactory";
private static final String CONVERSION_CUSTOMIZER_BEAN_NAME = "conversionCustomizer";
private final ApplicationEventPublisher applicationEventPublisher;
private final ApplicationContext context;
private final ApplicationContext applicationContext;
JdbcRepositoryFactoryBean(Class<? extends T> repositoryInterface, ApplicationEventPublisher applicationEventPublisher,
ApplicationContext context) {
ApplicationContext applicationContext) {
super(repositoryInterface);
this.applicationEventPublisher = applicationEventPublisher;
this.context = context;
this.applicationContext = applicationContext;
}
@Override
protected RepositoryFactorySupport doCreateRepositoryFactory() {
final JdbcMappingContext context = new JdbcMappingContext(findOrCreateNamingStrategy());
final JdbcMappingContext context = new JdbcMappingContext(findOrCreateNamingStrategy(), findOrCreateConversionCustomizer());
return new JdbcRepositoryFactory(applicationEventPublisher, context, createDataAccessStrategy(context));
}
@@ -156,6 +158,10 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extend
return getNamingStrategy().orElse(new DefaultNamingStrategy());
}
private ConversionCustomizer findOrCreateConversionCustomizer() {
return getConversionCustomizer().orElse(conversionService->{});
}
private Optional<NamedParameterJdbcOperations> getNamedParameterJdbcOperations() {
return getBean(NamedParameterJdbcOperations.class, NAMED_PARAMETER_JDBC_OPERATIONS_BEAN_NAME);
}
@@ -172,9 +178,13 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extend
return getBean(NamingStrategy.class, NAMING_STRATEGY_BEAN_NAME);
}
private Optional<ConversionCustomizer> getConversionCustomizer() {
return getBean(ConversionCustomizer.class, CONVERSION_CUSTOMIZER_BEAN_NAME);
}
private <R> Optional<R> getBean(Class<R> type, String name) {
Map<String, R> beansOfType = context.getBeansOfType(type);
Map<String, R> beansOfType = applicationContext.getBeansOfType(type);
if (beansOfType.size() == 1) {
return beansOfType.values().stream().findFirst();

View File

@@ -15,15 +15,12 @@
*/
package org.springframework.data.jdbc.core;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import lombok.RequiredArgsConstructor;
import java.util.HashMap;
import lombok.RequiredArgsConstructor;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
@@ -31,6 +28,9 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.support.KeyHolder;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
/**
* @author Jens Schauder
*/
@@ -39,7 +39,7 @@ public class DefaultDataAccessStrategyUnitTests {
public static final long ID_FROM_ADDITIONAL_VALUES = 23L;
public static final long ORIGINAL_ID = 4711L;
JdbcMappingContext context = new JdbcMappingContext(new DefaultNamingStrategy());
JdbcMappingContext context = new JdbcMappingContext(new DefaultNamingStrategy(), __ -> {});
NamedParameterJdbcOperations jdbcOperations = mock(NamedParameterJdbcOperations.class);
HashMap<String, Object> additionalParameters = new HashMap<>();
ArgumentCaptor<SqlParameterSource> captor = ArgumentCaptor.forClass(SqlParameterSource.class);

View File

@@ -15,14 +15,12 @@
*/
package org.springframework.data.jdbc.core;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.conversion.DbAction;
import org.springframework.data.jdbc.core.conversion.DbAction.Insert;
@@ -31,6 +29,9 @@ import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
/**
* Unit tests for {@link DefaultJdbcInterpreter}
*
@@ -46,7 +47,7 @@ public class DefaultJdbcInterpreterUnitTests {
public String getReverseColumnName(JdbcPersistentProperty property) {
return BACK_REFERENCE;
}
});
}, __ -> {});
DataAccessStrategy dataAccessStrategy = mock(DataAccessStrategy.class);
DefaultJdbcInterpreter interpreter = new DefaultJdbcInterpreter(context, dataAccessStrategy);

View File

@@ -40,7 +40,6 @@ import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.annotation.Id;
import org.springframework.data.convert.Jsr310Converters;
import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
@@ -118,7 +117,7 @@ public class EntityRowMapperUnitTests {
private <T> EntityRowMapper<T> createRowMapper(Class<T> type) {
JdbcMappingContext context = new JdbcMappingContext(new DefaultNamingStrategy());
JdbcMappingContext context = new JdbcMappingContext();
DataAccessStrategy accessStrategy = mock(DataAccessStrategy.class);
// the ID of the entity is used to determin what kind of resultset is needed for subsequent selects.

View File

@@ -30,7 +30,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.testing.TestConfiguration;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
@@ -254,7 +253,7 @@ public class JdbcEntityTemplateIntegrationTests {
JdbcEntityOperations operations(ApplicationEventPublisher publisher,
NamedParameterJdbcOperations namedParameterJdbcOperations) {
final JdbcMappingContext context = new JdbcMappingContext(new DefaultNamingStrategy());
final JdbcMappingContext context = new JdbcMappingContext();
return new JdbcEntityTemplate(publisher, context, dataAccessStrategy(namedParameterJdbcOperations, context));
}

View File

@@ -185,7 +185,7 @@ public class SqlGeneratorContextBasedNamingStrategyUnitTests {
*/
private SqlGenerator configureSqlGenerator(NamingStrategy namingStrategy) {
JdbcMappingContext context = new JdbcMappingContext(namingStrategy);
JdbcMappingContext context = new JdbcMappingContext(namingStrategy, __ -> {});
JdbcPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(DummyEntity.class);
return new SqlGenerator(context, persistentEntity, new SqlGeneratorSource(context));

View File

@@ -180,7 +180,7 @@ public class SqlGeneratorFixedNamingStrategyUnitTests {
*/
private SqlGenerator configureSqlGenerator(NamingStrategy namingStrategy) {
JdbcMappingContext context = new JdbcMappingContext(namingStrategy);
JdbcMappingContext context = new JdbcMappingContext(namingStrategy, __ -> {});
JdbcPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(DummyEntity.class);
return new SqlGenerator(context, persistentEntity, new SqlGeneratorSource(context));
}

View File

@@ -46,7 +46,7 @@ public class SqlGeneratorUnitTests {
public void setUp() {
NamingStrategy namingStrategy = new PrefixingNamingStrategy();
JdbcMappingContext context = new JdbcMappingContext(namingStrategy);
JdbcMappingContext context = new JdbcMappingContext(namingStrategy, __ -> {});
JdbcPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(DummyEntity.class);
this.sqlGenerator = new SqlGenerator(context, persistentEntity, new SqlGeneratorSource(context));
}

View File

@@ -25,7 +25,6 @@ import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.conversion.AggregateChange.Kind;
import org.springframework.data.jdbc.core.conversion.DbAction.Delete;
import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
/**
@@ -36,7 +35,7 @@ import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
@RunWith(MockitoJUnitRunner.class)
public class JdbcEntityDeleteWriterUnitTests {
JdbcEntityDeleteWriter converter = new JdbcEntityDeleteWriter(new JdbcMappingContext(new DefaultNamingStrategy()));
JdbcEntityDeleteWriter converter = new JdbcEntityDeleteWriter(new JdbcMappingContext());
@Test
public void deleteDeletesTheEntityAndReferencedEntities() {

View File

@@ -32,7 +32,6 @@ import org.springframework.data.jdbc.core.conversion.AggregateChange.Kind;
import org.springframework.data.jdbc.core.conversion.DbAction.Delete;
import org.springframework.data.jdbc.core.conversion.DbAction.Insert;
import org.springframework.data.jdbc.core.conversion.DbAction.Update;
import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
/**
@@ -44,7 +43,7 @@ import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
public class JdbcEntityWriterUnitTests {
public static final long SOME_ENTITY_ID = 23L;
JdbcEntityWriter converter = new JdbcEntityWriter(new JdbcMappingContext(new DefaultNamingStrategy()));
JdbcEntityWriter converter = new JdbcEntityWriter(new JdbcMappingContext());
@Test // DATAJDBC-112
public void newEntityGetsConvertedToOneInsert() {

View File

@@ -37,7 +37,7 @@ public class BasicJdbcPersistentPropertyUnitTests {
@Test // DATAJDBC-104
public void enumGetsStoredAsString() {
JdbcPersistentEntity<?> persistentEntity = new JdbcMappingContext(new DefaultNamingStrategy())
JdbcPersistentEntity<?> persistentEntity = new JdbcMappingContext()
.getRequiredPersistentEntity(DummyEntity.class);
persistentEntity.doWithProperties((PropertyHandler<JdbcPersistentProperty>) p -> {

View File

@@ -27,7 +27,6 @@ import org.springframework.data.jdbc.mapping.event.BeforeDelete;
import org.springframework.data.jdbc.mapping.event.BeforeSave;
import org.springframework.data.jdbc.mapping.event.Identifier;
import org.springframework.data.jdbc.mapping.event.JdbcEvent;
import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
import org.springframework.data.repository.CrudRepository;
@@ -47,7 +46,7 @@ public class SimpleJdbcRepositoryEventsUnitTests {
@Before
public void before() {
final JdbcMappingContext context = new JdbcMappingContext(new DefaultNamingStrategy());
final JdbcMappingContext context = new JdbcMappingContext();
JdbcRepositoryFactory factory = new JdbcRepositoryFactory( //
publisher, //
context, //

View File

@@ -4,6 +4,7 @@ import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.springframework.test.util.ReflectionTestUtils.*;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -17,11 +18,15 @@ import org.assertj.core.api.Condition;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.CascadingDataAccessStrategy;
import org.springframework.data.jdbc.core.DataAccessStrategy;
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
import org.springframework.data.jdbc.core.DelegatingDataAccessStrategy;
import org.springframework.data.jdbc.mapping.model.ConversionCustomizer;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.mybatis.MyBatisDataAccessStrategy;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
@@ -29,6 +34,7 @@ import org.springframework.instrument.classloading.ShadowingClassLoader;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
/**
@@ -43,6 +49,7 @@ public class JdbcRepositoryFactoryBeanUnitTests {
static final String ACCESS_STRATEGY_FIELD_NAME_IN_FACTORY = "accessStrategy";
static final String OPERATIONS_FIELD_NAME_IN_DEFAULT_ACCESS_STRATEGY = "operations";
private static final String MAPPING_CONTEXT_FIELD_NAME_IN_FACTORY = "context";
ApplicationEventPublisher eventPublisher = mock(ApplicationEventPublisher.class);
ApplicationContext context = mock(ApplicationContext.class);
@@ -52,12 +59,15 @@ public class JdbcRepositoryFactoryBeanUnitTests {
Map<String, NamedParameterJdbcOperations> namedJdbcOperations = new HashMap<>();
Map<String, SqlSessionFactory> sqlSessionFactories = new HashMap<>();
Map<String, ConversionCustomizer> conversionCustomizers = new HashMap<>();
{
when(context.getBeansOfType(DataSource.class)).thenReturn(dataSources);
when(context.getBeansOfType(JdbcOperations.class)).thenReturn(jdbcOperations);
when(context.getBeansOfType(NamedParameterJdbcOperations.class)).thenReturn(namedJdbcOperations);
when(context.getBeansOfType(SqlSessionFactory.class)).thenReturn(sqlSessionFactories);
when(context.getBeansOfType(ConversionCustomizer.class)).thenReturn(conversionCustomizers);
}
@Test // DATAJDBC-100
@@ -236,6 +246,33 @@ public class JdbcRepositoryFactoryBeanUnitTests {
ReflectionUtils.getAllDeclaredMethods(loadedClass);
}
@Test // DATAJDBC-147
public void registersConversionsCorrectly() {
dataSources.put("anyname", mock(DataSource.class));
conversionCustomizers.put("anyname", cs -> {
cs.addConverter(new Converter<Duration, Long>() {
@Nullable
@Override
public Long convert(Duration duration) {
return duration.toHours();
}
});
});
JdbcRepositoryFactoryBean<DummyEntityRepository, DummyEntity, Long> factoryBean = //
new JdbcRepositoryFactoryBean<>(DummyEntityRepository.class, eventPublisher, context);
RepositoryFactorySupport factory = factoryBean.doCreateRepositoryFactory();
JdbcMappingContext mappingContext = (JdbcMappingContext) getField(factory, MAPPING_CONTEXT_FIELD_NAME_IN_FACTORY);
ConversionService conversions = mappingContext.getConversions();
assertThat(conversions.convert(Duration.ofDays(3), Long.class)).isEqualTo(72L);
}
private Condition<? super RepositoryFactorySupport> using(NamedParameterJdbcOperations expectedOperations) {
Predicate<RepositoryFactorySupport> predicate = r -> extractNamedParameterJdbcOperations(r) == expectedOperations;

View File

@@ -50,12 +50,12 @@ public class TestConfiguration {
@Bean
JdbcRepositoryFactory jdbcRepositoryFactory() {
final JdbcMappingContext context = new JdbcMappingContext(new DefaultNamingStrategy(){
final JdbcMappingContext context = new JdbcMappingContext(new DefaultNamingStrategy() {
@Override
public String getColumnName(JdbcPersistentProperty property) {
return super.getColumnName(property);
}
});
}, __ -> {});
return new JdbcRepositoryFactory( //
publisher, //