Combining JPA And/Or Criteria Predicates - java

I want to query like: (sku = 'A' AND uom_code = 'B') OR (sku =
C' AND uom_code = 'D')
But my predicate generate such as (sku = 'A' AND uom_code = 'B' OR sku = 'C' AND uom_code = 'D')
how to fix it. Thank you.
public class VariantSpecification implements Specification<VariantModel> {
private final VariantFilter filter;
public VariantSpecification(VariantFilter filter) {
this.filter = filter;
}
#Override
public Predicate toPredicate(
Root<VariantModel> root,
CriteriaQuery<?> query,
CriteriaBuilder cb
) {
final List<Predicate> predicates = new ArrayList<>();
if (filter.getMerchantId() != null) {
predicates.add(cb.equal(root.get("merchantId"), filter.getMerchantId()));
}
Predicate predicate = cb.and(predicates.toArray(new Predicate[0]));
List<Predicate> predicatesMap = new ArrayList<>();
if (!CollectionUtils.isEmpty(filter.getSkusUoms())) {
filter.getSkusUoms().forEach(pair -> {
String sku = pair.getLeft();
String uom = pair.getRight();
Predicate predicateSku = cb.equal(root.get("sku"), sku);
Predicate predicateUom = cb.equal(root.get("uomCode"), uom);
predicatesMap.add(cb.or(cb.and(predicateSku, predicateUom)));
});
}
if (!predicatesMap.isEmpty()) {
Predicate predicateSector = cb.or(predicatesMap.toArray(new Predicate[0]));
return cb.and(predicate, predicateSector);
}
return predicate;
}
}

Yeah my mistake,
SELECT ... FROM ... WHERE (expression1 AND expression2) OR (expression3 AND expression4)
SELECT ... FROM ... WHERE expression1 AND expression2 OR expression3 AND expression4
According to the SQL specification, both statements mean the same thing. It doesn't matter whether the statement contains the () or not, so Hibernate doesn't use them. The order of precedence is like this, similar to 1+2*3 is the same as 1+(2*3).

Related

JPA many to many constraint violation exception

I am using JPA to store data and faced two problems during implementation. I have two entities (Station and Commodity) that have many-to-many relationship with intermediate table so that I had to created the third one. When app receives message it converts its data to entites and should save but sometimes app throwing a ConstraintViolationException because there is null value at foreign key field referencing to Commodity entity.
I've tried simple approach: selecting needed commodity from database and saving it if there is no one. Then I started to use bulk searching all commodities of message and then putting it where are needed. None of them did a trick.
In my opinion the problem could be caused by multi-threading read\insert.
The second problem is that service stop running when exception is thrown. App can lost some of transactions that's not a big deal but it simply stops after rollback.
How can I resolve these conflicts?
Here is code of data handling class and diagram of entities :
#Service
#AllArgsConstructor
#Slf4j
public class ZeromqCommoditiesServiceImpl implements ZeromqCommoditesService {
private final CategoryTransactionHandler categoryHandler;
private final CommodityTransactionHandler commodityHandler;
private final EconomyTransactionHandler economyHandler;
private final StationTransactionHandler stationHandler;
private final SystemTransactionHandler systemHandler;
#Override
#Transactional(
isolation = Isolation.READ_COMMITTED,
propagation = Propagation.REQUIRES_NEW,
rollbackFor = Throwable.class)
#Modifying
public void saveData(ZeromqCommodityPayload payload) {
CommodityContent content = payload.getContent();
var station = stationHandler.createOrFindStation(content.getStationName());
var system = systemHandler.createOrFindSystem(content.getSystemName());
var commodityReferences = getMapOfCommodities(content);
station.setSystem(system);
updateEconomies(station, content);
updateProhibited(station, content, commodityReferences);
updateStationCommodities(station, content, commodityReferences);
try {
saveStation(station);
} catch (ConstraintViolationException | PersistentObjectException | DataAccessException e) {
log.error("Error saving commodity info \n" + content, e);
}
}
public void saveStation(StationEntity station) {
stationHandler.saveStation(station);
if (station.getId() != null) {
log.debug(String.format("Updated \"%s\" station info", station.getName()));
} else {
log.debug(String.format("Updated \"%s\" station info", station.getName()));
}
}
private void updateEconomies(StationEntity station, CommodityContent content) {
station.getEconomies().clear();
if (content.getEconomies() != null) {
var economies = content.getEconomies()
.stream()
.map(economy -> {
var stationEconomyEntity = economyHandler.createOrFindEconomy(economy.getName());
Double proportion = economy.getProportion();
stationEconomyEntity.setProportion(proportion != null ? proportion : 1.0);
return stationEconomyEntity;
})
.peek(economy -> economy.setStation(station))
.toList();
station.getEconomies().addAll(economies);
}
}
private void updateProhibited(
StationEntity station,
CommodityContent content,
Map<String, CommodityEntity> commodityEntityMap) {
station.getProhibited().clear();
if (content.getProhibited() != null) {
var prohibitedCommodityEntities = content.getProhibited()
.stream()
.map(prohibited -> {
String eddnName = prohibited.toLowerCase(Locale.ROOT);
CommodityEntity commodityReference = getCommodityEntity(commodityEntityMap, eddnName);
return new ProhibitedCommodityEntity(station, commodityReference);
}
)
.toList();
station.getProhibited().addAll(prohibitedCommodityEntities);
}
}
private void updateStationCommodities(
StationEntity station,
CommodityContent content,
Map<String, CommodityEntity> commodityEntityMap) {
station.getCommodities().clear();
if (content.getCommodities() != null) {
var commodities = content.getCommodities()
.stream()
.map(commodity -> {
CommodityEntity commodityReference = getCommodityEntity(
commodityEntityMap,
commodity.getEddnName());
return StationCommodityEntity.builder()
.commodity(commodityReference)
.buyPrice(commodity.getBuyPrice())
.sellPrice(commodity.getSellPrice())
.demand(commodity.getDemand())
.stock(commodity.getStock())
.station(station)
.build();
})
.toList();
station.getCommodities().addAll(commodities);
}
}
private CommodityEntity getCommodityEntity(Map<String, CommodityEntity> commodityEntityMap, String eddnName) {
return commodityEntityMap.get(eddnName);
}
private Map<String, CommodityEntity> getMapOfCommodities(#NotNull CommodityContent content) {
Set<String> commodities = content.getCommodities()
.stream()
.map(Commodity::getEddnName)
.collect(Collectors.toSet());
if (content.getProhibited() != null && content.getProhibited().size() > 0) {
commodities.addAll(content.getProhibited().
stream()
.map(item -> item.toLowerCase(Locale.ROOT))
.collect(Collectors.toSet()));
}
var commodityReferencesMap = commodityHandler.findAllByEddnName(commodities)
.stream()
.collect(Collectors.toMap(
CommodityEntity::getEddnName,
item -> item
));
commodities.forEach(commodity -> {
if (commodityReferencesMap.get(commodity.toLowerCase()) == null) {
CommodityCategoryEntity category = categoryHandler.createOrFindCategory("Unknown");
CommodityEntity newCommodity = new CommodityEntity(commodity, commodity, category);
CommodityEntity managedCommodity = commodityHandler.saveCommodity(newCommodity);
commodityReferencesMap.put(managedCommodity.getEddnName(), managedCommodity);
}
});
return commodityReferencesMap;
}
}
Thanks in advance

Clean code - Search endpoint with one or more filters with specific rules: How to avoid multiple "ifs"?

if you were developing a search endpoint that can have one or more filters and there are specific rules for combining these filters, how to avoid using multiple "ifs"?
private void addCriteria(ProductFilter filter, Query query) {
//user does not fill anything, throws exception
if (ObjectUtils.isEmpty(filter.getIds()) && !validateComboFilters(filter)) {
throw new RequiredFilterException();
}
//user does not fill id filter but fills other filters
if (ObjectUtils.isEmpty(filter.getIds()) && validateComboFilters(filter)) {
query.addCriteria(Criteria.where(Constants.CREATED_AT)
.gte(filter.getFromDate())
.lte(filter.getToDate()));
query.addCriteria(Criteria.where(Constants.STATUS).in(filter.getStatus()));
}
//user fills id filter and one or more other filters
if (ObjectUtils.isNotEmpty(filter.getIds())) {
query.addCriteria(Criteria.where(Constants.ID)
.is(filter.getIds()));
if (filter.filterByRangeDate()) {
query.addCriteria(Criteria.where(Constants.CREATED_AT)
.gte(filter.getFromDate())
.lte(filter.getToDate()));
}
if (filter.filterByStatus()) {
query.addCriteria(Criteria.where(Constants.STATUS).in(filter.getStatus()));
}
}
}
It could look like this. I didn't check that for compilation, but I think that general approach is more or less correct.
private static final BiFunction<ProductFilter, Query, Boolean> ADD_CRITERIA_IDS = (filter, query) -> {
boolean res = ObjectUtils.isNotEmpty(filter.getIds());
if (res)
query.addCriteria(Criteria.where(Constants.ID).is(filter.getIds()));
return res;
};
private static final BiFunction<ProductFilter, Query, Boolean> ADD_CRITERIA_STATUS = (filter, query) -> {
boolean res = filter.filterByStatus();
if (res)
query.addCriteria(Criteria.where(Constants.STATUS).in(filter.getStatus()));
return res;
};
private static final BiFunction<ProductFilter, Query, Boolean> ADD_CRITERIA_DATE_RANGE = (filter, query) -> {
boolean res = filter.filterByRangeDate();
if (res)
query.addCriteria(Criteria.where(Constants.CREATED_AT)
.gte(filter.getFromDate())
.lte(filter.getToDate()));
return res;
};
private void addCriteria(ProductFilter filter, Query query) {
boolean added = ADD_CRITERIA_IDS.apply(filter, query);
added |= ADD_CRITERIA_STATUS.apply(filter, query);
added |= ADD_CRITERIA_DATE_RANGE.apply(filter, query);
if (!added)
throw new RequiredFilterException();
}

Merge List of Custom Objects to a Single List Object using Streams Java 8

I have ProductInfo object which looks like this
ProductInfo.java
public class ProductInfo
{
private List<String> servicetagInfo;
}
I have Order object like this which has list of Products info
OrderDetail.java
public class OrderDetail
{
private String orderNum;
private List<ProductInfo> productInfo;
}
And then I have a Response object which basically has List of Order objects
Response.java
public class Response
{
private List<OrderDetail> orderInfo;
}
I am getting response as expected.But right now in this format
orderInfo:
0: {orderNum: "162293591",...}
productInfo:
0: {servicetag_info: ["7LSMW33", "49SMW33"]}
1: {servicetag_info: ["JF6XN33", "CQ5XN33"]}
2: {servicetag_info: ["5VRR523", "13LR523"]}
Here I am trying to merge productInfo List to be like this
productInfo:
0: {servicetag_info: ["7LSMW33", "49SMW33","JF6XN33", "CQ5XN33","5VRR523", "13LR523"]}
Just add all strings into one main property.
Here is my code
List<String> serviceTagList = new ArrayList<>();
for (OrderDetail orderDetail : arInvoiceOrderResponseBody.getOrders()) { //Here i am getting orders from external service
if (orderDetail != null) {
if (orderDetail.getProductInfo() != null && orderDetail.getProductInfo().size() > 0) {
for (ProductInfo productInfoDetail : orderDetail.getProductInfo()) {
if (productInfoDetail != null) {
if (productInfoDetail.getServicetagInfo() != null) {
for (String serviceTag : productInfoDetail.getServicetagInfo()) {
serviceTagList.add(serviceTag);
}
}
}
}
}
}
ProductInfo productInfo = new ProductInfo();
productInfo.setServicetagInfo(serviceTagList);
orderDetail.setProductInfo(Arrays.asList(productInfo));
}
Can anyone suggest how can i achieve same using streams in java so that it will be readable.
Try this:
Set<String> tags = order.stream()
.flatMap(order -> order.getProductInfo().stream())
.map(ProductInfo::getServicetagInfo)
.collect(Collectors.toSet());
Full implementation:
for (OrderDetail orderDetail : arInvoiceOrderResponseBody.getOrders()) {
if (orderDetail != null && orderDetail.getProductInfo() != null) {
orderDetail.getProductInfo().removeAll(null); // Remove any null elems
Set<String> tags = orderDetail.getProductInfo().stream()
.flatMap(product -> (product.getServicetagInfo() == null) ? null : product.getServicetagInfo().stream())
.collect(Collectors.toSet());
tags.remove(null); // Remove null if exists
}
ProductInfo productInfo = new ProductInfo();
productInfo.setServicetagInfo(tags);
orderDetail.setProductInfo(Arrays.asList(productInfo));
}
With streams your code could be like this:
arInvoiceOrderResponseBody.getOrders().stream()
.filter(Objects::nonNull)
.forEach(YourClassName::mergeProductInfo);
The method mergeProductInfo would be:
private static void mergeProductInfo(OrderDetail orderDetail) {
List<String> serviceTagList = new ArrayList<>();
if (orderDetail.getProductInfo() != null) {
serviceTagList = orderDetail.getProductInfo().stream()
.filter(Objects::nonNull)
.map(ProductInfo::getServicetagInfo)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
ProductInfo productInfo = new ProductInfo();
productInfo.setServicetagInfo(serviceTagList);
orderDetail.setProductInfo(Arrays.asList(productInfo));
}
It could be simplified if you could be sure that you are not going to receive null lists or elements.

CriteriaBuilder search in 2 table get performance issue

I try to search a list keyword on 2 different table by CriteriaBuilder and get performance issue on line 26.
Can someone help me to improve this.
public static Specification<TrainingBatchVolunteerDetails> filterVolunteers(final TrainingVolunteerForm form) {
return new Specification<TrainingBatchVolunteerDetails>() {
#Override
public Predicate toPredicate(Root<TrainingBatchVolunteerDetails> root,
CriteriaQuery<?> query, CriteriaBuilder cb) {
....
List<Predicate> predicates = new ArrayList<Predicate>();
Join<TrainingBatchVolunteerDetails, TrainingBatches> batches = root.join("batches");
Join<TrainingBatches, Training> training = batches.join("training");
training.join("trainingVolunteerDetails").join("eventBaseProfile");
Join<TrainingBatches, EventBaseProfile> eventBaseProfile = root.join("eventBaseProfile");
Join<EventBaseProfile, BaseProfile> baseProfile = eventBaseProfile.join("baseProfile");
Join<CtExtendProfile, BaseProfile> ctExtendProfile = null;
if (StringUtils.isNotEmpty(form.getNricName())) {
...
ctExtendProfile = baseProfile.join("ctExtendProfile", JoinType.LEFT);
List<String> keywords = new ArrayList<String>();
...
predicates.add(cb.or(
baseProfile.<String>get("identifierId").in(keywords),
ctExtendProfile.<String>get("birthCertificate").in(keywords)
));
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
};
}

CMIS session.queryObjects doesn't return aspects

I have a couple test functions I've written to illustrate a problem (or at least some behavior that I don't understand). I am just doing some basic CMIS queries on an Alfresco 4.2.e community repository, but am getting some unexpected results depending on whether I use session.query() or session.queryObjects(). Specifically, the queryObjects doesn't return properties for custom aspects. Both return the relationships/associations fine. Am I doing something wrong, or is this a bug? I'm using opencmis 0.10, and the CMIS 1.1 URL.
private static Collection<Document> testCmisObjectQuery(Session session) {
List<Document> rv = new LinkedList<>();
OperationContext opCon = session.createOperationContext();
opCon.setLoadSecondaryTypeProperties(true);
opCon.setIncludeRelationships(IncludeRelationships.BOTH);
ItemIterable<CmisObject> cmisObjs =
session.queryObjects("D:af:insuringFormInstance", null, false, opCon);
for (CmisObject o : cmisObjs) {
Document d = (Document) o;
rv.add(d);
printDocProps(d);
}
return rv;
}
private static Collection<Document> testCmisQuery(Session session) {
List<Document> rv = new LinkedList<>();
String queryString = "SELECT cmis:objectId FROM af:insuringFormInstance";
OperationContext opCon = session.createOperationContext();
opCon.setIncludeRelationships(IncludeRelationships.SOURCE);
ItemIterable<QueryResult> results = session.query(queryString, false);
for (QueryResult qResult : results) {
String objectId = qResult.getPropertyValueByQueryName("cmis:objectId");
Document doc = (Document) session.getObject(session.createObjectId(objectId),opCon);
printDocProps(doc);
rv.add(doc);
}
return rv;
}
Looks like, you are missing a join as in
select d.*, o.* from cmis:document as d join cm:ownable as o on d.cmis:objectId = o.cmis:objectId
Have a look at https://wiki.alfresco.com/wiki/CMIS#Aspect_Query for further details.

Categories