5

If someone ever needed to use @Primary on Spring Data repositories: It looks like Spring Data JPA ignores @Primary annotations on repositories.

As a workaround I have created BeanFactoryPostProcessor which checks if given repository has @Primary annotation and sets that bean as primary.

This is the code:

@Component
public class SpringDataPrimaryPostProcessor implements BeanFactoryPostProcessor {
    public static final String REPOSITORY_INTERFACE_PROPERTY = "repositoryInterface";


    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        makeRepositoriesPrimary(getRepositoryBeans(beanFactory));
    }

    protected List<BeanDefinition> getRepositoryBeans(ConfigurableListableBeanFactory beanFactory) {
        List<BeanDefinition> springDataRepositoryDefinitions = Lists.newArrayList();
        for (String beanName : beanFactory.getBeanDefinitionNames()) {
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);

            String beanClassName = beanDefinition.getBeanClassName();
            try {
                Class<?> beanClass = Class.forName(beanClassName);
                if (isSpringDataJpaRepository(beanClass)) {
                    springDataRepositoryDefinitions.add(beanDefinition);
                }
            } catch (ClassNotFoundException e) {
                throw new ApplicationContextException(String.format("Error when trying to create instance of %s", beanClassName), e);
            }
        }

        return springDataRepositoryDefinitions;
    }

  protected void makeRepositoriesPrimary(List<BeanDefinition> repositoryBeans) {
    for (BeanDefinition repositoryBeanDefinition : repositoryBeans) {
        String repositoryInterface = (String) repositoryBeanDefinition.getPropertyValues().get(REPOSITORY_INTERFACE_PROPERTY);
            if (isPrimary(repositoryInterface)) {
                log.debug("Making site repository bean primary, class: {}", repositoryInterface);
                repositoryBeanDefinition.setPrimary(true);
            }
    }
}

protected boolean isSpringDataJpaRepository(Class<?> beanClass) {
    return RepositoryFactoryInformation.class.isAssignableFrom(beanClass);
}

private boolean isPrimary(String repositoryInterface) {
    return AnnotationUtils.findAnnotation(getClassSafely(repositoryInterface), Primary.class) != null;
}

    private Class<?> getClassSafely(String repositoryInterface) {
        try {
            return Class.forName(repositoryInterface);
        } catch (ClassNotFoundException e) {
            throw new ApplicationContextException(String.format("Error when trying to create instance of %s", repositoryInterface), e);
        }
    }
rgrebski
  • 2,354
  • 20
  • 29
  • 1
    And your question is??? – M. Deinum May 04 '15 at 12:16
  • what @Primary ? in which package/software is this annotation? – Neil Stockton May 04 '15 at 15:35
  • 1
    @Primary is a Spring annotation http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/Primary.html – rgrebski May 04 '15 at 18:48
  • Cannot resolve method `isSpringDataJpaRepository()`. – mkczyk Jan 17 '16 at 04:38
  • Thx mk321, I have updated the code – rgrebski Jan 18 '16 at 16:23
  • Today i face this problem when I realized I can't make fake implementations for unit tests because in app container throws exception about unambiguous beans, so I will check your solution and thank you in advance! – Radek Anuszewski Oct 29 '16 at 18:54
  • After 2 little changes: `Class> beanClass = Class.forName(beanClassName == null? "TestName" : beanClassName` instead of `Class> beanClass = Class.forName(beanClassName)` and remove throwing Exceptions few lines later: `throw new ApplicationContextException(String.format("Error when trying to create instance of %s", beanClassName), e);` cause I got `null` as bean name everything works, thank you very much! – Radek Anuszewski Oct 30 '16 at 09:57

1 Answers1

0

I tried applying the solution to spring boot application having two Mongo repositories. But it was not able to find repositoryInterface in propertyValues. Further investigation revealed that there is an attribute to identify the repository interface factoryBeanObjectType.

So changing the code in method makeRepositoriesPrimary()from:

String repositoryInterface = (String) repositoryBeanDefinition.getPropertyValues().get(REPOSITORY_INTERFACE_PROPERTY);

To:

String repositoryInterface = (String) repositoryBeanDefinition.getAttribute("factoryBeanObjectType");

worked as expected.

Hope this helps.