1

I've implemented a custom JPA repository as :

public class BaseEntity{...}

public class DerivedEntity extends BaseEntity{...}

@NoRepositoryBean
public interface BaseRepository<T extends BaseEntity> extends JpaRepository<T, ID>{
//some common method
}

@Repository
public interface DerivedRepository extends BaseRepository<DerivedEntity>{
}

Now, I want created a common data-service :

public class CommonService{

private BaseRepository<? extends BaseEntity> baseRepository;


  //wants to use common method by passing derivedRepository
  public CommonService(BaseRepository<? extends BaseEntity>  derivedRepository) {
    this.baseRepository=derivedRepository;
  }

}

Now,Somewhere in the code I want to use save method:

public void foo(BaseEntity entity ) {
    baseRepository.save(entity);
    ...
    ...
  }

I'm getting compile time error:

can not resolve save

How to resolve this issue and design ? Whats the best way to design a common data service by using common data repository ?

Ankit
  • 51
  • 1
  • 6
  • Can you clarify the requirements a bit? Are you saying you want a single service that is agnostic to the entity type that will invoke the appropriate repository for that entity? Meaning there is still 1 repository class per entity? – Joe Chiavaroli Feb 18 '18 at 20:08
  • Yes, I want a base service which is agonistic of entity type. – Ankit Feb 19 '18 at 03:20

1 Answers1

0

There is the PECS rule: Producer Extends, Consumer Super. This is an acronym that helps us to memorize when we should use super and when we should use extends. In this case the save() method is a consumer. It receives an object, but does not return it. So it should be super - and it compiles:

private BaseRepository<? super BaseEntity> baseRepository;
// ...
public void foo(BaseEntity entity ) {
    baseRepository.save(entity);
}

However, the JpaRepository has Producer methods too, like findAll(). If your CommonService uses that too, then this private BaseRepository<? super BaseEntity> baseRepository; is going to be useless.

There is a workaround too: you can specify an entity type at service level, and be more flexible at method level:

public static class CommonService {

    private BaseRepository<BaseEntity> baseRepository;

    public CommonService(BaseRepository<BaseEntity> derivedRepository) {
        this.baseRepository = derivedRepository;
    }

    // here you can accept any subtype you want
    public <T extends BaseEntity> void foo(T entity) {
        baseRepository.save(entity);
    }

    // you can be flexible here
    public List<? super BaseEntity> bar() {
        return baseRepository.findAll();
    }

}

Unfortunately, most of these extends and super and ? signs make the life difficult for the users of this code. I suggest to keep it as simple as possible. When this is not sufficient, then you can play with the generics until you figure out a solution for your needs. This would be the simplest solution:

    // accepts subclasses of BaseEntity
    public void foo(BaseEntity entity) {
        baseRepository.save(entity);
    }

    // reutrns a List<BaseEntity>. The _items_ of the list might be subclasses of BaseEntity.
    public List<BaseEntity> bar() {
        return baseRepository.findAll();
    }

An implementation note:

The following line does not define the ID type. That would something like Integer or Long - a field of BaseEntity.

public static interface BaseRepository<T extends BaseEntity> extends JpaRepository<T, ID>
Tamas Rev
  • 7,008
  • 5
  • 32
  • 49
  • The return type of bar() method is List .So the caller of the service have downcast the the list as:(List) ((List>)commonService.bar(); – Ankit Feb 20 '18 at 09:04
  • You can't downcast lists that way. Java generics are invariant: https://stackoverflow.com/questions/18666710/why-are-arrays-covariant-but-generics-are-invariant Moreover, the items aren't always necessarily `DerivedEntity`-es. On the other hand, you can downcast the items, if necessary. – Tamas Rev Feb 20 '18 at 09:51