Search code examples
javaspringspring-data-jpaspring-data

JPA Query with several different @Id columns


Problem

To make my code cleaner i want to introduce a generic Repository that each Repository could extend and therefore reduce the code i have to have in each of them. The problem is, that the Ids differ from Class to Class. On one (see example below) it would be id and in the other randomNumber and on the other may even be an @EmbeddedId. I want to have a derived (or non derived) query in the respository that gets One by id.

Preferred solution

I Imagine having something like:

public interface IUniversalRepository<T, K>{
    @Query("select t from # {#entityName} where @id = ?1")
    public T findById(K id);
}

Ecample Code

(that does not work because attribute id cannot be found on Settings)

public interface IUniversalRepository<T, K>{
    //should return the object with the id, reagardless of the column name
    public T findById(K id);
}
// two example classes with different @Id fields
public class TaxRate {

    @Id
    @Column()
    private Integer id;

    ...
}

public class Settings{

    @Id
    @Column() //cannot rename this column because it has to be named exactly as it is for backup reason
    private String randomNumber;

    ...
}
// the Repository would be used like this
public interface TaxRateRepository extends IUniversalRepository<TaxRate, Integer> {
}

public interface SettingsRepository extends IUniversalRepository<TaxRate, String> {
}

Happy for suggestions.


Solution

  • The idea of retrieving JPA entities via "id query" is not so good as you might think, the main problem is that is much slower, especially when you are hitting the same entity within transaction multiple times: if flush mode is set to AUTO (which is actually the reasonable default) Hibernate needs to perform dirty checking and flush changes into database before executing JPQL query, moreover, Hibernate doesn't guarantee that entities, retrieved via "id query" are not actually stale - if entity was already present in persistence context Hibernate basically ignores DB data.

    The best way to retrieve entities by id is to call EntityManager#find(java.lang.Class<T>, java.lang.Object) method, which in turn backs up CrudRepository#findById method, so, yours findByIdAndType(K id, String type) should actually look like:

    default Optional<T> findByIdAndType(K id, String type) {
        return findById(id)
                .filter(e -> Objects.equals(e.getType(), type));
    }
    

    However, the desire to place some kind of id placeholder in JQPL query is not so bad - one of it's applications could be preserving order stability in queries with pagination. I would suggest you to file corresponding CR to spring-data project.