Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 352
Arun Sudhakaran Avatar asked Apr 06 '20 08:04

Arun Sudhakaran


People also ask

Why is findById optional?

Its findById method retrieves an entity by its id. The return value is Optional<T> . Optional<T> is a container object which may or may not contain a non-null value. If a value is present, isPresent returns true and get returns the value.

What is orElseThrow in spring boot?

The orElseThrow method will return the value present in the Optional object. If the value is not present, then the supplier function passed as an argument is executed and an exception created on the function is thrown.

What is findOne in JPA?

According with the getOne API: Returns a reference to the entity with the given identifier. According with the findOne API: Retrieves an entity by its id.


2 Answers

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.

like image 140
xenteros Avatar answered Sep 18 '22 17:09

xenteros


@xenteros is right that is the problem. You can use findByIdentificationTypeOrId to get data in one query

return specRepo.findByIdentifcationTypeOrId(idType, idType)
                .orElseThrow(() -> new ResourceNotFoundException("...")));

and repository like

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

    Optional<IdentificationMaster> findByIdentificationTypeOrId(String identificationType, String id);
}
like image 31
Eklavya Avatar answered Sep 18 '22 17:09

Eklavya