DATAJDBC-290 - Allow specification of resultsetExtractorClass.
A @Query annotation may now specify a class implementing ResultSetExtractor to be useed for extracting objects from ResultSets. Original pull request: #101.
This commit is contained in:
committed by
Jens Schauder
parent
f032e6cf46
commit
4e7a109393
@@ -0,0 +1,27 @@
|
||||
package org.springframework.data.jdbc.repository;
|
||||
|
||||
import org.springframework.data.jdbc.support.RowMapperResultsetExtractorEither;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
|
||||
/**
|
||||
* A map from a type to a {@link ResultSetExtractor} to be used for extracting that type from {@link java.sql.ResultSet}s.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Evgeni Dimitrov
|
||||
*/
|
||||
public interface QueryMappingConfiguration {
|
||||
<T> RowMapperResultsetExtractorEither<?> getMapper(Class<T> type);
|
||||
|
||||
/**
|
||||
* An immutable empty instance that will return {@literal null} for all arguments.
|
||||
*/
|
||||
QueryMappingConfiguration EMPTY = new QueryMappingConfiguration() {
|
||||
|
||||
@Override
|
||||
public <T> RowMapperResultsetExtractorEither<?> getMapper(Class<T> type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 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.repository;
|
||||
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A map from a type to a {@link RowMapper} to be used for extracting that type from {@link java.sql.ResultSet}s.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public interface RowMapperMap {
|
||||
|
||||
/**
|
||||
* An immutable empty instance that will return {@literal null} for all arguments.
|
||||
*/
|
||||
RowMapperMap EMPTY = new RowMapperMap() {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.repository.RowMapperMap#rowMapperFor(java.lang.Class)
|
||||
*/
|
||||
public <T> RowMapper<? extends T> rowMapperFor(Class<T> type) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@Nullable
|
||||
<T> RowMapper<? extends T> rowMapperFor(Class<T> type);
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 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.repository.config;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.jdbc.repository.RowMapperMap;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link RowMapperMap} that allows for registration of {@link RowMapper}s via a fluent Api.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public class ConfigurableRowMapperMap implements RowMapperMap {
|
||||
|
||||
private Map<Class<?>, RowMapper<?>> rowMappers = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* Registers a the given {@link RowMapper} as to be used for the given type.
|
||||
*
|
||||
* @return this instance, so this can be used as a fluent interface.
|
||||
*/
|
||||
public <T> ConfigurableRowMapperMap register(Class<T> type, RowMapper<? extends T> rowMapper) {
|
||||
|
||||
rowMappers.put(type, rowMapper);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returs a {@link RowMapper} for the given type if such a {@link RowMapper} is present. If an exact match is found
|
||||
* that is returned. If not a {@link RowMapper} is returned that produces subtypes of the requested type. If no such
|
||||
* {@link RowMapper} is found the method returns {@code null}.
|
||||
*
|
||||
* @param type the type to be produced by the returned {@link RowMapper}. Must not be {@code null}.
|
||||
* @param <T> the type to be produced by the returned {@link RowMapper}.
|
||||
* @return Guaranteed to be not {@code null}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
public <T> RowMapper<? extends T> rowMapperFor(Class<T> type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null");
|
||||
|
||||
RowMapper<? extends T> candidate = (RowMapper<? extends T>) rowMappers.get(type);
|
||||
|
||||
if (candidate == null) {
|
||||
|
||||
for (Map.Entry<Class<?>, RowMapper<?>> entry : rowMappers.entrySet()) {
|
||||
|
||||
if (type.isAssignableFrom(entry.getKey())) {
|
||||
candidate = (RowMapper<? extends T>) entry.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.springframework.data.jdbc.repository.config;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
|
||||
import org.springframework.data.jdbc.support.RowMapperResultsetExtractorEither;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link QueryMappingConfiguration} that allows for registration of {@link RowMapper}s and {@link ResultSetExtractor}s via a fluent Api.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Evgeni Dimitrov
|
||||
*/
|
||||
public class DefaultQueryMappingConfiguration implements QueryMappingConfiguration{
|
||||
private Map<Class<?>, RowMapperResultsetExtractorEither<?>> mappers = new LinkedHashMap<>();
|
||||
|
||||
public <T> RowMapperResultsetExtractorEither<?> getMapper(Class<T> type) {
|
||||
Assert.notNull(type, "Type must not be null");
|
||||
|
||||
RowMapperResultsetExtractorEither<?> candidate = mappers.get(type);
|
||||
|
||||
if (candidate == null) {
|
||||
|
||||
for (Map.Entry<Class<?>, RowMapperResultsetExtractorEither<?>> entry : mappers.entrySet()) {
|
||||
|
||||
if (type.isAssignableFrom(entry.getKey())) {
|
||||
candidate = entry.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a the given {@link RowMapper} as to be used for the given type.
|
||||
*
|
||||
* @return this instance, so this can be used as a fluent interface.
|
||||
*/
|
||||
public <T> DefaultQueryMappingConfiguration registerRowMapper(Class<T> type, RowMapper<? extends T> rowMapper) {
|
||||
mappers.put(type, RowMapperResultsetExtractorEither.of(rowMapper));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a the given {@link ResultSetExtractor} as to be used for the given type.
|
||||
*
|
||||
* @return this instance, so this can be used as a fluent interface.
|
||||
*/
|
||||
public <T> DefaultQueryMappingConfiguration registerResultSetExtractor(Class<T> type, ResultSetExtractor resultSetExtractor) {
|
||||
mappers.put(type, RowMapperResultsetExtractorEither.of(resultSetExtractor));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.data.annotation.QueryAnnotation;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
/**
|
||||
@@ -44,6 +45,13 @@ public @interface Query {
|
||||
|
||||
/**
|
||||
* Optional {@link RowMapper} to use to convert the result of the query to domain class instances.
|
||||
* Cannot be used along with {@link #resultSetExtractorClass()} only one of the two can be set.
|
||||
*/
|
||||
Class<? extends RowMapper> rowMapperClass() default RowMapper.class;
|
||||
|
||||
/**
|
||||
* Optional {@link ResultSetExtractor} to use to convert the result of the query to domain class instances.
|
||||
* Cannot be used along with {@link #rowMapperClass()} only one of the two can be set.
|
||||
*/
|
||||
Class<? extends ResultSetExtractor> resultSetExtractorClass() default ResultSetExtractor.class;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.springframework.data.jdbc.repository.support;
|
||||
|
||||
import org.springframework.data.jdbc.repository.query.Query;
|
||||
|
||||
/**
|
||||
* Exception thrown when both {@link Query#resultSetExtractorClass()} and {@link Query#rowMapperClass()} are used in one {@link Query}.
|
||||
*
|
||||
* @author Evgeni Dimitrov
|
||||
*/
|
||||
public class InvalidQueryConfiguration extends RuntimeException {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 6604189906427682546L;
|
||||
|
||||
public InvalidQueryConfiguration(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,8 @@ import java.lang.reflect.Method;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.data.jdbc.core.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.EntityRowMapper;
|
||||
import org.springframework.data.jdbc.repository.RowMapperMap;
|
||||
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
|
||||
import org.springframework.data.jdbc.support.RowMapperResultsetExtractorEither;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.relational.core.conversion.RelationalConverter;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
@@ -29,6 +30,7 @@ import org.springframework.data.repository.core.NamedQueries;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.SingleColumnRowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
@@ -49,12 +51,12 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
|
||||
private final RelationalMappingContext context;
|
||||
private final RelationalConverter converter;
|
||||
private final DataAccessStrategy accessStrategy;
|
||||
private final RowMapperMap rowMapperMap;
|
||||
private final QueryMappingConfiguration mapperMap;
|
||||
private final NamedParameterJdbcOperations operations;
|
||||
|
||||
/**
|
||||
* Creates a new {@link JdbcQueryLookupStrategy} for the given {@link RelationalMappingContext},
|
||||
* {@link DataAccessStrategy} and {@link RowMapperMap}.
|
||||
* {@link DataAccessStrategy} and {@link QueryMappingConfiguration}.
|
||||
*
|
||||
* @param publisher must not be {@literal null}.
|
||||
* @param context must not be {@literal null}.
|
||||
@@ -63,7 +65,7 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
|
||||
* @param rowMapperMap must not be {@literal null}.
|
||||
*/
|
||||
JdbcQueryLookupStrategy(ApplicationEventPublisher publisher, RelationalMappingContext context, RelationalConverter converter,
|
||||
DataAccessStrategy accessStrategy, RowMapperMap rowMapperMap, NamedParameterJdbcOperations operations) {
|
||||
DataAccessStrategy accessStrategy, QueryMappingConfiguration rowMapperMap, NamedParameterJdbcOperations operations) {
|
||||
|
||||
Assert.notNull(publisher, "Publisher must not be null!");
|
||||
Assert.notNull(context, "RelationalMappingContext must not be null!");
|
||||
@@ -75,7 +77,7 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
|
||||
this.context = context;
|
||||
this.converter = converter;
|
||||
this.accessStrategy = accessStrategy;
|
||||
this.rowMapperMap = rowMapperMap;
|
||||
this.mapperMap = rowMapperMap;
|
||||
this.operations = operations;
|
||||
}
|
||||
|
||||
@@ -89,36 +91,35 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
|
||||
|
||||
JdbcQueryMethod queryMethod = new JdbcQueryMethod(method, repositoryMetadata, projectionFactory);
|
||||
|
||||
RowMapper<?> rowMapper = queryMethod.isModifyingQuery() ? null : createRowMapper(queryMethod);
|
||||
RowMapperResultsetExtractorEither<?> mapper = queryMethod.isModifyingQuery() ? null : createMapper(queryMethod);
|
||||
|
||||
return new JdbcRepositoryQuery(publisher, context, queryMethod, operations, rowMapper);
|
||||
return new JdbcRepositoryQuery(publisher, context, queryMethod, operations, mapper);
|
||||
}
|
||||
|
||||
private RowMapper<?> createRowMapper(JdbcQueryMethod queryMethod) {
|
||||
private RowMapperResultsetExtractorEither<?> createMapper(JdbcQueryMethod queryMethod) {
|
||||
|
||||
Class<?> returnedObjectType = queryMethod.getReturnedObjectType();
|
||||
|
||||
RelationalPersistentEntity<?> persistentEntity = context.getPersistentEntity(returnedObjectType);
|
||||
|
||||
if (persistentEntity == null) {
|
||||
return SingleColumnRowMapper.newInstance(returnedObjectType, converter.getConversionService());
|
||||
return RowMapperResultsetExtractorEither.of(
|
||||
SingleColumnRowMapper.newInstance(returnedObjectType, converter.getConversionService()));
|
||||
}
|
||||
|
||||
return determineDefaultRowMapper(queryMethod);
|
||||
return determineDefaultMapper(queryMethod);
|
||||
}
|
||||
|
||||
private RowMapper<?> determineDefaultRowMapper(JdbcQueryMethod queryMethod) {
|
||||
|
||||
private RowMapperResultsetExtractorEither<?> determineDefaultMapper(JdbcQueryMethod queryMethod) {
|
||||
Class<?> domainType = queryMethod.getReturnedObjectType();
|
||||
|
||||
RowMapper<?> typeMappedRowMapper = rowMapperMap.rowMapperFor(domainType);
|
||||
|
||||
return typeMappedRowMapper == null //
|
||||
? new EntityRowMapper<>( //
|
||||
context.getRequiredPersistentEntity(domainType), //
|
||||
context, //
|
||||
converter, //
|
||||
accessStrategy) //
|
||||
: typeMappedRowMapper;
|
||||
RowMapperResultsetExtractorEither<?> configuredQueryMapper = mapperMap.getMapper(domainType);
|
||||
if(configuredQueryMapper != null) return configuredQueryMapper;
|
||||
|
||||
EntityRowMapper<?> defaultEntityRowMapper = new EntityRowMapper<>( //
|
||||
context.getRequiredPersistentEntity(domainType), //
|
||||
context, //
|
||||
converter, //
|
||||
accessStrategy);
|
||||
return RowMapperResultsetExtractorEither.of(defaultEntityRowMapper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,16 @@ public class JdbcQueryMethod extends QueryMethod {
|
||||
public Class<?> getRowMapperClass() {
|
||||
return getMergedAnnotationAttribute("rowMapperClass");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the class to be used as {@link org.springframework.jdbc.core.ResultSetExtractor}
|
||||
*
|
||||
* @return May be {@code null}.
|
||||
*/
|
||||
@Nullable
|
||||
public Class<?> getResultSetExtractorClass() {
|
||||
return getMergedAnnotationAttribute("resultSetExtractorClass");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the query method is a modifying one.
|
||||
|
||||
@@ -20,7 +20,7 @@ import java.util.Optional;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.data.jdbc.core.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
|
||||
import org.springframework.data.jdbc.repository.RowMapperMap;
|
||||
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
|
||||
import org.springframework.data.relational.core.conversion.RelationalConverter;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
@@ -51,7 +51,7 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
|
||||
private final DataAccessStrategy accessStrategy;
|
||||
private final NamedParameterJdbcOperations operations;
|
||||
|
||||
private RowMapperMap rowMapperMap = RowMapperMap.EMPTY;
|
||||
private QueryMappingConfiguration mapperMap = QueryMappingConfiguration.EMPTY;
|
||||
|
||||
/**
|
||||
* Creates a new {@link JdbcRepositoryFactory} for the given {@link DataAccessStrategy},
|
||||
@@ -81,11 +81,11 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
|
||||
/**
|
||||
* @param rowMapperMap must not be {@literal null} consider {@link RowMapperMap#EMPTY} instead.
|
||||
*/
|
||||
public void setRowMapperMap(RowMapperMap rowMapperMap) {
|
||||
public void setRowMapperMap(QueryMappingConfiguration rowMapperMap) {
|
||||
|
||||
Assert.notNull(rowMapperMap, "RowMapperMap must not be null!");
|
||||
|
||||
this.rowMapperMap = rowMapperMap;
|
||||
this.mapperMap = rowMapperMap;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -133,6 +133,6 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
|
||||
throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s!", key));
|
||||
}
|
||||
|
||||
return Optional.of(new JdbcQueryLookupStrategy(publisher, context, converter, accessStrategy, rowMapperMap, operations));
|
||||
return Optional.of(new JdbcQueryLookupStrategy(publisher, context, converter, accessStrategy, mapperMap, operations));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.data.jdbc.core.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.SqlGeneratorSource;
|
||||
import org.springframework.data.jdbc.repository.RowMapperMap;
|
||||
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
|
||||
import org.springframework.data.relational.core.conversion.RelationalConverter;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.repository.Repository;
|
||||
@@ -49,7 +49,7 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extend
|
||||
private RelationalMappingContext mappingContext;
|
||||
private RelationalConverter converter;
|
||||
private DataAccessStrategy dataAccessStrategy;
|
||||
private RowMapperMap rowMapperMap = RowMapperMap.EMPTY;
|
||||
private QueryMappingConfiguration mapperMap = QueryMappingConfiguration.EMPTY;
|
||||
private NamedParameterJdbcOperations operations;
|
||||
|
||||
/**
|
||||
@@ -81,7 +81,7 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extend
|
||||
|
||||
JdbcRepositoryFactory jdbcRepositoryFactory = new JdbcRepositoryFactory(dataAccessStrategy, mappingContext,
|
||||
converter, publisher, operations);
|
||||
jdbcRepositoryFactory.setRowMapperMap(rowMapperMap);
|
||||
jdbcRepositoryFactory.setRowMapperMap(mapperMap);
|
||||
|
||||
return jdbcRepositoryFactory;
|
||||
}
|
||||
@@ -106,8 +106,8 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extend
|
||||
* {@literal null}.
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
public void setRowMapperMap(RowMapperMap rowMapperMap) {
|
||||
this.rowMapperMap = rowMapperMap;
|
||||
public void setRowMapperMap(QueryMappingConfiguration rowMapperMap) {
|
||||
this.mapperMap = rowMapperMap;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
@@ -137,8 +137,8 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extend
|
||||
operations);
|
||||
}
|
||||
|
||||
if (rowMapperMap == null) {
|
||||
this.rowMapperMap = RowMapperMap.EMPTY;
|
||||
if (mapperMap == null) {
|
||||
this.mapperMap = QueryMappingConfiguration.EMPTY;
|
||||
}
|
||||
|
||||
super.afterPropertiesSet();
|
||||
|
||||
@@ -18,11 +18,13 @@ package org.springframework.data.jdbc.repository.support;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.data.jdbc.support.RowMapperResultsetExtractorEither;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.event.AfterLoadEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.Identifier;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
@@ -49,7 +51,7 @@ class JdbcRepositoryQuery implements RepositoryQuery {
|
||||
private final RelationalMappingContext context;
|
||||
private final JdbcQueryMethod queryMethod;
|
||||
private final NamedParameterJdbcOperations operations;
|
||||
private final RowMapper<?> rowMapper;
|
||||
private final RowMapperResultsetExtractorEither<?> mapper;
|
||||
|
||||
/**
|
||||
* Creates a new {@link JdbcRepositoryQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
|
||||
@@ -62,7 +64,7 @@ class JdbcRepositoryQuery implements RepositoryQuery {
|
||||
* @param defaultRowMapper can be {@literal null} (only in case of a modifying query).
|
||||
*/
|
||||
JdbcRepositoryQuery(ApplicationEventPublisher publisher, RelationalMappingContext context, JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
|
||||
@Nullable RowMapper<?> defaultRowMapper) {
|
||||
@Nullable RowMapperResultsetExtractorEither<?> defaultMapper) {
|
||||
|
||||
Assert.notNull(publisher, "Publisher must not be null!");
|
||||
Assert.notNull(context, "Context must not be null!");
|
||||
@@ -70,14 +72,14 @@ class JdbcRepositoryQuery implements RepositoryQuery {
|
||||
Assert.notNull(operations, "NamedParameterJdbcOperations must not be null!");
|
||||
|
||||
if (!queryMethod.isModifyingQuery()) {
|
||||
Assert.notNull(defaultRowMapper, "RowMapper must not be null!");
|
||||
Assert.notNull(defaultMapper, "Mapper must not be null!");
|
||||
}
|
||||
|
||||
this.publisher = publisher;
|
||||
this.context = context;
|
||||
this.queryMethod = queryMethod;
|
||||
this.operations = operations;
|
||||
this.rowMapper = createRowMapper(queryMethod, defaultRowMapper);
|
||||
this.mapper = determineMapper(defaultMapper);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -99,15 +101,23 @@ class JdbcRepositoryQuery implements RepositoryQuery {
|
||||
}
|
||||
|
||||
if (queryMethod.isCollectionQuery() || queryMethod.isStreamQuery()) {
|
||||
|
||||
List<?> result = operations.query(query, parameters, rowMapper);
|
||||
List<?> result = null;
|
||||
if(this.mapper.isResultSetExtractor()) {
|
||||
result = (List<?>) operations.query(query, parameters, this.mapper.resultSetExtractor());
|
||||
} else {
|
||||
result = operations.query(query, parameters, this.mapper.rowMapper());
|
||||
}
|
||||
publishAfterLoad(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
Object result = operations.queryForObject(query, parameters, rowMapper);
|
||||
Object result = null;
|
||||
if(this.mapper.isResultSetExtractor()) {
|
||||
result = operations.query(query,parameters, this.mapper.resultSetExtractor());
|
||||
} else {
|
||||
result = operations.queryForObject(query, parameters, this.mapper.rowMapper());
|
||||
}
|
||||
publishAfterLoad(result);
|
||||
return result;
|
||||
} catch (EmptyResultDataAccessException e) {
|
||||
@@ -148,14 +158,30 @@ class JdbcRepositoryQuery implements RepositoryQuery {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private RowMapperResultsetExtractorEither<?> determineMapper(RowMapperResultsetExtractorEither<?> defaultMapper) {
|
||||
RowMapperResultsetExtractorEither<?> configuredMapper = getConfiguredMapper(queryMethod);
|
||||
if(configuredMapper != null) return configuredMapper;
|
||||
return defaultMapper;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static RowMapper<?> createRowMapper(JdbcQueryMethod queryMethod, @Nullable RowMapper<?> defaultRowMapper) {
|
||||
|
||||
private static RowMapperResultsetExtractorEither<?> getConfiguredMapper(JdbcQueryMethod queryMethod) {
|
||||
Class<?> rowMapperClass = queryMethod.getRowMapperClass();
|
||||
Class<?> resultSetExtractorClass = queryMethod.getResultSetExtractorClass();
|
||||
if(isConfigured(rowMapperClass, RowMapper.class) && isConfigured(resultSetExtractorClass, ResultSetExtractor.class))
|
||||
throw new InvalidQueryConfiguration("Cannot use both rowMapperClass and resultSetExtractorClass on @Query annotation. Query method: [" + queryMethod.getName() + "] query: [" + queryMethod.getAnnotatedQuery() + "]");
|
||||
|
||||
if(!isConfigured(rowMapperClass, RowMapper.class) && !isConfigured(resultSetExtractorClass, ResultSetExtractor.class))
|
||||
return null;
|
||||
if(isConfigured(rowMapperClass, RowMapper.class)) {
|
||||
return RowMapperResultsetExtractorEither.of((RowMapper<?>) BeanUtils.instantiateClass(rowMapperClass));
|
||||
} else {
|
||||
return RowMapperResultsetExtractorEither.of((ResultSetExtractor<?>) BeanUtils.instantiateClass(resultSetExtractorClass));
|
||||
}
|
||||
}
|
||||
|
||||
return rowMapperClass == null || rowMapperClass == RowMapper.class //
|
||||
? defaultRowMapper //
|
||||
: (RowMapper<?>) BeanUtils.instantiateClass(rowMapperClass);
|
||||
private static boolean isConfigured(Class<?> rowMapperClass, Class<?> defaultClass) {
|
||||
return rowMapperClass != null && rowMapperClass != defaultClass;
|
||||
}
|
||||
|
||||
private <T> void publishAfterLoad(Iterable<T> all) {
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package org.springframework.data.jdbc.support;
|
||||
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
/**
|
||||
* Represents either a RowMapper or a ResultSetExtractor
|
||||
*
|
||||
* @author Evgeni Dimitrov
|
||||
*/
|
||||
public class RowMapperResultsetExtractorEither<T> {
|
||||
private final RowMapper<T> rowMapper;
|
||||
private final ResultSetExtractor<T> resultSetExtractor;
|
||||
|
||||
private RowMapperResultsetExtractorEither(RowMapper<T> rowMapper, ResultSetExtractor<T> resultSetExtractor) {
|
||||
this.rowMapper = rowMapper;
|
||||
this.resultSetExtractor = resultSetExtractor;
|
||||
}
|
||||
|
||||
public static RowMapperResultsetExtractorEither<?> of(RowMapper<?> rowMapper) {
|
||||
return new RowMapperResultsetExtractorEither<>(rowMapper, null);
|
||||
}
|
||||
|
||||
public boolean isRowMapper() {
|
||||
return this.rowMapper != null;
|
||||
}
|
||||
|
||||
public RowMapper<?> rowMapper() {
|
||||
return this.rowMapper;
|
||||
}
|
||||
|
||||
public static RowMapperResultsetExtractorEither<?> of(ResultSetExtractor<?> resultSetExtractor) {
|
||||
return new RowMapperResultsetExtractorEither<>(null, resultSetExtractor);
|
||||
}
|
||||
|
||||
public boolean isResultSetExtractor() {
|
||||
return this.resultSetExtractor != null;
|
||||
}
|
||||
|
||||
public ResultSetExtractor<?> resultSetExtractor() {
|
||||
return this.resultSetExtractor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("RowMapperResultsetExtractorEither[%s]", this.rowMapper != null ? this.rowMapper : this.resultSetExtractor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((resultSetExtractor == null) ? 0 : resultSetExtractor.hashCode());
|
||||
result = prime * result + ((rowMapper == null) ? 0 : rowMapper.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
RowMapperResultsetExtractorEither other = (RowMapperResultsetExtractorEither) obj;
|
||||
if (resultSetExtractor == null) {
|
||||
if (other.resultSetExtractor != null) return false;
|
||||
} else {
|
||||
if (!resultSetExtractor.equals(other.resultSetExtractor)) return false;
|
||||
}
|
||||
if (rowMapper == null) {
|
||||
if (other.rowMapper != null) return false;
|
||||
} else {
|
||||
if (!rowMapper.equals(other.rowMapper)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2017-2018 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.repository;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.jdbc.repository.config.DefaultQueryMappingConfiguration;
|
||||
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
|
||||
import org.springframework.data.jdbc.repository.query.Query;
|
||||
import org.springframework.data.jdbc.testing.TestConfiguration;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.rules.SpringClassRule;
|
||||
import org.springframework.test.context.junit4.rules.SpringMethodRule;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Very simple use cases for creation and usage of {@link ResultSetExtractor}s in JdbcRepository.
|
||||
*
|
||||
* @author Evgeni Dimitrov
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@Transactional
|
||||
public class JdbcRepositoryMapperMapResultSetExtractorIntegrationTests {
|
||||
private static String CAR_MODEL = "ResultSetExtracotr Car";
|
||||
@Configuration
|
||||
@Import(TestConfiguration.class)
|
||||
@EnableJdbcRepositories(considerNestedRepositories = true)
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
Class<?> testClass() {
|
||||
return JdbcRepositoryMapperMapResultSetExtractorIntegrationTests.class;
|
||||
}
|
||||
|
||||
@Bean
|
||||
QueryMappingConfiguration mappers() {
|
||||
return new DefaultQueryMappingConfiguration()
|
||||
.registerResultSetExtractor(Car.class, new CarResultSetExtractor());
|
||||
}
|
||||
}
|
||||
|
||||
@ClassRule public static final SpringClassRule classRule = new SpringClassRule();
|
||||
@Rule public SpringMethodRule methodRule = new SpringMethodRule();
|
||||
|
||||
@Autowired NamedParameterJdbcTemplate template;
|
||||
@Autowired CarRepository carRepository;
|
||||
|
||||
@Test // DATAJDBC-290
|
||||
public void customFindAllCarsPicksResultSetExtractorFromMapperMap() {
|
||||
carRepository.save(new Car(null, "Some model"));
|
||||
Iterable<Car> cars = carRepository.customFindAll();
|
||||
assertThat(cars).hasSize(1);
|
||||
assertThat(cars).allMatch(car -> CAR_MODEL.equals(car.getModel()));
|
||||
}
|
||||
|
||||
interface CarRepository extends CrudRepository<Car, Long> {
|
||||
@Query("select * from car")
|
||||
public List<Car> customFindAll();
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
static class Car {
|
||||
@Id
|
||||
private Long id;
|
||||
private String model;
|
||||
}
|
||||
|
||||
static class CarResultSetExtractor implements ResultSetExtractor<List<Car>> {
|
||||
|
||||
@Override
|
||||
public List<Car> extractData(ResultSet rs) throws SQLException, DataAccessException {
|
||||
return Arrays.asList(new Car(1L, CAR_MODEL));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright 2017-2018 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.repository;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.RecoverableDataAccessException;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.jdbc.repository.query.Query;
|
||||
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
|
||||
import org.springframework.data.jdbc.testing.TestConfiguration;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.rules.SpringClassRule;
|
||||
import org.springframework.test.context.junit4.rules.SpringMethodRule;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Very simple use cases for creation and usage of {@link ResultSetExtractor}s in JdbcRepository.
|
||||
*
|
||||
* @author Evgeni Dimitrov
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@Transactional
|
||||
public class JdbcRepositoryResultSetExtractorIntegrationTests {
|
||||
@Configuration
|
||||
@Import(TestConfiguration.class)
|
||||
static class Config {
|
||||
|
||||
@Autowired JdbcRepositoryFactory factory;
|
||||
|
||||
@Bean
|
||||
Class<?> testClass() {
|
||||
return JdbcRepositoryResultSetExtractorIntegrationTests.class;
|
||||
}
|
||||
|
||||
@Bean
|
||||
PersonRepository personEntityRepository() {
|
||||
return factory.getRepository(PersonRepository.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ClassRule public static final SpringClassRule classRule = new SpringClassRule();
|
||||
@Rule public SpringMethodRule methodRule = new SpringMethodRule();
|
||||
|
||||
@Autowired NamedParameterJdbcTemplate template;
|
||||
@Autowired PersonRepository personRepository;
|
||||
|
||||
@Test // DATAJDBC-290
|
||||
public void findAllPeopleWithAdressesReturnsEmptyWhenNoneFound() {
|
||||
// NOT saving anything, so DB is empty
|
||||
assertThat(personRepository.findAllPeopleWithAdresses()).isEmpty();
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-290
|
||||
public void findAllPeopleWithAdressesReturnsOnePersonWithoutAdresses() {
|
||||
personRepository.save(new Person(null, "Joe", null));
|
||||
assertThat(personRepository.findAllPeopleWithAdresses()).hasSize(1);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-290
|
||||
public void findAllPeopleWithAdressesReturnsOnePersonWithAdresses() {
|
||||
final String personName = "Joe";
|
||||
Person savedPerson = personRepository.save(new Person(null, personName, null));
|
||||
String street1 = "Klokotnitsa";
|
||||
MapSqlParameterSource paramsAddress1 = buildAddressParameters(savedPerson.getId(), street1);
|
||||
template.update("insert into address (street, person_id) values (:street, :personId)",paramsAddress1);
|
||||
String street2 = "bul. Hristo Botev";
|
||||
MapSqlParameterSource paramsAddress2 = buildAddressParameters(savedPerson.getId(), street2);
|
||||
template.update("insert into address (street, person_id) values (:street, :personId)",paramsAddress2);
|
||||
|
||||
List<Person> people = personRepository.findAllPeopleWithAdresses();
|
||||
assertThat(people).hasSize(1);
|
||||
Person person = people.get(0);
|
||||
assertThat(person.getName()).isEqualTo(personName);
|
||||
assertThat(person.getAdresses()).hasSize(2);
|
||||
assertThat(person.getAdresses()).extracting(a -> a.getStreet()).containsExactlyInAnyOrder(street1, street2);
|
||||
}
|
||||
private MapSqlParameterSource buildAddressParameters(Long id, String streetName) {
|
||||
MapSqlParameterSource params = new MapSqlParameterSource();
|
||||
params.addValue("street", streetName, Types.VARCHAR);
|
||||
params.addValue("personId", id, Types.NUMERIC);
|
||||
return params;
|
||||
}
|
||||
|
||||
interface PersonRepository extends CrudRepository<Person, Long> {
|
||||
@Query(value="select p.id, p.name, a.id addrId, a.street from person p left join address a on(p.id = a.person_id)",
|
||||
resultSetExtractorClass=PersonResultSetExtractor.class)
|
||||
public List<Person> findAllPeopleWithAdresses();
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
static class Person {
|
||||
@Id
|
||||
private Long id;
|
||||
private String name;
|
||||
private List<Address> adresses;
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
static class Address {
|
||||
@Id
|
||||
private Long id;
|
||||
private String street;
|
||||
}
|
||||
|
||||
static class PersonResultSetExtractor implements ResultSetExtractor<List<Person>> {
|
||||
|
||||
@Override
|
||||
public List<Person> extractData(ResultSet rs) throws SQLException, DataAccessException {
|
||||
Map<Long, Person> peopleById = new HashMap<>();
|
||||
while(rs.next()) {
|
||||
long personId = rs.getLong("id");
|
||||
Person currentPerson = peopleById.computeIfAbsent(personId, t -> {
|
||||
try {
|
||||
return new Person(personId, rs.getString("name"), new ArrayList<>());
|
||||
} catch (SQLException e) {
|
||||
throw new RecoverableDataAccessException("Error mapping Person", e);
|
||||
}
|
||||
});
|
||||
|
||||
if(currentPerson.getAdresses() == null) currentPerson.setAdresses(new ArrayList<>());
|
||||
long addrId = rs.getLong("addrId");
|
||||
if(!rs.wasNull()) {
|
||||
currentPerson.getAdresses().add(new Address(addrId, rs.getString("street")));
|
||||
}
|
||||
}
|
||||
|
||||
return new ArrayList<Person>(peopleById.values());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,9 @@ import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.jdbc.repository.RowMapperMap;
|
||||
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
|
||||
import org.springframework.data.jdbc.support.RowMapperResultsetExtractorEither;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
/**
|
||||
@@ -32,9 +34,9 @@ public class ConfigurableRowMapperMapUnitTests {
|
||||
@Test
|
||||
public void freshInstanceReturnsNull() {
|
||||
|
||||
RowMapperMap map = new ConfigurableRowMapperMap();
|
||||
QueryMappingConfiguration map = new DefaultQueryMappingConfiguration();
|
||||
|
||||
assertThat(map.rowMapperFor(Object.class)).isNull();
|
||||
assertThat(map.getMapper(Object.class)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -42,9 +44,19 @@ public class ConfigurableRowMapperMapUnitTests {
|
||||
|
||||
RowMapper rowMapper = mock(RowMapper.class);
|
||||
|
||||
RowMapperMap map = new ConfigurableRowMapperMap().register(Object.class, rowMapper);
|
||||
QueryMappingConfiguration map = new DefaultQueryMappingConfiguration().registerRowMapper(Object.class, rowMapper);
|
||||
|
||||
assertThat(map.rowMapperFor(Object.class)).isEqualTo(rowMapper);
|
||||
assertThat(map.getMapper(Object.class)).isEqualTo(RowMapperResultsetExtractorEither.of(rowMapper));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnsConfiguredInstanceResultSetExtractorForClass() {
|
||||
|
||||
ResultSetExtractor resultSetExtractor = mock(ResultSetExtractor.class);
|
||||
|
||||
QueryMappingConfiguration map = new DefaultQueryMappingConfiguration().registerResultSetExtractor(Object.class, resultSetExtractor);
|
||||
|
||||
assertThat(map.getMapper(Object.class)).isEqualTo(RowMapperResultsetExtractorEither.of(resultSetExtractor));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -52,10 +64,21 @@ public class ConfigurableRowMapperMapUnitTests {
|
||||
|
||||
RowMapper rowMapper = mock(RowMapper.class);
|
||||
|
||||
RowMapperMap map = new ConfigurableRowMapperMap().register(Number.class, rowMapper);
|
||||
QueryMappingConfiguration map = new DefaultQueryMappingConfiguration().registerRowMapper(Number.class, rowMapper);
|
||||
|
||||
assertThat(map.rowMapperFor(Integer.class)).isNull();
|
||||
assertThat(map.rowMapperFor(String.class)).isNull();
|
||||
assertThat(map.getMapper(Integer.class)).isNull();
|
||||
assertThat(map.getMapper(String.class)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnsNullResultSetExtractorForClassNotConfigured() {
|
||||
|
||||
ResultSetExtractor resultSetExtractor = mock(ResultSetExtractor.class);
|
||||
|
||||
QueryMappingConfiguration map = new DefaultQueryMappingConfiguration().registerResultSetExtractor(Number.class, resultSetExtractor);
|
||||
|
||||
assertThat(map.getMapper(Integer.class)).isNull();
|
||||
assertThat(map.getMapper(String.class)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -63,9 +86,19 @@ public class ConfigurableRowMapperMapUnitTests {
|
||||
|
||||
RowMapper rowMapper = mock(RowMapper.class);
|
||||
|
||||
RowMapperMap map = new ConfigurableRowMapperMap().register(String.class, rowMapper);
|
||||
QueryMappingConfiguration map = new DefaultQueryMappingConfiguration().registerRowMapper(String.class, rowMapper);
|
||||
|
||||
assertThat(map.rowMapperFor(Object.class)).isEqualTo(rowMapper);
|
||||
assertThat(map.getMapper(Object.class)).isEqualTo(RowMapperResultsetExtractorEither.of(rowMapper));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnsInstanceOfResultSetExtractorRegisteredForSubClass() {
|
||||
|
||||
ResultSetExtractor resultSetExtractor = mock(ResultSetExtractor.class);
|
||||
|
||||
QueryMappingConfiguration map = new DefaultQueryMappingConfiguration().registerResultSetExtractor(String.class, resultSetExtractor);
|
||||
|
||||
assertThat(map.getMapper(Object.class)).isEqualTo(RowMapperResultsetExtractorEither.of(resultSetExtractor));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -73,12 +106,25 @@ public class ConfigurableRowMapperMapUnitTests {
|
||||
|
||||
RowMapper rowMapper = mock(RowMapper.class);
|
||||
|
||||
RowMapperMap map = new ConfigurableRowMapperMap() //
|
||||
.register(Object.class, mock(RowMapper.class)) //
|
||||
.register(Integer.class, rowMapper) //
|
||||
.register(Number.class, mock(RowMapper.class));
|
||||
QueryMappingConfiguration map = new DefaultQueryMappingConfiguration() //
|
||||
.registerRowMapper(Object.class, mock(RowMapper.class)) //
|
||||
.registerRowMapper(Integer.class, rowMapper) //
|
||||
.registerRowMapper(Number.class, mock(RowMapper.class));
|
||||
|
||||
assertThat(map.rowMapperFor(Integer.class)).isEqualTo(rowMapper);
|
||||
assertThat(map.getMapper(Integer.class)).isEqualTo(RowMapperResultsetExtractorEither.of(rowMapper));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prefersExactResultSetExtractorTypeMatchClass() {
|
||||
|
||||
ResultSetExtractor resultSetExtractor = mock(ResultSetExtractor.class);
|
||||
|
||||
QueryMappingConfiguration map = new DefaultQueryMappingConfiguration() //
|
||||
.registerResultSetExtractor(Object.class, mock(ResultSetExtractor.class)) //
|
||||
.registerResultSetExtractor(Integer.class, resultSetExtractor) //
|
||||
.registerResultSetExtractor(Number.class, mock(ResultSetExtractor.class));
|
||||
|
||||
assertThat(map.getMapper(Integer.class)).isEqualTo(RowMapperResultsetExtractorEither.of(resultSetExtractor));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -86,10 +132,22 @@ public class ConfigurableRowMapperMapUnitTests {
|
||||
|
||||
RowMapper rowMapper = mock(RowMapper.class);
|
||||
|
||||
RowMapperMap map = new ConfigurableRowMapperMap() //
|
||||
.register(Integer.class, mock(RowMapper.class)) //
|
||||
.register(Number.class, rowMapper);
|
||||
QueryMappingConfiguration map = new DefaultQueryMappingConfiguration() //
|
||||
.registerRowMapper(Integer.class, mock(RowMapper.class)) //
|
||||
.registerRowMapper(Number.class, rowMapper);
|
||||
|
||||
assertThat(map.rowMapperFor(Object.class)).isEqualTo(rowMapper);
|
||||
assertThat(map.getMapper(Object.class)).isEqualTo(RowMapperResultsetExtractorEither.of(rowMapper));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prefersLatestRegistrationOfResultSetExtractorForSuperTypeMatch() {
|
||||
|
||||
ResultSetExtractor resultSetExtractor = mock(ResultSetExtractor.class);
|
||||
|
||||
QueryMappingConfiguration map = new DefaultQueryMappingConfiguration() //
|
||||
.registerResultSetExtractor(Integer.class, mock(ResultSetExtractor.class)) //
|
||||
.registerResultSetExtractor(Number.class, resultSetExtractor);
|
||||
|
||||
assertThat(map.getMapper(Object.class)).isEqualTo(RowMapperResultsetExtractorEither.of(resultSetExtractor));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,12 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.FilterType;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.jdbc.repository.RowMapperMap;
|
||||
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
|
||||
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositoriesIntegrationTests.TestConfiguration;
|
||||
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBean;
|
||||
import org.springframework.data.jdbc.support.RowMapperResultsetExtractorEither;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
@@ -49,16 +51,17 @@ import org.springframework.util.ReflectionUtils;
|
||||
@ContextConfiguration(classes = TestConfiguration.class)
|
||||
public class EnableJdbcRepositoriesIntegrationTests {
|
||||
|
||||
static final Field ROW_MAPPER_MAP = ReflectionUtils.findField(JdbcRepositoryFactoryBean.class, "rowMapperMap");
|
||||
static final Field MAPPER_MAP = ReflectionUtils.findField(JdbcRepositoryFactoryBean.class, "mapperMap");
|
||||
public static final RowMapper DUMMY_ENTITY_ROW_MAPPER = mock(RowMapper.class);
|
||||
public static final RowMapper STRING_ROW_MAPPER = mock(RowMapper.class);
|
||||
public static final ResultSetExtractor<Integer> INTEGER_RESULT_SET_EXTRACTOR = mock(ResultSetExtractor.class);
|
||||
|
||||
@Autowired JdbcRepositoryFactoryBean factoryBean;
|
||||
@Autowired DummyRepository repository;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
ROW_MAPPER_MAP.setAccessible(true);
|
||||
MAPPER_MAP.setAccessible(true);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-100
|
||||
@@ -71,13 +74,25 @@ public class EnableJdbcRepositoriesIntegrationTests {
|
||||
assertThat(all).isNotNull();
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-290
|
||||
public void customResultSetExtractorConfigurationGetsPickedUp() {
|
||||
QueryMappingConfiguration mapping = (QueryMappingConfiguration) ReflectionUtils.getField(MAPPER_MAP, factoryBean);
|
||||
assertThat(mapping.getMapper(Integer.class)).isEqualTo(RowMapperResultsetExtractorEither.of(INTEGER_RESULT_SET_EXTRACTOR));
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-290
|
||||
public void customResultSetExtractorConfigurationIsNotPickedUpIfRowMapperIsRegisteredForTheSameType() {
|
||||
QueryMappingConfiguration mapping = (QueryMappingConfiguration) ReflectionUtils.getField(MAPPER_MAP, factoryBean);
|
||||
assertThat(mapping.getMapper(String.class).isResultSetExtractor()).isFalse();
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-166
|
||||
public void customRowMapperConfigurationGetsPickedUp() {
|
||||
|
||||
RowMapperMap mapping = (RowMapperMap) ReflectionUtils.getField(ROW_MAPPER_MAP, factoryBean);
|
||||
QueryMappingConfiguration mapping = (QueryMappingConfiguration) ReflectionUtils.getField(MAPPER_MAP, factoryBean);
|
||||
|
||||
assertThat(mapping.rowMapperFor(String.class)).isEqualTo(STRING_ROW_MAPPER);
|
||||
assertThat(mapping.rowMapperFor(DummyEntity.class)).isEqualTo(DUMMY_ENTITY_ROW_MAPPER);
|
||||
assertThat(mapping.getMapper(String.class)).isEqualTo(RowMapperResultsetExtractorEither.of(STRING_ROW_MAPPER));
|
||||
assertThat(mapping.getMapper(DummyEntity.class)).isEqualTo(RowMapperResultsetExtractorEither.of(DUMMY_ENTITY_ROW_MAPPER));
|
||||
}
|
||||
|
||||
interface DummyRepository extends CrudRepository<DummyEntity, Long> {
|
||||
@@ -100,10 +115,11 @@ public class EnableJdbcRepositoriesIntegrationTests {
|
||||
}
|
||||
|
||||
@Bean
|
||||
RowMapperMap rowMappers() {
|
||||
return new ConfigurableRowMapperMap() //
|
||||
.register(DummyEntity.class, DUMMY_ENTITY_ROW_MAPPER) //
|
||||
.register(String.class, STRING_ROW_MAPPER);
|
||||
QueryMappingConfiguration rowMappers() {
|
||||
return new DefaultQueryMappingConfiguration() //
|
||||
.registerRowMapper(DummyEntity.class, DUMMY_ENTITY_ROW_MAPPER) //
|
||||
.registerRowMapper(String.class, STRING_ROW_MAPPER)
|
||||
.registerResultSetExtractor(Integer.class, INTEGER_RESULT_SET_EXTRACTOR);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.data.jdbc.core.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.repository.RowMapperMap;
|
||||
import org.springframework.data.jdbc.repository.config.ConfigurableRowMapperMap;
|
||||
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
|
||||
import org.springframework.data.jdbc.repository.config.DefaultQueryMappingConfiguration;
|
||||
import org.springframework.data.jdbc.repository.query.Query;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.relational.core.conversion.BasicRelationalConverter;
|
||||
@@ -37,6 +37,7 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext
|
||||
import org.springframework.data.repository.core.NamedQueries;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
|
||||
@@ -74,7 +75,7 @@ public class JdbcQueryLookupStrategyUnitTests {
|
||||
public void typeBasedRowMapperGetsUsedForQuery() {
|
||||
|
||||
RowMapper<? extends NumberFormat> numberFormatMapper = mock(RowMapper.class);
|
||||
RowMapperMap rowMapperMap = new ConfigurableRowMapperMap().register(NumberFormat.class, numberFormatMapper);
|
||||
QueryMappingConfiguration rowMapperMap = new DefaultQueryMappingConfiguration().registerRowMapper(NumberFormat.class, numberFormatMapper);
|
||||
|
||||
RepositoryQuery repositoryQuery = getRepositoryQuery("returningNumberFormat", rowMapperMap);
|
||||
|
||||
@@ -82,8 +83,22 @@ public class JdbcQueryLookupStrategyUnitTests {
|
||||
|
||||
verify(operations).queryForObject(anyString(), any(SqlParameterSource.class), eq(numberFormatMapper));
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-290
|
||||
@SuppressWarnings("unchecked")
|
||||
public void typeBasedResultSetExtractorGetsUsedForQuery() {
|
||||
|
||||
private RepositoryQuery getRepositoryQuery(String name, RowMapperMap rowMapperMap) {
|
||||
ResultSetExtractor<? extends NumberFormat> numberFormatMapper = mock(ResultSetExtractor.class);
|
||||
QueryMappingConfiguration rowMapperMap = new DefaultQueryMappingConfiguration().registerResultSetExtractor(NumberFormat.class, numberFormatMapper);
|
||||
|
||||
RepositoryQuery repositoryQuery = getRepositoryQuery("returningNumberFormat", rowMapperMap);
|
||||
|
||||
repositoryQuery.execute(new Object[] {});
|
||||
|
||||
verify(operations).query(anyString(), any(SqlParameterSource.class), eq(numberFormatMapper));
|
||||
}
|
||||
|
||||
private RepositoryQuery getRepositoryQuery(String name, QueryMappingConfiguration rowMapperMap) {
|
||||
|
||||
JdbcQueryLookupStrategy queryLookupStrategy = new JdbcQueryLookupStrategy(publisher, mappingContext, converter, accessStrategy,
|
||||
rowMapperMap, operations);
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.jdbc.core.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.repository.RowMapperMap;
|
||||
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
|
||||
import org.springframework.data.relational.core.conversion.BasicRelationalConverter;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
@@ -100,7 +100,7 @@ public class JdbcRepositoryFactoryBeanUnitTests {
|
||||
assertThat(factoryBean.getObject()).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(factoryBean, "dataAccessStrategy"))
|
||||
.isInstanceOf(DefaultDataAccessStrategy.class);
|
||||
assertThat(ReflectionTestUtils.getField(factoryBean, "rowMapperMap")).isEqualTo(RowMapperMap.EMPTY);
|
||||
assertThat(ReflectionTestUtils.getField(factoryBean, "mapperMap")).isEqualTo(QueryMappingConfiguration.EMPTY);
|
||||
}
|
||||
|
||||
private static class DummyEntity {
|
||||
|
||||
@@ -22,16 +22,20 @@ import static org.mockito.ArgumentMatchers.isA;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.jdbc.support.RowMapperResultsetExtractorEither;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.event.AfterLoadEvent;
|
||||
import org.springframework.data.repository.query.DefaultParameters;
|
||||
import org.springframework.data.repository.query.Parameters;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
|
||||
@@ -48,6 +52,7 @@ public class JdbcRepositoryQueryUnitTests {
|
||||
JdbcQueryMethod queryMethod;
|
||||
|
||||
RowMapper<?> defaultRowMapper;
|
||||
ResultSetExtractor<?> defaultResultSetExtractor;
|
||||
JdbcRepositoryQuery query;
|
||||
NamedParameterJdbcOperations operations;
|
||||
ApplicationEventPublisher publisher;
|
||||
@@ -67,7 +72,7 @@ public class JdbcRepositoryQueryUnitTests {
|
||||
this.publisher = mock(ApplicationEventPublisher.class);
|
||||
this.context = mock(RelationalMappingContext.class, RETURNS_DEEP_STUBS);
|
||||
|
||||
this.query = new JdbcRepositoryQuery(publisher, context, queryMethod, operations, defaultRowMapper);
|
||||
this.query = new JdbcRepositoryQuery(publisher, context, queryMethod, operations, RowMapperResultsetExtractorEither.of(defaultRowMapper));
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-165
|
||||
@@ -106,11 +111,24 @@ public class JdbcRepositoryQueryUnitTests {
|
||||
doReturn("some sql statement").when(queryMethod).getAnnotatedQuery();
|
||||
doReturn(CustomRowMapper.class).when(queryMethod).getRowMapperClass();
|
||||
|
||||
new JdbcRepositoryQuery(publisher, context, queryMethod, operations, defaultRowMapper).execute(new Object[] {});
|
||||
new JdbcRepositoryQuery(publisher, context, queryMethod, operations, RowMapperResultsetExtractorEither.of(defaultRowMapper)).execute(new Object[] {});
|
||||
|
||||
verify(operations) //
|
||||
.queryForObject(anyString(), any(SqlParameterSource.class), isA(CustomRowMapper.class));
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-290
|
||||
public void customResultSetExtractorIsUsedWhenSpecified() {
|
||||
|
||||
doReturn("some sql statement").when(queryMethod).getAnnotatedQuery();
|
||||
doReturn(CustomResultSetExtractor.class).when(queryMethod).getResultSetExtractorClass();
|
||||
|
||||
new JdbcRepositoryQuery(publisher, context, queryMethod, operations, RowMapperResultsetExtractorEither.of(defaultRowMapper)).execute(new Object[] {});
|
||||
|
||||
verify(operations) //
|
||||
.query(anyString(), any(SqlParameterSource.class), isA(CustomResultSetExtractor.class));
|
||||
}
|
||||
|
||||
|
||||
@Test // DATAJDBC-263
|
||||
public void publishesSingleEventWhenQueryReturnsSingleAggregate() {
|
||||
@@ -121,7 +139,7 @@ public class JdbcRepositoryQueryUnitTests {
|
||||
doReturn(true).when(context).hasPersistentEntityFor(DummyEntity.class);
|
||||
when(context.getRequiredPersistentEntity(DummyEntity.class).getIdentifierAccessor(any()).getRequiredIdentifier()).thenReturn("some identifier");
|
||||
|
||||
new JdbcRepositoryQuery(publisher, context, queryMethod, operations, defaultRowMapper).execute(new Object[] {});
|
||||
new JdbcRepositoryQuery(publisher, context, queryMethod, operations, RowMapperResultsetExtractorEither.of(defaultRowMapper)).execute(new Object[] {});
|
||||
|
||||
verify(publisher).publishEvent(any(AfterLoadEvent.class));
|
||||
}
|
||||
@@ -135,7 +153,7 @@ public class JdbcRepositoryQueryUnitTests {
|
||||
doReturn(true).when(context).hasPersistentEntityFor(DummyEntity.class);
|
||||
when(context.getRequiredPersistentEntity(DummyEntity.class).getIdentifierAccessor(any()).getRequiredIdentifier()).thenReturn("some identifier");
|
||||
|
||||
new JdbcRepositoryQuery(publisher, context, queryMethod, operations, defaultRowMapper).execute(new Object[] {});
|
||||
new JdbcRepositoryQuery(publisher, context, queryMethod, operations, RowMapperResultsetExtractorEither.of(defaultRowMapper)).execute(new Object[] {});
|
||||
|
||||
verify(publisher, times(2)).publishEvent(any(AfterLoadEvent.class));
|
||||
}
|
||||
@@ -153,6 +171,14 @@ public class JdbcRepositoryQueryUnitTests {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class CustomResultSetExtractor implements ResultSetExtractor<Object> {
|
||||
|
||||
@Override
|
||||
public Object extractData(ResultSet rs) throws SQLException, DataAccessException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class DummyEntity {
|
||||
private Long id;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
CREATE TABLE car ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, model VARCHAR(100));
|
||||
@@ -0,0 +1 @@
|
||||
CREATE TABLE car ( id INT NOT NULL AUTO_INCREMENT, model VARCHAR(100), PRIMARY KEY (id));
|
||||
@@ -0,0 +1,2 @@
|
||||
DROP TABLE IF EXISTS car;
|
||||
CREATE TABLE car ( id int IDENTITY(1,1) PRIMARY KEY, model VARCHAR(100));
|
||||
@@ -0,0 +1 @@
|
||||
CREATE TABLE car ( id INT NOT NULL AUTO_INCREMENT, model VARCHAR(100), PRIMARY KEY (id));
|
||||
@@ -0,0 +1,2 @@
|
||||
DROP TABLE car;
|
||||
CREATE TABLE car ( id SERIAL PRIMARY KEY, model VARCHAR(100));
|
||||
@@ -0,0 +1,3 @@
|
||||
CREATE TABLE person ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, name VARCHAR(100));
|
||||
CREATE TABLE address ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, street VARCHAR(100), person_id BIGINT);
|
||||
ALTER TABLE address ADD FOREIGN KEY (person_id) REFERENCES person(id);
|
||||
@@ -0,0 +1,3 @@
|
||||
CREATE TABLE person ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(100), PRIMARY KEY (id));
|
||||
CREATE TABLE address ( id INT NOT NULL AUTO_INCREMENT, street VARCHAR(100), person_id INT, PRIMARY KEY (id));
|
||||
ALTER TABLE address ADD FOREIGN KEY (person_id) REFERENCES person(id);
|
||||
@@ -0,0 +1,5 @@
|
||||
DROP TABLE IF EXISTS person;
|
||||
DROP TABLE IF EXISTS address;
|
||||
CREATE TABLE person ( id int IDENTITY(1,1) PRIMARY KEY, name VARCHAR(100));
|
||||
CREATE TABLE address ( id int IDENTITY(1,1) PRIMARY KEY, street VARCHAR(100), person_id INT);
|
||||
ALTER TABLE address ADD FOREIGN KEY (person_id) REFERENCES person(id);
|
||||
@@ -0,0 +1,3 @@
|
||||
CREATE TABLE person ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(100), PRIMARY KEY (id));
|
||||
CREATE TABLE address ( id INT NOT NULL AUTO_INCREMENT, street VARCHAR(100), person_id INT, PRIMARY KEY (id));
|
||||
ALTER TABLE address ADD FOREIGN KEY (person_id) REFERENCES person(id);
|
||||
@@ -0,0 +1,5 @@
|
||||
DROP TABLE person;
|
||||
DROP TABLE address;
|
||||
CREATE TABLE person ( id SERIAL PRIMARY KEY, name VARCHAR(100));
|
||||
CREATE TABLE address ( id SERIAL PRIMARY KEY, street VARCHAR(100), person_id INT);
|
||||
ALTER TABLE address ADD FOREIGN KEY (person_id) REFERENCES person(id);
|
||||
Reference in New Issue
Block a user