Browse Source

Add support for RelationalManagedTypes.

See #1269
pull/1298/head
Mark Paluch 4 years ago
parent
commit
58bb158004
No known key found for this signature in database
GPG Key ID: 4406B84C1661DCD1
  1. 97
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java
  2. 24
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfigurationIntegrationTests.java
  3. 26
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/TopLevelEntity.java
  4. 83
      spring-data-relational/src/main/java/org/springframework/data/relational/RelationalManagedTypes.java
  5. 38
      spring-data-relational/src/main/java/org/springframework/data/relational/aot/RelationalManagedTypesBeanRegistrationAotProcessor.java
  6. 7
      spring-data-relational/src/main/java/org/springframework/data/relational/aot/package-info.java
  7. 2
      spring-data-relational/src/main/resources/META-INF/spring/aot.factories

97
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java

@ -16,33 +16,43 @@
package org.springframework.data.jdbc.repository.config; package org.springframework.data.jdbc.repository.config;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.data.convert.CustomConversions; import org.springframework.data.convert.CustomConversions;
import org.springframework.data.jdbc.core.JdbcAggregateOperations; import org.springframework.data.jdbc.core.JdbcAggregateOperations;
import org.springframework.data.jdbc.core.JdbcAggregateTemplate; import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
import org.springframework.data.jdbc.core.convert.*; 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.dialect.JdbcDialect;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes; import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes;
import org.springframework.data.mapping.model.SimpleTypeHolder; 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.conversion.RelationalConverter;
import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.dialect.Dialect;
import org.springframework.data.relational.core.mapping.NamingStrategy; 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.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. * Beans that must be registered for Spring Data JDBC to work.
@ -63,19 +73,50 @@ public class AbstractJdbcConfiguration implements ApplicationContextAware {
private ApplicationContext applicationContext; 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}. * Register a {@link JdbcMappingContext} and apply an optional {@link NamingStrategy}.
* *
* @param namingStrategy optional {@link NamingStrategy}. Use {@link NamingStrategy#INSTANCE} as fallback. * @param namingStrategy optional {@link NamingStrategy}. Use {@link NamingStrategy#INSTANCE} as fallback.
* @param customConversions see {@link #jdbcCustomConversions()}. * @param customConversions see {@link #jdbcCustomConversions()}.
* @param jdbcManagedTypes JDBC managed types, typically discovered through {@link #jdbcManagedTypes() an entity
* scan}.
* @return must not be {@literal null}. * @return must not be {@literal null}.
*/ */
@Bean @Bean
public JdbcMappingContext jdbcMappingContext(Optional<NamingStrategy> namingStrategy, public JdbcMappingContext jdbcMappingContext(Optional<NamingStrategy> namingStrategy,
JdbcCustomConversions customConversions) { JdbcCustomConversions customConversions, RelationalManagedTypes jdbcManagedTypes) {
JdbcMappingContext mappingContext = new JdbcMappingContext(namingStrategy.orElse(NamingStrategy.INSTANCE)); JdbcMappingContext mappingContext = new JdbcMappingContext(namingStrategy.orElse(NamingStrategy.INSTANCE));
mappingContext.setSimpleTypeHolder(customConversions.getSimpleTypeHolder()); mappingContext.setSimpleTypeHolder(customConversions.getSimpleTypeHolder());
mappingContext.setManagedTypes(jdbcManagedTypes);
return mappingContext; return mappingContext;
} }
@ -190,4 +231,56 @@ public class AbstractJdbcConfiguration implements ApplicationContextAware {
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext; 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;
}
} }

24
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfigurationIntegrationTests.java

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.JdbcConverter;
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; 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.Dialect;
import org.springframework.data.relational.core.dialect.LimitClause; import org.springframework.data.relational.core.dialect.LimitClause;
import org.springframework.data.relational.core.dialect.LockClause; 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.JdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.test.util.ReflectionTestUtils;
/** /**
* Integration tests for {@link AbstractJdbcConfiguration}. * Integration tests for {@link AbstractJdbcConfiguration}.
* *
* @author Oliver Drotbohm * @author Oliver Drotbohm
* @author Mark Paluch
*/ */
public class AbstractJdbcConfigurationIntegrationTests { class AbstractJdbcConfigurationIntegrationTests {
@Test // DATAJDBC-395 @Test // DATAJDBC-395
void configuresInfrastructureComponents() { void configuresInfrastructureComponents() {
@ -97,7 +100,22 @@ public class AbstractJdbcConfigurationIntegrationTests {
}, AbstractJdbcConfigurationUnderTest.class, Infrastructure.class); }, 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) { Class<?>... configurationClasses) {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) { try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {

26
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/TopLevelEntity.java

@ -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 {}

83
spring-data-relational/src/main/java/org/springframework/data/relational/RelationalManagedTypes.java

@ -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);
}
}

38
spring-data-relational/src/main/java/org/springframework/data/relational/aot/RelationalManagedTypesBeanRegistrationAotProcessor.java

@ -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);
}
}

7
spring-data-relational/src/main/java/org/springframework/data/relational/aot/package-info.java

@ -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;

2
spring-data-relational/src/main/resources/META-INF/spring/aot.factories

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