Spring Data JPA – Come impostare a livello di programmazione i pacchetti base JpaRepository

Quando si definisce un EntityManager in una class Spring Config Java, è ansible aggiungere i pacchetti base per eseguire la scansione delle classi Entity chiamando un metodo sul builder corrispondente:

public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) { // Some other configuration here builder.packages("org.foo.bar", "org.foo.baz"); return builder.build(); } 

Ho bisogno di qualcosa di simile per i luoghi in cui Spring cerca le interfacce del repository. Il solito modo è usare l’annotazione @EnableJpaRepositories :

 @EnableJpaRepositories(basePackages = {"org.foo.barbaz"}) 

Ma mi piacerebbe avere un modo dinamico per definire questi pacchetti in modo simile al modo sopra per le posizioni delle Entità. Qualcosa come questo:

 public SomeJpaRepositoryFactoryBean entityManagerFactory(JpaRepositoryFactoryBuilder builder) { // Some other configuration here builder.packages("org.foo.barbaz"); return builder.build(); } 

C’è un modo per farlo con l’attuale release Spring Data JPA, semplicemente non è pensato per essere fatto in questo modo?

È ansible utilizzare @AutoConfigurationPackage annotazione @AutoConfigurationPackage per aggiungere il pacchetto del modulo figlio ai pacchetti di scansione.

  1. Rimuovi tutti @EnableJpaRepositories dal tuo modulo figlio
  2. Aggiungi la class @AutoConfigurationPackage alla directory principale del tuo modulo figlio (simile a @SpringBootApplication , devi mettere questa class nella directory più in alto per esaminare tutti i sottopacchetti):

     @AutoConfigurationPackage public class ChildConfiguration { } 
  3. Creare il file spring.factories in /resources/META-INF/spring.factories del modulo figlio e aggiungere la class di configurazione:

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.child.package.ChildConfiguration

Ora puoi @Autowired tuo repository dal tuo progetto principale. (Testato e lavorato)

Senza avvio a molla (impostazione Spring MVC pianura)

@EnableJpaRepositories può essere utilizzato su più di una class @Configuration . Detto questo, ogni modulo può dichiarare i propri repository avendo una propria class di configurazione:

 @Configuration @EnableJpaRepositories(basePackages = "package1") public class ConfigClass1 { /* ... */ } @Configuration @EnableJpaRepositories(basePackages = "package2") public class ConfigClass2 { /* ... */ } 

Spring Data JPA conta quindi su tutti ( package1 e package2 ).

Anche se questo non è ancora un modo programmatico, risolve il mio problema.

Quello che stai cercando è @EntityScan ma è disponibile solo in Spring Boot. La configurazione che puoi annotare in Spring Data JPA è documentata qui https://docs.spring.io/spring-data/jpa/docs/2.0.8.RELEASE/reference/html/#jpa.java-config

 @Configuration @EnableJpaRepositories @EnableTransactionManagement class ApplicationConfig { @Bean public DataSource dataSource() { EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(); return builder.setType(EmbeddedDatabaseType.HSQL).build(); } @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory() { HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); vendorAdapter.setGenerateDdl(true); LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean(); factory.setJpaVendorAdapter(vendorAdapter); factory.setPackagesToScan("com.acme.domain"); factory.setDataSource(dataSource()); return factory; } @Bean public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { JpaTransactionManager txManager = new JpaTransactionManager(); txManager.setEntityManagerFactory(entityManagerFactory); return txManager; } } 

Questo problema mi ha anche lasciato perplesso per quasi una settimana. Eseguo il debug del codice “spring context context refresh” e org.springframework.data.jpa.repository.config.JpaRepositoriesRegistrar riga per riga e quindi org.springframework.data.jpa.repository.config.JpaRepositoriesRegistrar il problema.

Personalizzo la mia annotazione EnableJpaRepository e JpaRepositoriesRegistrar , quindi posso fare qualsiasi cosa in AbdJpaRepositoriesRegistrar (“abd” è il prefisso delle mie classi personalizzate).

Questo problema mi ha anche lasciato perplesso per quasi una settimana. Eseguo il debug del codice “spring context context refresh” e org.springframework.data.jpa.repository.config.JpaRepositoriesRegistrar riga per riga e quindi org.springframework.data.jpa.repository.config.JpaRepositoriesRegistrar il problema.

AbdEnableJpaRepositories.java

 import org.springframework.beans.factory.FactoryBean; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.Import; import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean; import org.springframework.data.repository.config.DefaultRepositoryBaseClass; import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.QueryLookupStrategy.Key; import org.springframework.transaction.PlatformTransactionManager; import javax.persistence.EntityManagerFactory; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * basePackages * 复制EnableJpaRepositories,Import自定义的AbdJpaRepositoriesRegistrar. * Copy from EnableJpaRepositories,Import customized AbdJpaRepositoriesRegistrar. * * @author Oliver Gierke * @author Thomas Darimont * @author ghj */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(AbdJpaRepositoriesRegistrar.class) public @interface AbdEnableJpaRepositories { /** * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations eg: * {@code @EnableJpaRepositories("org.my.pkg")} instead of {@code @EnableJpaRepositories(basePackages="org.my.pkg")}. */ String value() default ""; /** * Base packages to scan for annotated components. {@link #value()} is an alias for (and mutually exclusive with) this * attribute. Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names. */ String basePackages() default ""; /** * Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for annotated components. The * package of each class specified will be scanned. Consider creating a special no-op marker class or interface in * each package that serves no purpose other than being referenced by this attribute. */ Class[] basePackageClasses() default {}; /** * Specifies which types are eligible for component scanning. Further narrows the set of candidate components from * everything in {@link #basePackages()} to everything in the base packages that matches the given filter or filters. */ Filter[] includeFilters() default {}; /** * Specifies which types are not eligible for component scanning. */ Filter[] excludeFilters() default {}; /** * Returns the postfix to be used when looking up custom repository implementations. Defaults to {@literal Impl}. So * for a repository named {@code PersonRepository} the corresponding implementation class will be looked up scanning * for {@code PersonRepositoryImpl}. * * @return */ String repositoryImplementationPostfix() default "Impl"; /** * Configures the location of where to find the Spring Data named queries properties file. Will default to * {@code META-INF/jpa-named-queries.properties}. * * @return */ String namedQueriesLocation() default ""; /** * Returns the key of the {@link QueryLookupStrategy} to be used for lookup queries for query methods. Defaults to * {@link Key#CREATE_IF_NOT_FOUND}. * * @return */ Key queryLookupStrategy() default Key.CREATE_IF_NOT_FOUND; /** * Returns the {@link FactoryBean} class to be used for each repository instance. Defaults to * {@link JpaRepositoryFactoryBean}. * * @return */ Class repositoryFactoryBeanClass() default JpaRepositoryFactoryBean.class; /** * Configure the repository base class to be used to create repository proxies for this particular configuration. * * @return * @since 1.9 */ Class repositoryBaseClass() default DefaultRepositoryBaseClass.class; // JPA specific configuration /** * Configures the name of the {@link EntityManagerFactory} bean definition to be used to create repositories * discovered through this annotation. Defaults to {@code entityManagerFactory}. * * @return */ String entityManagerFactoryRef() default "entityManagerFactory"; /** * Configures the name of the {@link PlatformTransactionManager} bean definition to be used to create repositories * discovered through this annotation. Defaults to {@code transactionManager}. * * @return */ String transactionManagerRef() default "transactionManager"; /** * Configures whether nested repository-interfaces (eg defined as inner classs) should be discovered by the * repositories infrastructure. */ boolean considerNestedRepositories() default false; /** * Configures whether to enable default transactions for Spring Data JPA repositories. Defaults to {@literal true}. If * disabled, repositories must be used behind a facade that's configuring transactions (eg using Spring's annotation * driven transaction facilities) or repository methods have to be used to demarcate transactions. * * @return whether to enable default transactions, defaults to {@literal true}. */ boolean enableDefaultTransactions() default true; } 

AbdJpaRepositoriesRegistrar.java

 import org.springframework.data.jpa.repository.config.JpaRepositoryConfigExtension; import org.springframework.data.repository.config.RepositoryConfigurationExtension; import java.lang.annotation.Annotation; class AbdJpaRepositoriesRegistrar extends AbdRepositoryBeanDefinitionRegistrarSupport { /* * (non-Javadoc) * @see org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport#getAnnotation() */ @Override protected Class getAnnotation() { return AbdEnableJpaRepositories.class; } /* * (non-Javadoc) * @see org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport#getExtension() */ @Override protected RepositoryConfigurationExtension getExtension() { return new JpaRepositoryConfigExtension(); } } 

AbdRepositoryBeanDefinitionRegistrarSupport.java

 import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.EnvironmentAware; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.AnnotationMetadata; import org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport; import org.springframework.data.repository.config.RepositoryConfigurationDelegate; import org.springframework.data.repository.config.RepositoryConfigurationExtension; import org.springframework.data.repository.config.RepositoryConfigurationUtils; import org.springframework.util.Assert; /** * * @author ghj */ abstract class AbdRepositoryBeanDefinitionRegistrarSupport extends RepositoryBeanDefinitionRegistrarSupport implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { private ResourceLoader resourceLoader; private Environment environment; @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } @Override public void setEnvironment(Environment environment) { this.environment = environment; } @Override public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) { Assert.notNull(resourceLoader, "ResourceLoader must not be null!"); Assert.notNull(annotationMetadata, "AnnotationMetadata must not be null!"); Assert.notNull(registry, "BeanDefinitionRegistry must not be null!"); // Guard against calls for sub-classs if (annotationMetadata.getAnnotationAttributes(getAnnotation().getName()) == null) { return; } // 使用自定义的AbdAnnotationRepositoryConfigurationSource AbdAnnotationRepositoryConfigurationSource configurationSource = new AbdAnnotationRepositoryConfigurationSource( annotationMetadata, getAnnotation(), resourceLoader, environment); RepositoryConfigurationExtension extension = getExtension(); RepositoryConfigurationUtils.exposeRegistration(extension, registry, configurationSource); RepositoryConfigurationDelegate delegate = new RepositoryConfigurationDelegate(configurationSource, resourceLoader, environment); delegate.registerRepositoriesIn(registry, extension); } } 

AbdAnnotationRepositoryConfigurationSource.java. Puoi sovrascrivere getBasePackages quindi puoi restituire tutti i pacchetti che desideri.

 import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.AnnotationMetadata; import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; /** * * @author ghj */ class AbdAnnotationRepositoryConfigurationSource extends AnnotationRepositoryConfigurationSource { private static final String BASE_PACKAGES = "basePackages"; private static final String BASE_PACKAGE_CLASSES = "basePackageClasses"; private final AnnotationMetadata configMetadata; private final AnnotationAttributes attributes; private final Environment environment; AbdAnnotationRepositoryConfigurationSource(AnnotationMetadata metadata, Class annotation, ResourceLoader resourceLoader, Environment environment) { super(metadata, annotation, resourceLoader, environment); this.attributes = new AnnotationAttributes(metadata.getAnnotationAttributes(annotation.getName())); this.configMetadata = metadata; this.environment = environment; } @Override public Iterable getBasePackages() { String value = attributes.getStringArray("value")[0]; String basePackages = attributes.getStringArray(BASE_PACKAGES)[0]; Class[] basePackageClasses = attributes.getClassArray(BASE_PACKAGE_CLASSES); // Default configuration - return package of annotated class if (StringUtils.isEmpty(value) && StringUtils.isEmpty(basePackages) && basePackageClasses.length == 0) { String className = configMetadata.getClassName(); return Collections.singleton(ClassUtils.getPackageName(className)); } String[] packagesFromValue = parsePackagesSpel(value); String[] packagesFromBasePackages = parsePackagesSpel(basePackages); Set packages = new HashSet<>(); packages.addAll(Arrays.asList(packagesFromValue)); packages.addAll(Arrays.asList(packagesFromBasePackages)); for (Class typeName : basePackageClasses) { packages.add(ClassUtils.getPackageName(typeName)); } return packages; } private String[] parsePackagesSpel(String raw) { if (!raw.trim().startsWith("$")) { if (StringUtils.isEmpty(raw)) { return new String[]{}; } return raw.split(","); } else { raw = raw.trim(); String packages = this.environment.getProperty(raw.substring("${".length(), raw.length() - "}".length())); return packages.split(","); } } } 

Come usare? Ecco il file di configurazione. PrimaryJpaConfiguration.java

 import com.shinow.abd.springjpa2.annotation.AbdEnableJpaRepositories; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.env.Environment; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import org.springframework.transaction.PlatformTransactionManager; import javax.persistence.EntityManager; import javax.sql.DataSource; import java.util.Map; @Configuration @AbdEnableJpaRepositories( basePackages = "${spring.jpa.base-packages}", entityManagerFactoryRef = "entityManagerFactory", transactionManagerRef = "transactionManager" ) @EnableAutoConfiguration(exclude = HibernateJpaAutoConfiguration.class) public class PrimaryJpaConfiguration implements EnvironmentAware { private Environment env; @Bean @ConditionalOnMissingBean(name = "entityManager") @Primary public EntityManager entityManager(@Qualifier("entityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) { return entityManagerFactory.getObject().createEntityManager(); } @Bean @ConditionalOnMissingBean(name = "entityManagerFactory") @Primary public LocalContainerEntityManagerFactoryBean entityManagerFactory(@Qualifier("dataSource") DataSource dataSource) { Map properties = JpaProperties.get("", env); LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean(); entityManagerFactoryBean.setDataSource(dataSource); entityManagerFactoryBean.setJpaPropertyMap(properties); entityManagerFactoryBean.setPackagesToScan(env.getProperty("spring.jpa.base-packages").split(",")); entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); return entityManagerFactoryBean; } @Bean @ConditionalOnMissingBean(name = "dataSource") @Primary @ConfigurationProperties(prefix = "spring.datasource") public DataSource dataSource() { return DataSourceBuilder.create().build(); } @Bean @ConditionalOnMissingBean(name = "transactionManager") @Primary public PlatformTransactionManager transactionManager(@Qualifier("entityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) { JpaTransactionManager transactionManager = new JpaTransactionManager(); transactionManager.setEntityManagerFactory( entityManagerFactory.getObject()); return transactionManager; } @Override public void setEnvironment(Environment environment) { this.env = environment; } } 

È ansible aggiungere la spring.jpa.base-packages ai propri application.properties. Ad esempio: spring.jpa.base-packages=com.foo.a,com.bar.b , e i repository e le quadro sotto quei pacchetti “com.foo.a” e “com.bar.b” saranno aggiunti a il contenitore di spring Ioc.