Spring Data JPA – 如何以编程方式设置JpaRepository基础包

在Spring Java Config类中定义EntityManager时,我可以通过调用相应构建器上的方法来添加基础包以扫描Entity类:

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

我需要一些类似于Spring寻找存储库接口的地方。 通常的方法是使用@EnableJpaRepositories注释:

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

但是我希望有一种动态的方式来定义这些包,类似于上面对Entity位置的方式。 像这样的东西:

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

有没有办法用当前的Spring Data JPA版本执行此操作,这是不是意味着这样做?

您可以使用@AutoConfigurationPackage批注将子模块的包添加到scan-packages。

  1. 子模块中 删除所有@EnableJpaRepositories
  2. @AutoConfigurationPackage类添加到子模块顶级目录 (类似于@SpringBootApplication ,您必须将此类放在最顶层的目录中以扫描所有子包):

     @AutoConfigurationPackage public class ChildConfiguration { } 
  3. 子模块的 /resources/META-INF/spring.factories下创建spring.factories文件并添加配置类:

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

现在,您可以从核心项目@Autowired您的存储库。 (经过测试和工作)

没有Spring Boot(普通的Spring MVC设置)

@EnableJpaRepositories可以在多个@Configuration类上使用。 也就是说,每个模块都可以通过拥有自己的配置类来声明自己的存储库:

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

Spring Data JPA然后计入所有这些( package1package2 )。

虽然这仍然不是一种程序化的方式,但它解决了我的问题。

您正在寻找的是@EntityScan但它仅在Spring Boot中可用。 您可以在Spring Data JPA中注释的配置在此处记录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; } } 

这个问题也困扰了我近一个星期。我逐行调试“spring application context refresh”代码和org.springframework.data.jpa.repository.config.JpaRepositoriesRegistrar然后我解决了这个问题。

我自定义了我的EnableJpaRepository注释和JpaRepositoriesRegistrar ,然后我可以在AbdJpaRepositoriesRegistrar做任何事情(“abd”是我的自定义类的前缀)。

这个问题也困扰了我近一个星期。我逐行调试“spring application context refresh”代码和org.springframework.data.jpa.repository.config.JpaRepositoriesRegistrar然后我解决了这个问题。

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 classes) 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-classes 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。 您可以覆盖getBasePackages然后您可以返回所需的任何包。

 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(","); } } } 

怎么用?这是配置文件。 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; } } 

您可以将spring.jpa.base-packages config添加到application.properties。 例如: spring.jpa.base-packages=com.foo.a,com.bar.b ,这些包“com.foo.a”和“com.bar.b”下的存储库和实体将被添加到spring的Ioc容器。