복붙노트

[SPRING] Spring Data JPA - 프로그래밍 방식으로 JpaRepository 기본 패키지를 설정하는 방법

SPRING

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이 Repository Interfaces를 찾을 곳과 비슷한 것이 필요하다. 일반적인 방법은 @EnableJpaRepositories 주석을 사용하는 것입니다.

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

그러나 엔티티 위치에 대해 위의 방법과 비슷한 패키지를 정의 할 수있는 역동적 인 방법을 원합니다. 이 같은:

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

현재 Spring Data JPA 릴리스에서이 작업을 수행 할 수있는 방법이 있습니까?

해결법

  1. ==============================

    1.@AutoConfigurationPackage 주석을 사용하여 하위 모듈의 패키지를 스캔 패키지에 추가 할 수 있습니다.

    @AutoConfigurationPackage 주석을 사용하여 하위 모듈의 패키지를 스캔 패키지에 추가 할 수 있습니다.

    이제 코어 프로젝트에서 @Autowired 저장소를 사용할 수 있습니다. (테스트 및 작업)

  2. ==============================

    2.@EnableJpaRepositories는 둘 이상의 @Configuration 클래스에서 사용할 수 있습니다. 즉, 모든 모듈은 자체 구성 클래스를 사용하여 자체 저장소를 선언 할 수 있습니다.

    @EnableJpaRepositories는 둘 이상의 @Configuration 클래스에서 사용할 수 있습니다. 즉, 모든 모듈은 자체 구성 클래스를 사용하여 자체 저장소를 선언 할 수 있습니다.

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

    스프링 데이터 JPA는 그 다음 모든 것을 계산합니다 (package1 및 package2).

    이것은 여전히 ​​프로그래밍 방식이 아니지만 내 문제를 해결합니다.

  3. ==============================

    3.이 문제는 또한 거의 일주일 동안 저를 당황 시켰습니다. "spring application context refresh"코드와 org.springframework.data.jpa.repository.config.JpaRepositoriesRegistrar를 한 줄씩 디버그 한 다음 문제를 해결합니다.

    이 문제는 또한 거의 일주일 동안 저를 당황 시켰습니다. "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 e.g.:
         * {@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 (e.g. 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 (e.g. 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<? extends Annotation> 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<? extends Annotation> 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<String> 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<String> 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<String, Object> 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"아래의 리포지토리 및 엔티티가 다음에 추가됩니다. 봄 IOC 컨테이너.

  4. ==============================

    4.@EntityScan은 Spring Boot에서만 사용할 수 있습니다. Spring Data JPA에서 주석 처리 할 수있는 설정은 https://docs.spring.io/spring-data/jpa/docs/2.0.8.RELEASE/reference/html/#jpa.java-config에서 확인할 수있다.

    @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;
      }
    }
    
  5. ==============================

    5.의해 답변을 高 慧 觉 스프링 데이터 JPA는 - 프로그래밍 방식으로 설정 JpaRepository 기본 패키지 나를 위해 일한,하지만 난 간단하고 안정적인 AnnotationRepositoryConfigurationSource 구현을 함께 왔어 : 그냥 봄 데이터 방식으로 패키지를 수집하고 후 처리 속성 자리 표시자를 패키지 이름으로 확장합니다.

    의해 답변을 高 慧 觉 스프링 데이터 JPA는 - 프로그래밍 방식으로 설정 JpaRepository 기본 패키지 나를 위해 일한,하지만 난 간단하고 안정적인 AnnotationRepositoryConfigurationSource 구현을 함께 왔어 : 그냥 봄 데이터 방식으로 패키지를 수집하고 후 처리 속성 자리 표시자를 패키지 이름으로 확장합니다.

    class AbdAnnotationRepositoryConfigurationSource extends AnnotationRepositoryConfigurationSource {
    
        private final Environment environment;
    
        ExpressionsSupportingAnnotationRepositoryConfigurationSource(AnnotationMetadata metadata, Class<? extends Annotation> annotation,
                ResourceLoader resourceLoader, Environment environment, BeanDefinitionRegistry registry) {
            super(metadata, annotation, resourceLoader, environment, registry);
    
            this.environment = environment;
        }
    
        @Override
        public Streamable<String> getBasePackages() {
            Streamable<String> rawPackages = super.getBasePackages();
            return Streamable.of(() -> rawPackages.stream()
                    .flatMap(raw -> parsePackagesSpel(raw).stream())
            );
        }
    
        private List<String> parsePackagesSpel(@Nullable String rawPackage) {
            Objects.requireNonNull(rawPackage, "Package specification cannot be null");
    
            if (!rawPackage.trim().startsWith("$")) {
                return Collections.singletonList(rawPackage);
            }
    
            rawPackage = rawPackage.trim();
            String propertyName = rawPackage.substring("${".length(), rawPackage.length() - "}".length());
            String packages = this.environment.getProperty(propertyName);
    
            if (!StringUtils.hasText(packages)) {
                throw new IllegalStateException(
                        String.format("Could not resolve the following packages definition: %s", rawPackage));
            }
    
            return Arrays.stream(packages.split(","))
                    .map(String::trim)
                    .filter(StringUtils::hasText)
                    .collect(Collectors.toList());
        }
    }
    
  6. from https://stackoverflow.com/questions/47635650/spring-data-jpa-how-to-programmatically-set-jparepository-base-packages by cc-by-sa and MIT license