简体   繁体   English

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

[英]Spring Data JPA - How to programmatically set JpaRepository base packages

When defining an EntityManager in a Spring Java Config class, I can add the base packages to scan for Entity classes by calling a method on the corresponding builder:在 Spring Java Config 类中定义 EntityManager 时,我可以添加基础包以通过调用相应构建器上的方法来扫描实体类:

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

I need something similar for the places where Spring looks for Repository Interfaces.对于 Spring 寻找存储库接口的地方,我需要类似的东西。 The usual way is using the @EnableJpaRepositories annotation:通常的方法是使用@EnableJpaRepositories注解:

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

But I would like to have a dynamical way for defining these packages similar to the way above for the Entity locations.但我想有一种动态的方式来定义这些包,类似于上述实体位置的方式。 Something like this:像这样的东西:

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

Is there a way to do this with the current Spring Data JPA release is it simply not meant to be done this way?有没有办法用当前的 Spring Data JPA 版本来做到这一点,难道它根本不打算这样做吗?

You can use @AutoConfigurationPackage annotation to add your child module's package to scan-packages.您可以使用@AutoConfigurationPackage批注将您的子模块的包添加到 scan-packages。

  1. Remove all @EnableJpaRepositories from your child module从您的子模块中删除所有@EnableJpaRepositories
  2. Add @AutoConfigurationPackage class to the top directory of your child module (similar to @SpringBootApplication , you must put this class to the top-most directory to scan all subpackages):@AutoConfigurationPackage类添加到子模块顶层目录(类似于@SpringBootApplication ,您必须将此类放在最顶层目录才能扫描所有子包):

     @AutoConfigurationPackage public class ChildConfiguration { }
  3. Create spring.factories file under /resources/META-INF/spring.factories of your child module and add the configuration class:子模块的/resources/META-INF/spring.factories下创建spring.factories文件并添加配置类:

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

Now you can @Autowired your repository from your core project.现在您可以从您的核心项目@Autowired您的存储库。 (Tested and worked) (测试和工作)

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

@EnableJpaRepositories can be used on more than one @Configuration class. @EnableJpaRepositories可用于多个@Configuration类。 That said, every module can declare its own repositories by having an own configuration class:也就是说,每个模块都可以通过拥有自己的配置类来声明自己的存储库:

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

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

Spring Data JPA then counts in all of them ( package1 and package2 ). Spring Data JPA 然后计入所有这些( package1package2 )。

Although this is still not a programmatical way, it solves my problem.虽然这仍然不是一种编程方式,但它解决了我的问题。

This problem also puzzled me for almost a week.I debug the "spring application context refresh" code and org.springframework.data.jpa.repository.config.JpaRepositoriesRegistrar line by line and then I solve the problem.这个问题也困扰了我将近一周,我一行一行调试了“spring application context refresh”代码和org.springframework.data.jpa.repository.config.JpaRepositoriesRegistrar ,然后我就解决了问题。

I customize my EnableJpaRepository annotation and JpaRepositoriesRegistrar ,then I can do anything in AbdJpaRepositoriesRegistrar ("abd" is my customized classes's prefix).我自定义了我的EnableJpaRepository注释和JpaRepositoriesRegistrar ,然后我可以在AbdJpaRepositoriesRegistrar做任何事情(“abd”是我自定义类的前缀)。

This problem also puzzled me for almost a week.I debug the "spring application context refresh" code and org.springframework.data.jpa.repository.config.JpaRepositoriesRegistrar line by line and then I solve the problem.这个问题也困扰了我将近一周,我一行一行调试了“spring application context refresh”代码和org.springframework.data.jpa.repository.config.JpaRepositoriesRegistrar ,然后我就解决了问题。

AbdEnableJpaRepositories.java 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 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 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. AbdAnnotationRepositoryConfigurationSource.java。 You can override getBasePackages then you can return any packages you want.你可以覆盖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(",");
        }
    }
}

How to use?Here is configuration file.如何使用?这里是配置文件。 PrimaryJpaConfiguration.java主JpaConfiguration.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;
    }
}

You can add spring.jpa.base-packages config to your application.properties.您可以将spring.jpa.base-packages配置添加到您的 application.properties。 For example: spring.jpa.base-packages=com.foo.a,com.bar.b ,and the repositories and entities under those packages "com.foo.a" and "com.bar.b" will be added to the spring Ioc container.例如: spring.jpa.base-packages=com.foo.a,com.bar.b ,这些包“com.foo.a”和“com.bar.b”下的存储库和实体将被添加到弹簧 Ioc 容器。

What your looking for is @EntityScan but it's only available in Spring Boot.您要寻找的是@EntityScan但它仅在 Spring Boot 中可用。 The configuration you can annotate in Spring Data JPA is documented here https://docs.spring.io/spring-data/jpa/docs/2.0.8.RELEASE/reference/html/#jpa.java-config您可以在 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;
  }
}

Answer by 高慧觉Spring Data JPA - How to programmatically set JpaRepository base packages worked for me, but I've come up with a simpler and more reliable AnnotationRepositoryConfigurationSource implementation: just allow Spring Data to collect the packages in its way, and then post-process them and expand property placeholders to package names.高慧觉Spring Data JPA - How to programmatically set JpaRepository 基本包对我有用,但我想出了一个更简单、更可靠的AnnotationRepositoryConfigurationSource实现:只允许 Spring Data 以它的方式收集包,然后进行后期处理它们并将属性占位符扩展为包名称。

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

I've implemented a way to invoke @EnableJpaRepositories purely programmatically.我已经实现了一种纯粹以编程方式调用@EnableJpaRepositories的方法。 For the purpose I've created a class which simulates the Annotation data passed to a config class annotated with the @EnableJpaRepositories annotation.为此,我创建了一个类,该类模拟传递给使用@EnableJpaRepositories注释注释的配置类的注释数据。 I called my class EnableJpaRepositoriesData .我称我的班级为EnableJpaRepositoriesData It is confirmed working with Spring 5.1.5, SpringBoot 2.1.3.已确认可与 Spring 5.1.5、SpringBoot 2.1.3 一起使用。 I guess it must be compatible with earlier and later versions alike, with none or very little changes at most.我想它必须与早期和更高版本兼容,最多没有或几乎没有变化。

Below follows the class code, and a sample usage code for it.下面是类代码,以及它的示例使用代码。

EnableJpaRepositoriesData.java:启用JpaRepositoriesData.java:

package org.patladj.jpa.config.multijpa;


import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.AnnotationMetadataReadingVisitor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Indexed;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.TransactionManagementConfigurationSelector;

import java.lang.reflect.Method;
import java.util.*;


public class EnableJpaRepositoriesData extends AnnotationMetadataReadingVisitor implements AnnotationMetadata {

    private Map<String, ?> data;

    private void initIt() throws NoSuchMethodException, ClassNotFoundException {

        //################## protected final Set<String> annotationSet = new LinkedHashSet<>(4);
        annotationSet.add(Configuration.class.getCanonicalName());
        annotationSet.add(EnableTransactionManagement.class.getCanonicalName());
        annotationSet.add(EnableJpaRepositories.class.getCanonicalName());

        //################## protected final Map<String, Set<String>> metaAnnotationMap = new LinkedHashMap<>(4);
        metaAnnotationMap.put(Configuration.class.getCanonicalName(),
                new LinkedHashSet<>(Arrays.asList(
                        Component.class.getCanonicalName(),
                        Indexed.class.getCanonicalName()
                )));
        metaAnnotationMap.put(EnableTransactionManagement.class.getCanonicalName(),
                new LinkedHashSet<>(Arrays.asList(
                        Import.class.getCanonicalName()
                )));
        metaAnnotationMap.put(EnableJpaRepositories.class.getCanonicalName(),
                new LinkedHashSet<>(Arrays.asList(
                        Import.class.getCanonicalName()
                )));

        //################## protected final LinkedMultiValueMap<String, AnnotationAttributes> attributesMap = new LinkedMultiValueMap<>(4);
        attributesMap.put(Configuration.class.getCanonicalName(),
                new LinkedList<AnnotationAttributes>() {{
                    add(new AnnotationAttributes(new LinkedHashMap<String, Object>() {{
                        put("value", defaultFor(Configuration.class, "value"));
                    }}));
                }});

        attributesMap.put(Component.class.getCanonicalName(),
                new LinkedList<AnnotationAttributes>() {{
                    add(new AnnotationAttributes(new LinkedHashMap<String, Object>() {{
                        put("value", defaultFor(Component.class, "value"));
                    }}));
                }});
        attributesMap.put(Indexed.class.getCanonicalName(),
                new LinkedList<AnnotationAttributes>() {{
                    add(new AnnotationAttributes(new LinkedHashMap<String, Object>() {{
                    }}));
                }});
        attributesMap.put(EnableTransactionManagement.class.getCanonicalName(),
                new LinkedList<AnnotationAttributes>() {{
                    add(new AnnotationAttributes(new LinkedHashMap<String, Object>() {{
                        put("order", defaultFor(EnableTransactionManagement.class, "order"));
                        put("mode", defaultFor(EnableTransactionManagement.class, "mode"));
                        put("proxyTargetClass", defaultFor(EnableTransactionManagement.class, "proxyTargetClass"));
                    }}));
                }});
        attributesMap.put(Import.class.getCanonicalName(),
                new LinkedList<AnnotationAttributes>() {{
                    add(new AnnotationAttributes(new LinkedHashMap<String, Object>() {{
                        put("value", new Class<?>[]{TransactionManagementConfigurationSelector.class});
                    }}));
                    add(new AnnotationAttributes(new LinkedHashMap<String, Object>() {{
                        put("value", new Class<?>[]{Class.forName("org.springframework.data.jpa.repository.config.JpaRepositoriesRegistrar")});
                    }}));
                }});

        attributesMap.put(EnableJpaRepositories.class.getCanonicalName(),
                new LinkedList<AnnotationAttributes>() {{
                    add(new AnnotationAttributes(new LinkedHashMap<String, Object>() {{
                        put("repositoryBaseClass", data.get("repositoryBaseClass"));
                        put("basePackages", data.get("basePackages"));
                        put("value", defaultFor(EnableJpaRepositories.class, "value"));
                        put("excludeFilters", new AnnotationAttributes[]{});
                        put("includeFilters", new AnnotationAttributes[]{});
                        put("basePackageClasses", defaultFor(EnableJpaRepositories.class, "basePackageClasses"));
                        put("bootstrapMode", defaultFor(EnableJpaRepositories.class, "bootstrapMode"));
                        put("transactionManagerRef", data.get("transactionManagerRef"));
                        put("considerNestedRepositories", defaultFor(EnableJpaRepositories.class, "considerNestedRepositories"));
                        put("namedQueriesLocation", defaultFor(EnableJpaRepositories.class, "namedQueriesLocation"));
                        put("queryLookupStrategy", defaultFor(EnableJpaRepositories.class, "queryLookupStrategy"));
                        put("entityManagerFactoryRef", data.get("entityManagerFactoryRef"));
                        put("enableDefaultTransactions", defaultFor(EnableJpaRepositories.class, "enableDefaultTransactions"));
                        put("repositoryImplementationPostfix", defaultFor(EnableJpaRepositories.class, "repositoryImplementationPostfix"));
                        put("repositoryFactoryBeanClass", defaultFor(EnableJpaRepositories.class, "repositoryFactoryBeanClass"));

                    }}));
                }});


        //##################
    }

    public EnableJpaRepositoriesData(@Nullable ClassLoader classLoader, Map<String, ?> data) throws NoSuchMethodException, ClassNotFoundException {
        super(classLoader);
        this.data = data;
        this.initIt();
    }

    private Object defaultFor(Class<?> clazz, String methodName) throws NoSuchMethodException {
        Method method = clazz.getDeclaredMethod(methodName);
        return method.getDefaultValue();
    }
}

Sample usage of the Class to actually invoke enableJpaRepositories programmatically:编程方式实际调用 enableJpaRepositories 的类的示例用法

AnnotationMetadata enableJpaRepositoriesData = null;
try {
    enableJpaRepositoriesData = new EnableJpaRepositoriesData(this.getClass().getClassLoader(), new HashMap<String, Object>() {{
        put("repositoryBaseClass", MyJPAConnectorImpl.class);
        put("basePackages", new String[] {

                //This is where you set the repositories base packages to scan.
                //... Having your best programmatic convenience
                "org.patladj.connector.multijpa.common",
                "org.patladj.connector.multijpa.anotherrepositoriespackage",
                "org.patladj.connector.multijpa.morepackagestoscanforrepositories..."
        });

        //Ofcourse you need to create by yourself the custom transactionManagerFactory,
        // ... entityManagerFactory, JPAVendorAdapter and Datasource beans for your
        // ... custom persistence contexts using unique bean names (different than
        // ... the default Spring data names: 'entityManagerFactory', etc...)
        // ... Then "send" these bean names as references to each and every ...
        // and as many as you want persistence contexts programmatically here
        put("transactionManagerRef", myCustomPersistanceContextName + "_transactionManager");
        put("entityManagerFactoryRef", myCustomPersistanceContextName + "_entityManagerFactory");
    }});
} catch (NoSuchMethodException e) {
    throw new ExceptionInInitializerError(e);
} catch (ClassNotFoundException e) {
    throw new ExceptionInInitializerError(e);
}

AnnotationRepositoryConfigurationSource configurationSource = new AnnotationRepositoryConfigurationSource(
        enableJpaRepositoriesData, EnableJpaRepositories.class, applicationContext, environment, registry
);

RepositoryConfigurationExtension extension = new JpaRepositoryConfigExtension();

RepositoryConfigurationUtils.exposeRegistration(extension, registry, configurationSource);

RepositoryConfigurationDelegate delegate = new RepositoryConfigurationDelegate(configurationSource, applicationContext, environment);
delegate.registerRepositoriesIn(registry, extension);

And finally, in case if you don't know, you can use the following @Configuration class最后,如果您不知道,可以使用以下 @Configuration 类

public class MultiJPABeanDefinitionRegistrar implements BeanDefinitionRegistryPostProcessor, ApplicationContextAware

... to override 2 of its methods to have the post process bean registry and applicationContext at your disposal to register bean definitions dynamically. ... 覆盖其 2 个方法,以便您可以使用后期处理 bean registryapplicationContext来动态注册 bean 定义。 Methods to override:重写方法:

 - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
 - public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException

I referred this post only and created a module aware multi database aware spring data library for mysql(compatible with spring boot) in github.Some application properties need to be added and you are done .我只引用了这篇文章,并在 github 中为 mysql(与 spring boot 兼容)创建了一个模块感知多数据库感知 spring 数据库。需要添加一些应用程序属性,你就完成了。

Documentation and other details could be found at :-可以在以下位置找到文档和其他详细信息:-

https://github.com/yatharthamishra0419/spring-boot-data-multimodule-mysql https://github.com/yatharthamishra0419/spring-boot-data-multimodule-mysql

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何从 Spring 数据 JPA 在 JpaRepository 中创建自定义方法? - How to make a custom method in JpaRepository from Spring Data JPA? 如何使用 Spring 数据 JPA (JPARepository) 实现延迟加载? - How to implement lazy loading using Spring data JPA (JPARepository)? Spring Data JPA - JpaRepository 中的自定义排序 - Spring Data JPA - Custom Sort in JpaRepository 关于Spring Data JPA Rest(JpaRepository)的安全性问题 - Security questions on Spring Data JPA Rest (JpaRepository) Spring Data JPA:没有JpaRepository的命名方法 - Spring Data JPA: Named method without JpaRepository 如何使用基于类的配置使用不同的基本包、transactionManagerRef 设置多个 Spring Data JPA? - How to setup multiple Spring Data JPA with different base-packages,transactionManagerRef using class based configuration? 如何在JpaRepository中构造spring JPA / JPQL查询 - how to construct spring JPA/JPQL query in JpaRepository Spring Data JPA JpaRepository.save()不返回默认值 - Spring Data JPA JpaRepository.save() does not return default values 为什么 JpaRepository 在 Spring Data Jpa 中没有 saveAll 方法? - Why doesn't JpaRepository have a saveAll method in Spring Data Jpa? Spring数据jpa,jparepository返回字符串列表代替DTO对象 - Spring data jpa, jparepository returning list of string in place of DTO object
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM