Skip to content

La sostituzione del deprecato QuerydslJpaRepository con QuerydslJpaPredicateExecutor fallisce

Il nostro team di esperti, dopo alcuni giorni di ricerca e raccolta di informazioni, ha trovato i dati necessari, la nostra speranza è che sia utile per il vostro lavoro.

Soluzione:

Con Spring Boot 2.1.1 la seguente soluzione può essere utile. La chiave è estendere JpaRepositoryFactory e sovrascrivere il metodo getRepositoryFragments(RepositoryMetadata metadata). In questo metodo si possono fornire implementazioni di base (o frammenti più specifici) per qualsiasi repository personalizzato, che dovrebbe essere preso per ogni repository estensibile.

Mostriamo un esempio:

QueryableReadRepository:

@NoRepositoryBean
public interface QueryableReadRepository extends Repository {

  List findAll(Predicate predicate);

  List findAll(Sort sort);

  List findAll(Predicate predicate, Sort sort);

  List findAll(OrderSpecifier... orders);

  List findAll(Predicate predicate, OrderSpecifier... orders);

  Page findAll(Pageable page);

  Page findAll(Predicate predicate, Pageable page);

  Optional findOne(Predicate predicate);

  boolean exists(Predicate predicate);
}

La seguente interfaccia combina diversi repository.

Repository di dati:

@NoRepositoryBean
public interface DataRepository
    extends CrudRepository, QueryableReadRepository {
}

Ora i repository del dominio specifico possono estendersi da DataRepository:

@Repository
public interface UserRepository extends DataRepository {

}

QueryableReadRepositoryImpl:

@Transactional
public class QueryableReadRepositoryImpl extends QuerydslJpaPredicateExecutor
    implements QueryableReadRepository {

  private static final EntityPathResolver resolver = SimpleEntityPathResolver.INSTANCE;

  private final EntityPath path;
  private final PathBuilder builder;
  private final Querydsl querydsl;

  public QueryableReadRepositoryImpl(JpaEntityInformation entityInformation,
      EntityManager entityManager) {
    super(entityInformation, entityManager, resolver, null);
    this.path = resolver.createPath(entityInformation.getJavaType());
    this.builder = new PathBuilder(path.getType(), path.getMetadata());
    this.querydsl = new Querydsl(entityManager, builder);
  }

  @Override
  public Optional findOne(Predicate predicate) {
    return super.findOne(predicate);
  }

  @Override
  public List findAll(OrderSpecifier... orders) {
    return super.findAll(orders);
  }

  @Override
  public List findAll(Predicate predicate, Sort sort) {
    return executeSorted(createQuery(predicate).select(path), sort);
  }

  @Override
  public Page findAll(Predicate predicate, Pageable pageable) {
    return super.findAll(predicate, pageable);
  }

  @Override
  public List findAll(Predicate predicate) {
    return super.findAll(predicate);
  }

  public List findAll(Sort sort) {
    return executeSorted(createQuery().select(path), sort);
  }

  @Override
  public Page findAll(Pageable pageable) {
    final JPQLQuery countQuery = createCountQuery();
    JPQLQuery query = querydsl.applyPagination(pageable, createQuery().select(path));

    return PageableExecutionUtils.getPage(
        query.distinct().fetch(), 
        pageable,
        countQuery::fetchCount);
  }

  private List executeSorted(JPQLQuery query, Sort sort) {
    return querydsl.applySorting(sort, query).distinct().fetch();
  }
}

Fabbrica di repository personalizzati:

public class CustomRepositoryFactoryBean, S, I>
    extends JpaRepositoryFactoryBean {

  public CustomRepositoryFactoryBean(Class repositoryInterface) {
    super(repositoryInterface);
  }

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

Fabbrica di repository personalizzati:

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 (QueryableReadRepository.class.isAssignableFrom(
        metadata.getRepositoryInterface())) {

      JpaEntityInformation entityInformation = 
          getEntityInformation(metadata.getDomainType());

      Object queryableFragment = getTargetRepositoryViaReflection(
          QueryableReadRepositoryImpl.class, entityInformation, entityManager);

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

    return fragments;
  }

Classe principale:

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

Questo ha il vantaggio di fornire una sola implementazione (frammento) per un repo personalizzato. L'implementazione del repository di base è ancora quella predefinita di Spring. L'esempio ha fornito un nuovo repo, ma probabilmente si può anche sovrascrivere l'implementazione predefinita di QuerydslPredicateExecutor in CustomRepositoryFactory

In Spring Data JPA 2.1.6 il costruttore di QuerydslJpaPredicateExecutor è cambiato.
Qui presento un approccio alternativo, utilizzando un wrapper per https://stackoverflow.com/a/53960209/3351474. Questo rende la soluzione indipendente dagli interni di Spring Data JPA. È necessario implementare tre classi.

A titolo di esempio, presento un'implementazione personalizzata di Querydsl, utilizzando sempre la classe creationDate di un'entità come criterio di ordinamento se non viene passato nulla. In questo esempio assumo che questa colonna esista in un qualche file @MappedSuperClass per tutte le entità. Utilizzare i metadati statici generati nella vita reale al posto della stringa "creationDate" codificata in modo rigido.

Come prima cosa il wrapped che delega tutti i CustomQuerydslJpaRepositoryIml delegando tutti i metodi alla classe QuerydslJpaPredicateExecutor:

/**
 * Customized Querydsl JPA repository to apply custom filtering and sorting logic.
 *
 */
public class CustomQuerydslJpaRepositoryIml implements QuerydslPredicateExecutor {

    private final QuerydslJpaPredicateExecutor querydslPredicateExecutor;

    public CustomQuerydslJpaRepositoryIml(QuerydslJpaPredicateExecutor querydslPredicateExecutor) {
        this.querydslPredicateExecutor = querydslPredicateExecutor;
    }

    private Sort applyDefaultOrder(Sort sort) {
        if (sort.isUnsorted()) {
            return Sort.by("creationDate").ascending();
        }
        return sort;
    }

    private Pageable applyDefaultOrder(Pageable pageable) {
        if (pageable.getSort().isUnsorted()) {
            Sort defaultSort = Sort.by(AuditableEntity_.CREATION_DATE).ascending();
            pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), defaultSort);
        }
        return pageable;
    }

    @Override
    public Optional findOne(Predicate predicate) {
        return querydslPredicateExecutor.findOne(predicate);
    }

    @Override
    public List findAll(Predicate predicate) {
        return querydslPredicateExecutor.findAll(predicate);
    }

    @Override
    public List findAll(Predicate predicate, Sort sort) {
        return querydslPredicateExecutor.findAll(predicate, applyDefaultOrder(sort));
    }

    @Override
    public List findAll(Predicate predicate, OrderSpecifier... orders) {
        return querydslPredicateExecutor.findAll(predicate, orders);
    }

    @Override
    public List findAll(OrderSpecifier... orders) {
        return querydslPredicateExecutor.findAll(orders);
    }

    @Override
    public Page findAll(Predicate predicate, Pageable pageable) {
        return querydslPredicateExecutor.findAll(predicate, applyDefaultOrder(pageable));
    }

    @Override
    public long count(Predicate predicate) {
        return querydslPredicateExecutor.count(predicate);
    }

    @Override
    public boolean exists(Predicate predicate) {
        return querydslPredicateExecutor.exists(predicate);
    }
}

Successivamente il CustomJpaRepositoryFactory che fa la magia e fornisce la classe wrapper Querydsl al posto di quella predefinita. Quella predefinita viene passata come parametro e avvolta.

/**
 * Custom JpaRepositoryFactory allowing to support a custom QuerydslJpaRepository.
 *
 */
public class CustomJpaRepositoryFactory extends JpaRepositoryFactory {

    /**
     * Creates a new {@link JpaRepositoryFactory}.
     *
     * @param entityManager must not be {@literal null}
     */
    public CustomJpaRepositoryFactory(EntityManager entityManager) {
        super(entityManager);
    }

    @Override
    protected RepositoryComposition.RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
        final RepositoryComposition.RepositoryFragments[] modifiedFragments = {RepositoryComposition.RepositoryFragments.empty()};
        RepositoryComposition.RepositoryFragments fragments = super.getRepositoryFragments(metadata);
        // because QuerydslJpaPredicateExecutor is using som internal classes only a wrapper can be used.
        fragments.stream().forEach(
                f -> {
                    if (f.getImplementation().isPresent() &&
                            QuerydslJpaPredicateExecutor.class.isAssignableFrom(f.getImplementation().get().getClass())) {
                        modifiedFragments[0] = modifiedFragments[0].append(RepositoryFragment.implemented(
                                new CustomQuerydslJpaRepositoryIml((QuerydslJpaPredicateExecutor) f.getImplementation().get())));
                    } else {
                        modifiedFragments[0].append(f);
                    }
                }
        );
        return modifiedFragments[0];
    }
}

Infine il CustomJpaRepositoryFactoryBean. Questo deve essere registrato con l'applicazione Spring Boot, per far sì che Spring sappia da dove prendere le implementazioni del repository, ad esempio con:

@SpringBootApplication
@EnableJpaRepositories(basePackages = "your.package",
        repositoryFactoryBeanClass = CustomJpaRepositoryFactoryBean.class)
...

Ecco ora la classe:

public class CustomJpaRepositoryFactoryBean, S, I> extends JpaRepositoryFactoryBean {

    /**
     * Creates a new {@link JpaRepositoryFactoryBean} for the given repository interface.
     *
     * @param repositoryInterface must not be {@literal null}.
     */
    public CustomJpaRepositoryFactoryBean(Class repositoryInterface) {
        super(repositoryInterface);
    }

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

Se accetti, puoi lasciare una divisione su ciò che ti ha colpito di questo post.



Utilizzate il nostro motore di ricerca

Ricerca
Generic filters

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.