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

ggazkfy8  于 2023-05-16  发布在  Spring
关注(0)|答案(8)|浏览(300)

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

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版本中做到这一点?它根本就不应该这样做吗?

u5i3ibmn

u5i3ibmn1#

您可以使用@AutoConfigurationPackage注解将子模块的包添加到scan-packages。
1.从您的子模块中删除所有@EnableJpaRepositories
1.将@AutoConfigurationPackage类添加到您的子模块的top目录(类似于@SpringBootApplication,您必须将该类放置到最顶层目录才能扫描所有子包):

@AutoConfigurationPackage
public class ChildConfiguration {
}

1.在您的子模块的**/resources/META-INF/spring.factories**下创建spring.factories文件,添加配置类:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.child.package.ChildConfiguration
现在,您可以从核心项目@Autowired您的存储库。(已测试和工作)

0sgqnhkj

0sgqnhkj2#

不带Sping Boot (纯Spring MVC设置)

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

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

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

然后Spring Data JPA会将它们全部计算在内(package1package2)。
虽然这仍然不是一个程序化的方法,但它解决了我的问题。

ccgok5k5

ccgok5k53#

这个问题也困扰了我差不多一个星期,我一行一行调试“spring application context refresh”代码和org.springframework.data.jpa.repository.config.JpaRepositoriesRegistrar,然后解决了这个问题。
我定制了EnableJpaRepository注解和JpaRepositoriesRegistrar,然后我可以在AbdJpaRepositoriesRegistrar中做任何事情(“abd”是我定制的类的前缀)。
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配置添加到application.properties。例如:spring.jpa.base-packages=com.foo.a,com.bar.b,以及那些包“com.foo.a”和“com.bar.b”下的存储库和实体将被添加到spring Ioc容器中。

ipakzgxi

ipakzgxi4#

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.

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

sauutmhj5#

我实现了一种纯编程方式调用@EnableJpaRepositories的方法。为此,我创建了一个类,它模拟了传递给配置类的Annotation数据,该配置类使用@EnableJpaRepositories注解进行注解。我叫我的班级EnableJpaRepositoriesData。它被确认与Spring 5.1.5,SpringBoot 2.1.3一起工作。我猜它必须兼容早期和后期版本一样,没有或很少的变化,在最多。
下面是类代码,以及它的示例使用代码。

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

通过编程方式实际调用enableJpaRepositories的Class用法示例**:**

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

最后,如果你不知道,可以使用下面的@Configuration类

public class MultiJPABeanDefinitionRegistrar implements BeanDefinitionRegistryPostProcessor, ApplicationContextAware

...重写它的两个方法,让后处理bean registryapplicationContext可以动态注册bean定义。要重写的方法:

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

p4tfgftt6#

我只是参考了这篇文章,并在github中为mysql创建了一个模块感知的多数据库感知的spring数据库(与spring Boot 兼容)。需要添加一些应用程序属性,你就完成了。
文件和其他详细信息可在以下网址找到:-
https://github.com/yatharthamishra0419/spring-boot-data-multimodule-mysql

hkmswyz6

hkmswyz67#

code-in-the-middle的Angular 来看,有一种方法可以将所需的功能注入到确切的代码位置,以实现dynamic basePackages value功能,这里的dynamic是指runtime
设法通过以下逻辑绕过现有的Spring限制(代码被简化,以突出code-in-the-middle解决方案本身)。
1.应用程序JPA配置类

package com.me0x.meta.persistence.configuration;

import org.springframework.context.annotation.Configuration;

import com.me0x.meta.spring.data.EnableJpaRepositories;

@Configuration
@EnableJpaRepositories
public class JpaConfiguration {
    // other configuration details, if any
}
  1. EnableJpaRepositories注解的自定义版本
package com.me0x.meta.spring.data;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.repository.config.BootstrapMode;
import org.springframework.data.repository.config.DefaultRepositoryBaseClass;
import org.springframework.data.repository.query.QueryLookupStrategy;

/**
 * This annotation is just a copy of the
 * {@link org.springframework.data.jpa.repository.config.EnableJpaRepositories}
 * spring specific one.
 * <p>
 * The dynamic loading aspect of JPA repositories is enabled IF AND ONLY IF the
 * {@link #basePackages()} property is set to the
 * {@link DynamicJpaRepositoriesRegistrar#DYNAMIC_JPA_REPOSITORIES_REGISTRATION_FEATURE_KEY}
 * value, as it is by default, otherwise this annotation works similar to the
 * spring specific one.
 * <p>
 * TODO: Remove this custom annotation and everything around it once spring
 *  starts to allow by default a dynamic loading strategy of the
 *  {@link #basePackages()} value!
 * TODO: Keep it updated (synced) to the spring specific version of it everytime
 *  the spring framework is upgraded!
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DynamicJpaRepositoriesRegistrar.class)
@SuppressWarnings("unused")
public @interface EnableJpaRepositories {

    String[] value() default {};

    String[] basePackages() default {DynamicJpaRepositoriesRegistrar.DYNAMIC_JPA_REPOSITORIES_REGISTRATION_FEATURE_KEY};

    Class<?>[] basePackageClasses() default {};

    ComponentScan.Filter[] includeFilters() default {};

    ComponentScan.Filter[] excludeFilters() default {};

    String repositoryImplementationPostfix() default "Impl";

    String namedQueriesLocation() default "";

    QueryLookupStrategy.Key queryLookupStrategy() default QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND;

    Class<?> repositoryFactoryBeanClass() default JpaRepositoryFactoryBean.class;

    Class<?> repositoryBaseClass() default DefaultRepositoryBaseClass.class;

    // JPA specific configuration

    String entityManagerFactoryRef() default "entityManagerFactory";

    String transactionManagerRef() default "transactionManager";

    boolean considerNestedRepositories() default false;

    boolean enableDefaultTransactions() default true;

    BootstrapMode bootstrapMode() default BootstrapMode.DEFAULT;

    char escapeCharacter() default '\\';

}
  1. org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport类的自定义版本
package com.me0x.meta.spring.data;

import java.lang.annotation.Annotation;
import java.lang.reflect.Proxy;
import java.util.Map;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.jpa.repository.config.JpaRepositoryConfigExtension;
import org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport;
import org.springframework.data.repository.config.RepositoryConfigurationExtension;

import com.me0x.meta.exception.ServiceException;

/**
 * @see org.springframework.data.jpa.repository.config.JpaRepositoriesRegistrar
 */
@Log4j2
public class DynamicJpaRepositoriesRegistrar extends RepositoryBeanDefinitionRegistrarSupport {

    public static final String DYNAMIC_JPA_REPOSITORIES_REGISTRATION_FEATURE_KEY = "Dynamic";

    @Override
    public void registerBeanDefinitions(final AnnotationMetadata metadata, final BeanDefinitionRegistry registry, final BeanNameGenerator generator) {
        final AnnotationMetadata annotationMetadataProxy = (AnnotationMetadata) Proxy.newProxyInstance(
                getClass().getClassLoader(),
                new Class<?>[]{AnnotationMetadata.class},
                (proxy, method, args) -> {
                    final Object methodResult = method.invoke(metadata, args);

                    if (methodResult instanceof Map) {
                        try {
                            @SuppressWarnings("unchecked") final Map<String, Object> mapMethodResult = (Map<String, Object>) methodResult;
                            final Object basePackages = mapMethodResult.get("basePackages");

                            if (null != basePackages) {
                                final String[] basePackagesArray = (String[]) basePackages;

                                if (1 == basePackagesArray.length && DYNAMIC_JPA_REPOSITORIES_REGISTRATION_FEATURE_KEY.equals(basePackagesArray[0])) {
                                    mapMethodResult.put("basePackages", new String[]{"com.me0x"}); // TODO: Add dynamic values identified at runtime instead!
                                }
                            }
                        } catch (final Exception e) {
                            throw new ServiceException("Unable to customize the JPA repositories registration process!", e);
                        }
                    }

                    return methodResult;
                }
        );

        super.registerBeanDefinitions(annotationMetadataProxy, registry, generator);
    }

    @Override
    protected Class<? extends Annotation> getAnnotation() {
        return EnableJpaRepositories.class;
    }

    @Override
    protected RepositoryConfigurationExtension getExtension() {
        return new JpaRepositoryConfigExtension();
    }

}
xzv2uavs

xzv2uavs8#

你要找的是@EntityScan,但它只在Sping 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;
  }
}

相关问题