Add support for RelationalManagedTypes.

See #1269
This commit is contained in:
Mark Paluch
2022-07-06 15:05:56 +02:00
parent 59a1761dbc
commit 58bb158004
7 changed files with 272 additions and 5 deletions

View File

@@ -16,33 +16,43 @@
package org.springframework.data.jdbc.repository.config;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.jdbc.core.JdbcAggregateOperations;
import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
import org.springframework.data.jdbc.core.convert.*;
import org.springframework.data.jdbc.core.convert.JdbcArrayColumns;
import org.springframework.data.jdbc.core.dialect.JdbcDialect;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.relational.RelationalManagedTypes;
import org.springframework.data.relational.core.conversion.RelationalConverter;
import org.springframework.data.relational.core.dialect.Dialect;
import org.springframework.data.relational.core.mapping.NamingStrategy;
import org.springframework.data.relational.core.mapping.Table;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* Beans that must be registered for Spring Data JDBC to work.
@@ -63,19 +73,50 @@ public class AbstractJdbcConfiguration implements ApplicationContextAware {
private ApplicationContext applicationContext;
/**
* Returns the base packages to scan for JDBC mapped entities at startup. Returns the package name of the
* configuration class' (the concrete class, not this one here) by default. So if you have a
* {@code com.acme.AppConfig} extending {@link AbstractJdbcConfiguration} the base package will be considered
* {@code com.acme} unless the method is overridden to implement alternate behavior.
*
* @return the base packages to scan for mapped {@link Table} classes or an empty collection to not enable scanning
* for entities.
* @since 3.0
*/
protected Collection<String> getMappingBasePackages() {
Package mappingBasePackage = getClass().getPackage();
return Collections.singleton(mappingBasePackage == null ? null : mappingBasePackage.getName());
}
/**
* Returns the a {@link RelationalManagedTypes} object holding the initial entity set.
*
* @return new instance of {@link RelationalManagedTypes}.
* @throws ClassNotFoundException
* @since 3.0
*/
@Bean
public RelationalManagedTypes jdbcManagedTypes() throws ClassNotFoundException {
return RelationalManagedTypes.fromIterable(getInitialEntitySet());
}
/**
* Register a {@link JdbcMappingContext} and apply an optional {@link NamingStrategy}.
*
* @param namingStrategy optional {@link NamingStrategy}. Use {@link NamingStrategy#INSTANCE} as fallback.
* @param customConversions see {@link #jdbcCustomConversions()}.
* @param jdbcManagedTypes JDBC managed types, typically discovered through {@link #jdbcManagedTypes() an entity
* scan}.
* @return must not be {@literal null}.
*/
@Bean
public JdbcMappingContext jdbcMappingContext(Optional<NamingStrategy> namingStrategy,
JdbcCustomConversions customConversions) {
JdbcCustomConversions customConversions, RelationalManagedTypes jdbcManagedTypes) {
JdbcMappingContext mappingContext = new JdbcMappingContext(namingStrategy.orElse(NamingStrategy.INSTANCE));
mappingContext.setSimpleTypeHolder(customConversions.getSimpleTypeHolder());
mappingContext.setManagedTypes(jdbcManagedTypes);
return mappingContext;
}
@@ -190,4 +231,56 @@ public class AbstractJdbcConfiguration implements ApplicationContextAware {
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* Scans the mapping base package for classes annotated with {@link Table}. By default, it scans for entities in all
* packages returned by {@link #getMappingBasePackages()}.
*
* @see #getMappingBasePackages()
* @return
* @throws ClassNotFoundException
* @since 3.0
*/
protected Set<Class<?>> getInitialEntitySet() throws ClassNotFoundException {
Set<Class<?>> initialEntitySet = new HashSet<>();
for (String basePackage : getMappingBasePackages()) {
initialEntitySet.addAll(scanForEntities(basePackage));
}
return initialEntitySet;
}
/**
* Scans the given base package for entities, i.e. JDBC-specific types annotated with {@link Table}.
*
* @param basePackage must not be {@literal null}.
* @return
* @throws ClassNotFoundException
* @since 3.0
*/
protected Set<Class<?>> scanForEntities(String basePackage) throws ClassNotFoundException {
if (!StringUtils.hasText(basePackage)) {
return Collections.emptySet();
}
Set<Class<?>> initialEntitySet = new HashSet<>();
if (StringUtils.hasText(basePackage)) {
ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(
false);
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Table.class));
for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) {
initialEntitySet
.add(ClassUtils.forName(candidate.getBeanClassName(), AbstractJdbcConfiguration.class.getClassLoader()));
}
}
return initialEntitySet;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,6 +37,7 @@ import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.relational.RelationalManagedTypes;
import org.springframework.data.relational.core.dialect.Dialect;
import org.springframework.data.relational.core.dialect.LimitClause;
import org.springframework.data.relational.core.dialect.LockClause;
@@ -44,13 +45,15 @@ import org.springframework.data.relational.core.sql.render.SelectRenderContext;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.test.util.ReflectionTestUtils;
/**
* Integration tests for {@link AbstractJdbcConfiguration}.
*
* @author Oliver Drotbohm
* @author Mark Paluch
*/
public class AbstractJdbcConfigurationIntegrationTests {
class AbstractJdbcConfigurationIntegrationTests {
@Test // DATAJDBC-395
void configuresInfrastructureComponents() {
@@ -97,7 +100,22 @@ public class AbstractJdbcConfigurationIntegrationTests {
}, AbstractJdbcConfigurationUnderTest.class, Infrastructure.class);
}
protected static void assertApplicationContext(Consumer<ConfigurableApplicationContext> verification,
@Test // GH-1269
void detectsInitialEntities() {
assertApplicationContext(context -> {
JdbcMappingContext mappingContext = context.getBean(JdbcMappingContext.class);
RelationalManagedTypes managedTypes = (RelationalManagedTypes) ReflectionTestUtils.getField(mappingContext,
"managedTypes");
assertThat(managedTypes.toList()).contains(JdbcRepositoryConfigExtensionUnitTests.Sample.class,
TopLevelEntity.class);
}, AbstractJdbcConfigurationUnderTest.class, Infrastructure.class);
}
static void assertApplicationContext(Consumer<ConfigurableApplicationContext> verification,
Class<?>... configurationClasses) {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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.jdbc.repository.config;
import org.springframework.data.relational.core.mapping.Table;
/**
* Empty test entity annotated with {@code @Table}.
*
* @author Mark Paluch
*/
@Table
class TopLevelEntity {}

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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.relational;
import java.util.Arrays;
import java.util.function.Consumer;
import org.springframework.data.domain.ManagedTypes;
/**
* Relational-specific extension to {@link ManagedTypes}.
*
* @author Mark Paluch
* @since 3.0
*/
public final class RelationalManagedTypes implements ManagedTypes {
private final ManagedTypes delegate;
private RelationalManagedTypes(ManagedTypes types) {
this.delegate = types;
}
/**
* Wraps an existing {@link ManagedTypes} object with {@link RelationalManagedTypes}.
*
* @param managedTypes
* @return
*/
public static RelationalManagedTypes from(ManagedTypes managedTypes) {
return new RelationalManagedTypes(managedTypes);
}
/**
* Factory method used to construct {@link RelationalManagedTypes} from the given array of {@link Class types}.
*
* @param types array of {@link Class types} used to initialize the {@link ManagedTypes}; must not be {@literal null}.
* @return new instance of {@link RelationalManagedTypes} initialized from {@link Class types}.
*/
public static RelationalManagedTypes from(Class<?>... types) {
return fromIterable(Arrays.asList(types));
}
/**
* Factory method used to construct {@link RelationalManagedTypes} from the given, required {@link Iterable} of
* {@link Class types}.
*
* @param types {@link Iterable} of {@link Class types} used to initialize the {@link ManagedTypes}; must not be
* {@literal null}.
* @return new instance of {@link RelationalManagedTypes} initialized the given, required {@link Iterable} of
* {@link Class types}.
*/
public static RelationalManagedTypes fromIterable(Iterable<? extends Class<?>> types) {
return from(ManagedTypes.fromIterable(types));
}
/**
* Factory method to return an empty {@link RelationalManagedTypes} object.
*
* @return an empty {@link RelationalManagedTypes} object.
*/
public static RelationalManagedTypes empty() {
return from(ManagedTypes.empty());
}
@Override
public void forEach(Consumer<Class<?>> action) {
delegate.forEach(action);
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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.relational.aot;
import org.springframework.data.aot.ManagedTypesBeanRegistrationAotProcessor;
import org.springframework.data.relational.RelationalManagedTypes;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
/**
* Relational-specific extension to {@link ManagedTypesBeanRegistrationAotProcessor}.
*
* @author Mark Paluch
* @since 3.0
*/
class RelationalManagedTypesBeanRegistrationAotProcessor extends ManagedTypesBeanRegistrationAotProcessor {
protected boolean isMatch(@Nullable Class<?> beanType, @Nullable String beanName) {
return this.matchesByType(beanType);
}
protected boolean matchesByType(@Nullable Class<?> beanType) {
return beanType != null && ClassUtils.isAssignable(RelationalManagedTypes.class, beanType);
}
}

View File

@@ -0,0 +1,7 @@
/**
* Ahead of Time processing utilities for Spring Data Relational.
*/
@NonNullApi
package org.springframework.data.relational.aot;
import org.springframework.lang.NonNullApi;

View File

@@ -0,0 +1,2 @@
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
org.springframework.data.relational.aot.RelationalManagedTypesBeanRegistrationAotProcessor