Search code examples
javaspring-bootspring-data-jpaoption-typefindby

JPA findBy method always goes to orElseThrow


This is our code

private IdentificationMaster validateIdentificationType(String idType) {
    if(!StringUtils.isNotBlank(idType))
        throw new IllegalArgumentException("Invalid idType");

    Optional<IdentificationMaster> op1 = specRepo.findById(idType); //testing purpose

    Optional<IdentificationMaster> op2 = specRepo.findByIdentificationType(idType); //testing purpose

    return specRepo.findById(idType)
            .orElse(specRepo.findByIdentificationType(idType)
                    .orElseThrow(() -> new ResourceNotFoundException("Id Type Not Found " + idType)));
}

For idType we're expecting two values it can either be primary key id or its corresponding identificationType. Table only has two columns id and identificationType. The problem is that it throws ResourceNotFoundException even if op1 or op2 is not empty. Now if I change my return like this

return specRepo.findByIdentificationType(idType)
            .orElse(specRepo.findById(idType)
                .orElseThrow(() -> new ResourceNotFoundException("Id Type Not Found " + idType)));

Its again throwing the same exception!

Repository

@Repository
public interface IdentificationSpecRepository extends CrudRepository<IdentificationMaster, String>{

    Optional<IdentificationMaster> findByIdentificationType(String identificationType);
}

Entity

@Entity
@Table(name = "IDENTIFICATION_MASTER")
public class IdentificationMaster {

    @Id
    @Column(name = "ID")
    private String id;


    @Column(name = "IDENTIFICATION_TYPE", unique = true)
    private String identificationType;

    // getters and setters

}

What could be the problem?


Solution

  • return specRepo.findByIdentificationType(idType)
                .orElse(specRepo.findById(idType)
                    .orElseThrow(() -> new ResourceNotFoundException("...")));
    

    Is the reason.

    Java is quite eager in execution and always calls the orElse method to prepare just in case it would need it.

    The order of your execution is somehow:

    1. specRepo.findByIdentificationType(idType)
    2. orElse cannot be executed as it's argument is not evaluated yet
    3. specRepo.findById(idType)
    4. .orElseThrow(() -> new ResourceNotFoundException("..."))
    5. The result of 3 and 4 becomes an object o
    6. orElse(o)

    Instead of using orElse one should prefer orElseGet.

    return specRepo.findByIdentificationType(idType)
                .orElseGet(() -> specRepo.findById(idType)
                    .orElseThrow(() -> new ResourceNotFoundException("...")));
    

    It will only be called when needed.

    We have two scenarios here:

    1. specRepo returns an non-empty Optional.
    2. specRepo returns empty object.

    In scenario 1, idType is a valid identificationType thus is not an id, so the findById will throw an exception. In scenario 2, idType is not a valid identificationType and if it is a legal id the method should result in exception being thrown.

    Edit:

    While this answers diagnoses the problem and describes what is the reason of such behavior, @Abinash Ghosh answer provides the simplest and imo best solution of the problem.

    In general, avoid using orElse. In this case, add the findByIdentificationTypeOrId(String it, String id) to your repository.