Spring Data: multiple repository interfaces into a single 'repository' service class

13,945

Solution 1

I do not think you can / shoudl create a meta-repository like abstraction. Repositories have a well defined meaning, conceptually, they are CRUD services (and a bit more sometimes :-)) for your Hibernate/JPA/Datastore entities. And I guess this is enough for them. Anything more is confusing.

Now what I would propose is a "smart" way of building your "Person" objects that is automa(g)tically aware of any new services that contribute to the meaning of the Person object.

The crux of it would be that :

  • you could have your Repositories implement a given Interface, say PersonDataProvider, which would have a method, say public PersonPart contributeDataToPersonBuidler(PersonBuilder).
  • You would make your @Service implement Spring's BeanFactoryPostProcessor interface, allowing you to inspect the container for all such PersonDataProvider instances, and inject them to your service (see accepted answer at How to collect and inject all beans of a given type in Spring XML configuration)
  • Your @Service implementation would then be to ask all the PersonDataProviders in turn to ask them to contribute their data.

I could expand a bit, but this seems to me like the way to go.

One could argue that this is not clean (it makes your Repositories aware of "something" that happens at the service layer, and they should not have to), and one could work around that, but it's simpler to expose the gist of the solution that way.

EDIT : since this post was first written, I came aware that Spring can auto-detect and inject all beans of a certain type, without the need of PostProcessors. See the accepted answer here : Autowire reference beans into list by type

Solution 2

I see it as a quite reasonable and practical data aggregation on Service layer. It's perfectly achievable in Spring. If you have access to repositories code you can name them all like:

@Repository("repoOne")
public class RepositoryOne {

@Repository("repoTwo")
public class RepositoryTwo {

And inject them into the aggregation service as necessary:

@Service
public class MultipleRepoService {

    @Autowired
    @Qualifier("repoOne")
    private RepositoryOne repositoryOne;

    @Autowired
    @Qualifier("repoTwo")
    private RepositoryTwo repositoryTwo;

    public void doMultipleBusiness() {
        repositoryOne.one();
        repositoryTwo.two();
    }
}

In fact, you even don't need to name and Qualify them if they are different classes, but if they are in hierarchy or have the same interface...

Also, you can inject directly to constructing method if autowiring is not a case:

public void construct(@Qualifier("repoOne")RepositoryOne repoOne,
                          @Qualifier("repoTwo")RepositoryTwo repoTwo) {
        repoOne.one();
        repoTwo.two();
    }
Share:
13,945
Deniss M.
Author by

Deniss M.

I'm just a newb trying to get it together. It is quite hard to do it in a sense that I needs some time to do it.

Updated on June 27, 2022

Comments

  • Deniss M.
    Deniss M. almost 2 years

    I have quite some JpaRepository extended Repository interfaces due to the design of the database.

    In order to construct a simple object i.e Person I have to make method calls to about 4 - 5 repositories just because the data is spread like that throughout the database. Something like this (pardon for pseudocode):

    @Service
    public class PersonConstructService {
    
        public PersonConstructService(Repository repository,
                                      RepositoryTwo repositoryTwo,
                                      RepositoryThree repositoryThree) {
    
            public Person constructPerson() {
                person
                    .add(GetDataFromRepositoryOne())
                    .add(GetDataFromRepositoryTwo())
                    .add(GetDataFromRepositoryThree());
    
                return person;
            }
    
            private SomeDataTypeReturnedOne GetDataFromRepositoryOne() {
                repository.doSomething();
            }
    
            private SomeDataTypeReturnedTwo GetDataFromRepositoryTwo() {
                repositoryTwo.doSomething();
            }
    
            private SomeDataTypeReturnedThree GetDataFromRepositoryThree() {
                repositoryThree.doSomething();
            }
        }
    }
    

    PersonConstructService class uses all these interfaces just to construct a simple Person object. I am calling these repositories from different methods inside the PersonConstructService class. I have thought about spreading this class into multiple classes, but I do not think this is correct.

    Instead I would like to use a repositoryService which would include all the repositories listed necessary for creation of a Person object. Is that a good approach? Is it possible in Spring?

    The reason I am asking is that sometimes the count of injected Services into a class is about 7-8. This is definitely not good.