5

I use Spring JPA repositories and entities in an application. Now, in a flavor of that application, I need to extend one of my entities and also provide an extended repository.

For all other beans I need to override/extend I simply create a new implementation and annotate it with @Primary so it will be autowired instead of the default implementation.

For repositories, however, this does not work. I can annotate the new repository with @Primary but it has no effect (both beans are found and can thus not be autowired). This makes sense because the repository is an interface and not an implementation, the implementation is provided by Spring dynamically.

Can I somehow tell Spring (via annotation on the repository or via configuration) which repository to use? Or do I have to do a manual workaround like this Using @Primary in Spring Data JPA repositories or should I come up with a kind of repository provider instead of autowiring?

Edit to make things clearer: Let's say I have an entity A

@Entity
public class A {
  @Id
  private long id;
}

and its repository

public ARepository extends Repository<A, Long> {
}

Now I extend it to the entity B

@Entity
public class B extends A {
}

public interface BRepository extends ARepository {
}

Normally, as per the documentation, you use repositories like this:

@Autowired
private ARepository repository;

This does, however, not work because there are now two beans of the type ARepository. For beans that I implement myself I would use @Primary on the extending class but for repositories there is no implementation of the interface at compile time.

Community
  • 1
  • 1
Erik Hofer
  • 2,296
  • 2
  • 18
  • 39
  • Why does extending of entities and repository for that entity requires what you describe in your second paragraph. ... Maybe the 2. paragraph should been rephrased - I (and it seams nobody else) understand what you want to do/ what your problem is. – Ralph Nov 03 '15 at 15:57
  • @Ralph I added some sample code to explain what my problem is. There is also it linked question where I try to use a repository factory instead of normal autowiring. – Erik Hofer Nov 03 '15 at 18:00
  • Now I got it. - interesting question. – Ralph Nov 04 '15 at 07:51

3 Answers3

5

I would adapt the idea form this answer: https://stackoverflow.com/a/27549198/280244 and this git example https://github.com/netgloo/spring-boot-samples/tree/master/spring-boot-springdatajpa-inheritance/src/main/java/netgloo/models

Introduce a common abstract Repository that is marked with @NoRepositoryBean

@NoRepositoryBean
public interface AbstractARepository<T extends A>
                                    extends Repository<T, Long> {
    T findByName(String name); //or what ever your queries are
}

public ARepository extends AbstractARepository<A> {
   //almost emtpy
}


public BRepository extends AbstractARepository<B> {
   //queries that are special for B
}

Now you can inject ARepository and BRepository, and both are type save!

Community
  • 1
  • 1
Ralph
  • 118,862
  • 56
  • 287
  • 383
  • 1
    Of course I read the question you linked before but I thought it was not applicable to my case. Your code example is very interesting though. This is a good solution if you want to use `A` alongside with `B`. In my case, however, I want to "override" the entity in a way that `@Autowire ARepository` actually returns an implementation of `BRepository` so that enitities are created as `B` and do not have to be loaded again if I want to use `B`'s functionality. This would be the behavior of traditional `@Primary` beans. – Erik Hofer Nov 04 '15 at 08:30
  • @zero_cool: have a close look at my suggestion. The point is, that `ARepository` and `BRepository` are empty. You can inject `ARepository` or `BRepository` by type. Of course `BRepository` does not extends `ARepository`, but I do not expect that this is needed. – Ralph Nov 04 '15 at 08:49
  • @zero_cool:But I you will never load `B` entities again, then there is no reason to have a `BRepository` at all. - Or why do you need it? – Ralph Nov 04 '15 at 08:52
  • Maybe my use case is not that usual. As stated in the original question (maybe wasn't clear), I got an application that only provides `A` and `ARepository` (and in your suggestion `AbstractARepository`). Now in a flavor of the application, e.g. a customization for a specific customer, I need to extend `A` to `B` (and the repository accordingly) so that `A` gets replaced entirely by `B` in the application. In the original application I autowire `ARepository` by type which now has to return an instance of the implementation of `BRepository` and thus create instances of `B` rather than `A` – Erik Hofer Nov 04 '15 at 09:04
  • @zero_cool: "so that A gets replaced entirely by B in the application." then delete all A stuff and replace it by the B stuff (or just rename it A->B and then modify it) – Ralph Nov 04 '15 at 09:14
  • Of course it should only be replaced in that flavor, not the original application. This seems to be possible with everything else, just not with repositories. In that case I probably need to provide repositories with a helper class instead of autowiring. – Erik Hofer Nov 04 '15 at 09:27
  • 1
    I realized that this is indeed the solution, there is just one missing link. The repositories work fine this way. But if I dont want entities of type `A` ending up in my database, I must not create them with `repository.save(new A());` in my main application. That will make the repository distinguish between the types by the `dtype` column. I need to have a kind of factory that returns an instance of `B` in my flavor. I always thought the repositories where the problem, but really I needed to replace the entity instances as well. Thanks Ralph for your patience :) – Erik Hofer Nov 04 '15 at 13:12
1

Just for the record, it is possible to add @Primary support to JPA repositories as suggested here Using @Primary in Spring Data JPA repositories

My implementation of the missing code:

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

I think the answer of @Ralph is better because of the type safety.

Community
  • 1
  • 1
Erik Hofer
  • 2,296
  • 2
  • 18
  • 39
0

Can I somehow tell Spring (via annotation on the repository or via configuration) which repository to use?

Yes you can. First you give each repository class a unique bean name.

@Repository("myARepository")
public ARepository extends Repository<A, Long> {
}

@Repository("myBRepository")
public interface BRepository extends ARepository {
}

Then when you autowire using ARepository as a type you should use the @Qualifier annotation to tell Spring which of the repositories you want.

@Autowire
@Qualifier("myBRepository")
private ARepository repository;

This will autowire a BRepository

Maurice
  • 6,698
  • 9
  • 47
  • 104