2

Quoting https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.custom-implementations

To enrich a repository with custom functionality, you must first define a fragment interface and an implementation for the custom functionality

And Example 32 shows that this is even the case for repositories that do not extend CrudRepository (as in Example 31) or merge multiple repository interfaces (as in Example 33).

It also notes that

The most important part of the class name that corresponds to the fragment interface is the Impl postfix

But forgoes any explanation as to why.

How do custom repositories / repository fragments work, and why isn't an @Repository annotation on an ordinary bean sufficient?

User1291
  • 7,664
  • 8
  • 51
  • 108
  • 1
    If you don't want to extend a repository you don't need a fragment and just create an implementation and just inject that interface. If you want to have functionality added to your repository that needs a custom implementation that is when you need a fragment and adhere to this standard (so you only have 1 repository instead of 2 for your entities). – M. Deinum Nov 26 '20 at 09:22

1 Answers1

1

You have basically two options. The first one is to provide an implementation of each repository interface. This can be seen in your examples. In this case you can override the default implementation of a repository for one entity repo. This can be useful to enhance a special entity repository, e.g. for reportings, etc.

The other option is when you want to provide a base implementation for ALL repositories then you need to use base repository fragments. Repository fragments can be very useful when you want to provide only one base implementation for every custom repository. Let me show you an example:

CustomReadRepository:

@NoRepositoryBean
public interface CustomReadRepository<T> extends Repository<T, String> {

  public List<T> someMethod();

  public List<T> someHighPerformanceMethod();
}

Now let's say we want one interface which combines different repositories and call it DataRepository.

DataRepository:

@NoRepositoryBean
public interface DataRepository<T>
  extends CrudRepository<T, String>, CustomReadRepository<T> {
}

The following interface is now the specific repository for an entity which extends the overall repository

@Repository
public interface UserRepository extends DataRepository<UserEntity> {

}

or

@Repository
public interface OrderRepository extends DataRepository<OrderEntity> {

}

And now we want to provide only ONE implementation for all specific repositories (OrderRepository, UserRepository, etc.).

public class CustomReadRepositoryImpl<T> implements CustomReadRepository<T> {

  private static final EntityPathResolver resolver = SimpleEntityPathResolver.INSTANCE;

  private final JpaEntityInformation<T, ?> entityInformation;
  private final EntityManager entityManager;
  private final Querydsl entityManager;

  public CustomReadRepositoryImpl(JpaEntityInformation<T, ?> entityInformation,
      EntityManager entityManager) {
    this.entityManager = entityManager;
    this.entityInformation = entityInformation;
  }

  @Override
  public List<T> someMethod() {
    //do something and return result
  }
 
  @Override
  public List<T> someHighPerformanceMethod() {
    // do some high performance querying here
  }
}

So what you basically can do here is to enhance your repositories and provide only one implementation which can be useful e.g. to override Spring's default implementations (in order to make it faster or whatever reason). You also can provide new custom repos and implementations.

To make your fragment implementation accessible in Spring you have to do the following:

CustomRepositoryFactoryBean:

public class CustomRepositoryFactoryBean<T extends Repository<S, I>, S, I>
    extends JpaRepositoryFactoryBean<T, S, I> {

  public CustomRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
    super(repositoryInterface);
  }

  protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
    return new CustomRepositoryFactory(entityManager);
  }

this bean should used in the Main class:

Main Class:

@EnableJpaRepositories(repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class)
public class App {
}

and the implementation of the factory:

CustomRepositoryFactory:

public class CustomRepositoryFactory extends JpaRepositoryFactory {

  private final EntityManager entityManager;

  public CustomRepositoryFactory(EntityManager entityManager) {
    super(entityManager);
    this.entityManager = entityManager;
  }

  @Override
  protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
    RepositoryFragments fragments = super.getRepositoryFragments(metadata);

    if (CustomReadRepository.class.isAssignableFrom(
        metadata.getRepositoryInterface())) {

      JpaEntityInformation<?, Serializable> entityInformation = 
          getEntityInformation(metadata.getDomainType());

      Object customRepoFragment = getTargetRepositoryViaReflection(
          CustomReadRepositoryImpl.class, entityInformation, entityManager);

      fragments = fragments.append(RepositoryFragment.implemented(customRepoFragment));
    }

    return fragments;
  }

If you want to see a kind of real example have a look on the question here.

Vetemi
  • 784
  • 2
  • 9
  • 26