Spring Data JPA : Efficient Way to Invoke Repository Methods with Optional Parameters - spring

I have the below Java 11 method which is invoked by the controller where ID is the required param and status,version are optional params. I had to write multiple repository methods to fetch the record based on those params. Am wondering is there a better/effiecient way to refactor this method with out the if/else ladder?
#Override
#Transactional(transactionManager = "customTransactionManager")
public Optional<String> getInformation(UUID id, Status status, Long version) {
try {
Preconditions.checkNotNull(id, ID_MUST_BE_NOT_NULL_MSG);
if (status != null && version != null) {
return repository.findByIdAndVersionAndStatus(id, version, status);
} else if (status != null) {
return repository.findFirstByIdAndStatus(id, status);
} else if (version != null) {
return repository.findFirstByIdAndVersion(id, version);
} else {
return repository.findFirstByIdOrderByIdDesc(id);
}
} catch (Exception e) {
log.error(e);
throw new CustomException(MessageFormat.format(PUBLIC_ERROR_MESSAGE, id));
}
}

You could use Specifications for that:
private Specification<YourEntity> toSpecification(UUID id, Status status, Long version) {
return (root, query, builder) -> {
Set<Predicate> predicates = new HashSet<>();
predicates.add(builder.equal(root.get("id"), id));
if (status != null) predicates.add(builder.equal(root.get("status"), status));
if (version != null) predicates.add(builder.equal(root.get("version"), version));
return builder.and(predicates.toArray(Predicate[]::new));
};
}
If you let your repository extend JpaSpecificationExecutor you can use the build specification object like so:
Specification<YourEntity> specification = toSpecification(id, status, version);
Optional<YourEntity> result = repository.findOne(specification);
When using Hibernate Metamodel Generator you can also write builder.equal(YourEntity_.id, id) instead of builder.equal(root.get("id"), id).

In addition to the accepted answer, I find Query By Examples much more intuitive and simple.
https://www.baeldung.com/spring-data-query-by-example would be a good start.
It basically creates a query based on non-null fields from your jpa entity.

Related

How to correctly chain Mono/Flux calls

I'm having trouble with understanding how to achieve my goal with reactive approach.
Let's assume that I have a Controller, that will return Flux:
#PostMapping(value = "/mutation/stream/{domainId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Mutation> getMutationReactive(#RequestBody List<MutationRequest> mutationRequests, #PathVariable Integer domainId) {
return mutationService.getMutations(mutationRequests, domainId);
}
In service, currently with .subscribeOn(Schedulers.boundedElastic()), because it calls for a blocking code that is wrapped into a Callable.
public Flux<Mutation> getMutations(List<MutationRequest> mutationRequests, int domainId) {
return Flux.fromIterable(mutationRequests)
.subscribeOn(Schedulers.boundedElastic())
.flatMap(mutationRequest -> getMutation(mutationRequest.getGameId(), mutationRequest.getTypeId(), domainId));
}
getMutation() with blocking calls, currently wrapped into a Callable:
private Mono<Mutation> getMutation(int gameId, int typeId, int domainId) {
return Mono.fromCallable(() -> {
Mutation mutation = mutationProvider.findByGameIdAndTypeId(gameId, typeId).block(); // mutationProvider.findByGameIdAndTypeId() returns Mono<Mutation>
if (mutation == null) {
throw new RuntimeException("Mutation was not found by gameId and typeId");
}
State state = stateService.getStateByIds(mutation.getId()), domainId).blockFirst(); //stateService.getStateByIds() returns Mono<State>
if (state == null || state.getValue() == null) {
log.info("Requested mutation with gameId[%s] typeId[%s] domainId[%s] is disabled. Value is null.".formatted(gameId, typeId, domainId));
return null;
}
mutation.setTemplateId(state.getTemplateId());
return (mutation);
});
}
How do I approach the getMutation() function to use reactive streams, instead of using .block() methods inside a Callable?
Basically, I first need to retrieve Mutation from DB -> then using ID of mutation, get its state from other service -> then if state and its value are not null, set templateId of state to mutation and return, or return null.
I've tried something like this:
private Mono<Mutation> getMutation(int gameId, int typeId, int domainId) {
return mutationProvider.findByGameIdAndTypeId(gameId, typeId)
.flatMap(mutation -> {
stateService.getStatesByIds(mutation.getId(), domainId).flatMap(state -> {
if (state != null && state.getValue() != null) {
mutation.setTemplateId(state.getTemplateId());
}
//TODO if state/value is null -> need to propagate further to return null instead of mutation...
return Mono.justOrEmpty(state);
});
return Mono.just(mutation);
});
}
But it's obviously incorrect, nothing is subscribed to stateService.getStatesByIds(mutation.getId()), domainId)
AND
I would like to return a null if the retrieved state of mutation or its value are null.
You are ignoring the value of the inner flatMap hence the warning.
Without trying you need something like this
private Mono<Mutation> getMutation(int gameId, int typeId, int domainId) {
return mutationProvider.findByGameIdAndTypeId(gameId, typeId)
.flatMap(mutation -> {
return stateService.getStatesByIds(mutation.getId(), domainId).flatMap(state -> {
if (state != null && state.getValue() != null) {
mutation.setTemplateId(state.getTemplateId());
return Mono.just(mutation);
}
return Mono.empty();
});
});
}
Although not sure if you could rewrite the outer flatMap not to a regular map instead and you might want to use filter and defaultIfEmpty with that as well
private Mono<Mutation> getMutation(int gameId, int typeId, int domainId) {
return mutationProvider.findByGameIdAndTypeId(gameId, typeId)
.flatMap(mutation -> {
return stateService.getStatesByIds(mutation.getId(), domainId)
.filter(state -> state != null && state.getValue() != null)
.flatMap(state -> {
mutation.setTemplateId(state.getTemplateId());
return Mono.just(mutation);})
.defaultIfEmpty(Mono.empty());
}
This is just from the top of my head and I have no idea what some of the return types are here (Flux or Mono) for your own APIs.

How to define dependencies on two client calls in quarkus reactive programming

I have two Client APIs that return an Uni.
Uni<Customer> getCustomer(customerID)
Uni<Address> getAddress(addressID)
And I want to open a REST API
Uni<FullCustomer> getFullCustomer(String customerID)
The logic is to make the Customer Client call first. If the returned customer object has addressID then make the second Address Client call and get shipping address details. If shipping address is not available then just wrap the customer in FullCustomer object and return else wrap both customer and address in FullCustomer object and return.
I dont want to block the thread on client call (await().indefinitely()), hence i am using onItem and transfer method call. But my code returns a Uni<Uni> and i want it to return a Uni.
#GET
#Path("api/customer/{id}")
#Produces({ "application/json" })
Uni<Uni<FullCustomer>> getFullCustomer(#PathParam("id") String customerID){
Uni<Customer> customerResponse = getCustomer(customerID);
Uni<Uni<FullCustomer>> asyncResponse = customerResponse.onItem().transform(customer -> {
if (customer.getAddressId() != null) {
Uni<Address> addressResponse = getAddress(customer.getAddressId());
Uni<FullCustomer> fullCustomer = addressResponse.onItem().transform(address -> {
if (address.getShippingAddress() != null) {
return new FullCustomer(customer, address.getShippingAddress());
} else {
return new FullCustomer(customer);
}
});
}
return Uni.createFrom().item(new FullCustomer(customer));
});
return asyncResponse;
}
How can I rewrite my code so that it returns Uni keeping reactive ( async client ) calls
Got the solution. Thanks Ladicek for comments.
public Uni<FullCustomer> getFullCustomer(#PathParam("id") String customerID) {
return getCustomer(customerID)
.onItem()
.transformToUni(customer -> {
if (customer.getAddressId() != null) {
return getAddress(customer.getAddressId()).onItem().transform(address -> {
if (address.getShippingAddress() != null) {
return new FullCustomer(customer, address.getShippingAddress());
} else {
return new FullCustomer(customer);
}
});
} else {
return Uni.createFrom().item(new FullCustomer(customer));
}
});
}

Getting multiple Mono objects with reactive Mongo queries

I'm using the webflux framework for spring boot, the behavior I'm trying to implement is creating a new customer in the database, if it does not already exist (throw an exception if it does)
and also maintain another country code database (if the new customer is from a new country, add to the database, if the country is already saved, use the old information)
This is the function in the service :
public Mono<Customer> createNewCustomer(Customer customer) {
if(!customer.isValid()) {
return Mono.error(new BadRequestException("Bad email or birthdate format"));
}
Mono<Customer> customerFromDB = customerDB.findByEmail(customer.getEmail());
Mono<Country> countryFromDB = countryDB.findByCountryCode(customer.getCountryCode());
Mono<Customer> c = customerFromDB.zipWith(countryFromDB).doOnSuccess(new Consumer<Tuple2<Customer, Country>>() {
#Override
public void accept(Tuple2<Customer, Country> t) {
System.err.println("tuple " + t);
if(t == null) {
countryDB.save(new Country(customer.getCountryCode(), customer.getCountryName())).subscribe();
customerDB.save(customer).subscribe();
return;
}
Customer cus = t.getT1();
Country country = t.getT2();
if(cus != null) {
throw new CustomerAlreadyExistsException();
}
if(country == null) {
countryDB.save(new Country(customer.getCountryCode(), customer.getCountryName())).subscribe();
}
else {
customer.setCountryName(country.getCountryName());
}
customerDB.save(customer).subscribe();
}
}).thenReturn(customer);
return c;
}
My problem is, the tuple returns null if either country or customer are not found, while I need to know about them separately if they exist or not, so that I can save to the database correctly.
country == null is never true
I also tried to use customerFromDB.block() to get the actual value but I receive an error that it's not supported, so I guess that's not the way
Is there anyway to do two queries to get their values?
Solved it with the following solution:
public Mono<Customer> createNewCustomer(Customer customer) {
if(!customer.isValid()) {
return Mono.error(new BadRequestException("Bad email or birthdate format"));
}
return customerDB.findByEmail(customer.getEmail())
.defaultIfEmpty(new Customer("empty", "", "", "", "", ""))
.flatMap(cu -> {
if(!cu.getEmail().equals("empty")) {
return Mono.error(new CustomerAlreadyExistsException());
}
return countryDB.findByCountryCode(customer.getCountryCode())
.defaultIfEmpty(new Country(customer.getCountryCode(), customer.getCountryName()))
.flatMap(country -> {
customer.setCountryName(country.getCountryName());
customerDB.save(customer).subscribe();
countryDB.save(country).subscribe();
return Mono.just(customer);});
});
}
Instead of doing both queries simulatneaously, I queried for one result and then queries for the next, I think this is the reactive way of doing it, but I'm open for corrections.

How to reduce the if-else depth?

I have this example code
public static ActionProcessable getActionProcessor(TaskType currentTaskType, UserAction userAction){
String actionKey;
if(userAction != null){
if(currentTaskType != null){
actionKey = buildKey(currentTaskType, userAction);
if(dossierActions.containsKey(actionKey)){
return dossierActions.get(actionKey);
}
}
actionKey = buildKey(anyTaskType(), userAction);
if(dossierActions.containsKey(actionKey)){
return dossierActions.get(actionKey);
}
}
return new NullActionProcessor();
}
In this logic i have a map to store the ActionProcessable by combined-key TaskType and UserAction. This method will return ActionProcessable with input taskType and action. TaskType can be null so in that case we only need to get by userAction.
When i check this code by sonar, it say the third if is "Nested if-else depth is 2 (max allowed is 1)"
But i don't know how to make it better.
Does anyone suggest me something?
You can move "if containsKey" part out of condition to remove code duplication:
public static ActionProcessable getActionProcessor(TaskType currentTaskType, UserAction userAction){
if (userAction != null) {
String actionKey = currentTaskType != null
? buildKey(currentTaskType, userAction)
: buildKey(anyTaskType(), userAction);
if (dossierActions.containsKey(actionKey)){
return dossierActions.get(actionKey);
}
}
return new NullActionProcessor();
}
Now, intent of the code looks more clear (at least, for me).
You can also make the first condition short-circuit and\or use ternary if for containsKey, it will remove even more ifs, but can make code more complex for some people.
public static ActionProcessable getActionProcessor(TaskType currentTaskType, UserAction userAction){
if (userAction == null) {
return new NullActionProcessor();
}
String actionKey = currentTaskType != null
? buildKey(currentTaskType, userAction)
: buildKey(anyTaskType(), userAction);
return dossierActions.containsKey(actionKey)
? dossierActions.get(actionKey);
: new NullActionProcessor();
}
Choose the one you like, they are technically similar.
Since you haven't specified specific programming language, one more thing to say: your code is a good example of use case of null-coalsecing operator. Sadly, AFAIK, there is none in Java. In C#, the code could look like this:
public static ActionProcessable GetActionProcessor(TaskType currentTaskType, UserAction userAction) {
if (userAction == null) {
return new NullActionProcessor();
}
var actionKey = BuildKey(currentTaskType ?? anyTaskType(), userAction);
return dossierActions[actionKey] ?? new NullActionProcessor();
}

Relay nodeDefinition throws "Right-hand side of 'instanceof' is not callable"

I am learning GraphQL and have just started implementing graphql-relay on my node server in the past couple days. When declaring my Relay node definitions I am getting the error "Right-hand side of 'instanceof' is not callable", where the right-hand side is an object with a constructor function. As far as I can tell this is not due to improper usage, also considering this is copied from the docs. I am not sure what the expected outcome is when it is properly working, I assume it returns the GraphQL type to be put through the works and have the requested data returned.
var {nodeInterface, nodeField} = nodeDefinitions(
(globalId) => {
var {type, id} = fromGlobalId(globalId);
console.log(type);
if (type === 'User') {
return db.models.user.findById(id)
} else if (type === 'Video') {
return db.models.video.findById(id)
}
else if (type === 'Producer') {
return db.models.user.findById(id)
}
else if (type === 'Viewer') {
return db.models.user.findById(id)
}else {
return null;
}
},
(obj) => {
console.log(obj); // Sequelize object
console.log(User); // user
console.log(User.constructor); // valid constructor
// This is where the error occurs
if (obj instanceof User) {
return UserType;
} else if (obj instanceof Video) {
return VideoType;
} else {
return null;
}
});
Notes:
Using Sequelize ORM.
User is an Interface in GraphQL the schema implemented by Viewer, Producer, and GeneralUser types. My psql database on the otherhand has one User table which is why the second function only checks for User and not these additional types.
All my other queries for users, videos, etc. work fine, it is only when searching by node && globalId when it breaks
An "object with a constructor function" isn't callable. Probably you need to make that object a class with a constructor like this:
class User {
constructor(id, name, email) {
this.id = id;
// etc
}
}
You need to change the code from:
...
if (obj instanceof User) {
...
} else if (obj instanceof Video) {
....
to:
...
if (obj instanceof User.Instance) {
...
} else if (obj instanceof Video.Instance) {
....

Resources