diff --git a/src/main/java/org/brapi/test/BrAPITestServer/controller/germ/GermplasmApiController.java b/src/main/java/org/brapi/test/BrAPITestServer/controller/germ/GermplasmApiController.java index 9f1ed1ae..407dc9b6 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/controller/germ/GermplasmApiController.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/controller/germ/GermplasmApiController.java @@ -199,8 +199,9 @@ public ResponseEntity germplasmPost(@RequestBody List data = germplasmService.saveGermplasm(body); - pedigreeService.updateGermplasmPedigree(data); + pedigreeService.updateGermplasmPedigreeForPost(data); return responseOK(new GermplasmListResponse(), new GermplasmListResponseResult(), data); } diff --git a/src/main/java/org/brapi/test/BrAPITestServer/repository/BrAPIRepository.java b/src/main/java/org/brapi/test/BrAPITestServer/repository/BrAPIRepository.java index e038360c..3a796a07 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/repository/BrAPIRepository.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/repository/BrAPIRepository.java @@ -10,6 +10,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.NoRepositoryBean; @NoRepositoryBean @@ -30,4 +31,6 @@ public interface BrAPIRepository void refresh(S entity); public void fetchXrefs(Page page, Class searchClass) throws InvalidPagingException; + + List findByIdIn(List ids); } \ No newline at end of file diff --git a/src/main/java/org/brapi/test/BrAPITestServer/repository/BrAPIRepositoryImpl.java b/src/main/java/org/brapi/test/BrAPITestServer/repository/BrAPIRepositoryImpl.java index 9bfa577c..3de292d0 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/repository/BrAPIRepositoryImpl.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/repository/BrAPIRepositoryImpl.java @@ -119,6 +119,10 @@ public Optional findById(ID id) { return response; } + public List findByIdIn(List ids) { + return super.findAllById(ids); + } + public S save(S entity) { entity.setAuthUserId(SecurityUtils.getCurrentUserId()); return super.save(entity); diff --git a/src/main/java/org/brapi/test/BrAPITestServer/repository/core/CropRepository.java b/src/main/java/org/brapi/test/BrAPITestServer/repository/core/CropRepository.java index c009b911..9808025f 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/repository/core/CropRepository.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/repository/core/CropRepository.java @@ -5,6 +5,10 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import java.util.List; + public interface CropRepository extends BrAPIRepository{ public Page findByCropName(String cropName, Pageable pageRequest); + + public List findByCropNameIn(List names); } diff --git a/src/main/java/org/brapi/test/BrAPITestServer/repository/germ/BreedingMethodRepository.java b/src/main/java/org/brapi/test/BrAPITestServer/repository/germ/BreedingMethodRepository.java index a77d30aa..e090aeb6 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/repository/germ/BreedingMethodRepository.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/repository/germ/BreedingMethodRepository.java @@ -3,6 +3,5 @@ import org.brapi.test.BrAPITestServer.model.entity.germ.BreedingMethodEntity; import org.brapi.test.BrAPITestServer.repository.BrAPIRepository; -public interface BreedingMethodRepository extends BrAPIRepository{ - +public interface BreedingMethodRepository extends BrAPIRepository { } diff --git a/src/main/java/org/brapi/test/BrAPITestServer/repository/germ/CrossingProjectRepository.java b/src/main/java/org/brapi/test/BrAPITestServer/repository/germ/CrossingProjectRepository.java index 998ee194..0ceb9d8f 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/repository/germ/CrossingProjectRepository.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/repository/germ/CrossingProjectRepository.java @@ -4,5 +4,4 @@ import org.brapi.test.BrAPITestServer.repository.BrAPIRepository; public interface CrossingProjectRepository extends BrAPIRepository { - } diff --git a/src/main/java/org/brapi/test/BrAPITestServer/repository/germ/GermplasmRepository.java b/src/main/java/org/brapi/test/BrAPITestServer/repository/germ/GermplasmRepository.java index 7c5d9df6..62901285 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/repository/germ/GermplasmRepository.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/repository/germ/GermplasmRepository.java @@ -19,4 +19,6 @@ public interface GermplasmRepository extends BrAPIRepository germplasmIds, @Param("softDeleted") boolean softDeleted); + + List findByGermplasmNameIn(List germplasmNames); } diff --git a/src/main/java/org/brapi/test/BrAPITestServer/repository/germ/PedigreeRepository.java b/src/main/java/org/brapi/test/BrAPITestServer/repository/germ/PedigreeRepository.java index 911707d4..2c11da57 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/repository/germ/PedigreeRepository.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/repository/germ/PedigreeRepository.java @@ -7,4 +7,6 @@ public interface PedigreeRepository extends BrAPIRepository, PedigreeRepositoryCustom { public List findByGermplasm_Id(String germplasmDbId); + + public List findByGermplasm_IdIn(List germplasmDbIds); } diff --git a/src/main/java/org/brapi/test/BrAPITestServer/service/UpdateUtility.java b/src/main/java/org/brapi/test/BrAPITestServer/service/UpdateUtility.java index f198c2ab..7052732d 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/service/UpdateUtility.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/service/UpdateUtility.java @@ -1,9 +1,13 @@ package org.brapi.test.BrAPITestServer.service; import io.swagger.model.BrAPIDataModel; +import io.swagger.model.ExternalReferences; +import io.swagger.model.ExternalReferencesInner; import org.brapi.test.BrAPITestServer.model.entity.BrAPIPrimaryEntity; +import org.brapi.test.BrAPITestServer.model.entity.ExternalReferenceEntity; -import java.util.Optional; +import java.util.*; +import java.util.stream.Collectors; public class UpdateUtility { @@ -34,12 +38,60 @@ public static T convertFromEntity(BrAPIPrimaryEntity } public static T updateEntity(BrAPIDataModel model, T entity) { + updateAdditionalInfo(model, entity); + if (model.getExternalReferences() != null) { + entity.setExternalReferences(model.getExternalReferences()); + } + return entity; + } + + private static void updateAdditionalInfo(BrAPIDataModel model, T entity) { if (model.getAdditionalInfo() != null) { entity.setAdditionalInfo(model.getAdditionalInfo()); } + } + + /* + Call this method when external references are eagerly loaded for bulk updates to entities to ensure + unnecessary deletions and insertions don't occur. This will improve performance in these use cases. + + WARN: If refs aren't eagerly loaded, hibernate will generate a query on the entity.getReferences() call. This could slow performance. + + This method will check if the external references in the model already exist in the entity, and will prevent setting + the entity with the model's refs. + + TODO: See if migrating all callers of updateEntity to this method can be done. + */ + public static T updateEntityCheckExRefs(BrAPIDataModel model, T entity) { + updateAdditionalInfo(model, entity); if (model.getExternalReferences() != null) { - entity.setExternalReferences(model.getExternalReferences()); + + ExternalReferences exRefs = model.getExternalReferences(); + + Map> existingRefsById = + entity.getExternalReferences() != null ? + entity.getExternalReferences().stream().collect(Collectors.groupingBy(ExternalReferenceEntity::getExternalReferenceId)) + : Collections.emptyMap(); + + + List newExRefs = exRefs.stream().filter(exRef -> { + List existingEntityRefList = existingRefsById.get(exRef.getReferenceID()); + + if (existingEntityRefList == null || existingEntityRefList.isEmpty()) { + return true; + } + + ExternalReferenceEntity existingEntityRef = existingEntityRefList.get(0); + + return !existingEntityRef.getExternalReferenceSource().equals(exRef.getReferenceSource()); + }).collect(Collectors.toList()); + + if (!newExRefs.isEmpty()) { + // Detected different ex refs than what is in the original entity. Updating entity exRefs. + entity.setExternalReferences(model.getExternalReferences()); + } + // If there are no new exRefs no update is made. } return entity; } -} +} \ No newline at end of file diff --git a/src/main/java/org/brapi/test/BrAPITestServer/service/core/CropService.java b/src/main/java/org/brapi/test/BrAPITestServer/service/core/CropService.java index a3d2d450..3932e698 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/service/core/CropService.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/service/core/CropService.java @@ -63,12 +63,16 @@ public CropEntity getCropEntity(String commonCropName) throws BrAPIServerExcepti return entity; } + public List findCropsByNames(List names) { + return cropRepository.findByCropNameIn(names); + } + public CropEntity saveCropEntity(String commonCropName) throws BrAPIServerException { CropEntity entity = null; if (commonCropName != null) { entity = new CropEntity(); entity.setCropName(commonCropName); - entity = cropRepository.saveAndFlush(entity); + entity = cropRepository.save(entity); } return entity; } diff --git a/src/main/java/org/brapi/test/BrAPITestServer/service/germ/BreedingMethodService.java b/src/main/java/org/brapi/test/BrAPITestServer/service/germ/BreedingMethodService.java index fbfb64a5..a38a8d3c 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/service/germ/BreedingMethodService.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/service/germ/BreedingMethodService.java @@ -1,7 +1,9 @@ package org.brapi.test.BrAPITestServer.service.germ; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerDbIdNotFoundException; import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerException; @@ -52,6 +54,10 @@ public BreedingMethodEntity getBreedingMethodEntity(String breedingMethodDbId, H return breedingMethodEntity; } + public List findBreedingMethodsByIds(List ids) { + return breedingMethodRepository.findByIdIn(ids); + } + private BreedingMethod convertFromEntity(BreedingMethodEntity entity) { BreedingMethod method = new BreedingMethod(); method.setAbbreviation(entity.getAbbreviation()); diff --git a/src/main/java/org/brapi/test/BrAPITestServer/service/germ/CrossingProjectService.java b/src/main/java/org/brapi/test/BrAPITestServer/service/germ/CrossingProjectService.java index d8edb9ec..fa2f9b1b 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/service/germ/CrossingProjectService.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/service/germ/CrossingProjectService.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Optional; +import io.swagger.model.IndexPagination; import jakarta.validation.Valid; import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerDbIdNotFoundException; @@ -69,6 +70,10 @@ public List findCrossingProjects(String crossingProjectDbId, St return crossingProjects; } + public List findCrossingProjectsByIds(List crossingProjectIds) { + return crossingProjectRepository.findByIdIn(crossingProjectIds); + } + public CrossingProject getCrossingProject(String crossingProjectDbId) throws BrAPIServerException { return convertFromEntity(getCrossingProjectEntity(crossingProjectDbId, HttpStatus.NOT_FOUND)); } diff --git a/src/main/java/org/brapi/test/BrAPITestServer/service/germ/GermplasmService.java b/src/main/java/org/brapi/test/BrAPITestServer/service/germ/GermplasmService.java index 7513d84c..db46885a 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/service/germ/GermplasmService.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/service/germ/GermplasmService.java @@ -436,7 +436,7 @@ private void fetchRemainingGermCollectionsUsingQuery(SearchQueryBuilder pedigree = germplasmRepository.findAllBySearch(searchQuery); - Map pedigreeByGerm = new HashMap<>(); + Map pedigreeByGerm = new HashMap<>(); pedigree.forEach(germ -> pedigreeByGerm.put(germ.getId().toString(), germ.getPedigree())); germEntities.forEach(germ -> { @@ -473,7 +473,7 @@ public Germplasm updateGermplasm(String germplasmDbId, GermplasmNewRequest body) throws BrAPIServerException { GermplasmEntity entity = getGermplasmEntity(germplasmDbId, HttpStatus.NOT_FOUND); updateEntity(entity, body); - GermplasmEntity savedEntity = germplasmRepository.saveAndFlush(entity); + GermplasmEntity savedEntity = germplasmRepository.save(entity); return convertFromEntity(savedEntity); } @@ -502,14 +502,8 @@ public void softDeleteGermplasm(String germplasmDbId) throws BrAPIServerExceptio } public List saveGermplasm(@Valid List body) throws BrAPIServerException { - List toSave = new ArrayList<>(); - for (GermplasmNewRequest germplasm : body) { - GermplasmEntity entity = new GermplasmEntity(); - updateEntity(entity, germplasm); - toSave.add(entity); - } // Save batch. - return germplasmRepository.saveAllAndFlush(toSave) + return germplasmRepository.saveAll(createEntitiesInBatch(body)) .stream() .map(this::convertFromEntity) .collect(Collectors.toList()); @@ -572,6 +566,106 @@ private Germplasm convertFromEntity(GermplasmEntity entity) { return germ; } + private List createEntitiesInBatch(List body) + throws BrAPIServerException { + List toSave = new ArrayList<>(); + + List breedingMethodIds = body.stream() + .map(GermplasmNewRequest::getBreedingMethodDbId) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + // Use a set since all crop names are likely all or mostly the same. + Set cropNames = body.stream() + .map(GermplasmNewRequest::getCommonCropName) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + Map foundBreedingMethodsById + = breedingMethodService.findBreedingMethodsByIds(breedingMethodIds) + .stream() + .collect(Collectors.toMap(BrAPIBaseEntity::getId, e -> e)); + Map foundCropsByCropName + = cropService.findCropsByNames(new ArrayList<>(cropNames)) + .stream() + .collect(Collectors.toMap(CropEntity::getCropName, e -> e)); + + for (GermplasmNewRequest request : body) { + GermplasmEntity entity = new GermplasmEntity(); + + UpdateUtility.updateEntity(request, entity); + + if (request.getAccessionNumber() != null) + entity.setAccessionNumber(request.getAccessionNumber()); + if (request.getAcquisitionDate() != null) { + entity.setAcquisitionDate(DateUtility.toDate(request.getAcquisitionDate())); + entity.setAcquisitionSourceCode(AcquisitionSourceCodeEnum._99); + } + if (request.getBiologicalStatusOfAccessionCode() != null) + entity.setBiologicalStatusOfAccessionCode(request.getBiologicalStatusOfAccessionCode()); + if (request.getBreedingMethodDbId() != null) { + entity.setBreedingMethod(foundBreedingMethodsById.get(request.getBreedingMethodDbId())); + } + if (request.getCollection() != null) + entity.setCollection(request.getCollection()); + if (request.getCommonCropName() != null) { + CropEntity crop = foundCropsByCropName.get(request.getCommonCropName()); + if (crop == null) { + crop = cropService.saveCropEntity(request.getCommonCropName()); + } + entity.setCrop(crop); + } + if (request.getCountryOfOriginCode() != null) + entity.setCountryOfOriginCode(request.getCountryOfOriginCode()); + if (request.getDefaultDisplayName() != null) + entity.setDefaultDisplayName(request.getDefaultDisplayName()); + if (request.getDocumentationURL() != null) + entity.setDocumentationURL(request.getDocumentationURL()); + if (request.getDonors() != null) + updateDonorEntities(request.getDonors(), entity); + if (request.getGenus() != null) + entity.setGenus(request.getGenus()); + if (request.getGermplasmName() != null) + entity.setGermplasmName(request.getGermplasmName()); + if (request.getGermplasmOrigin() != null) + updateOriginEntities(request.getGermplasmOrigin(), entity); + if (request.getGermplasmPreprocessing() != null) + entity.setGermplasmPreprocessing(request.getGermplasmPreprocessing()); + if (request.getGermplasmPUI() != null) + entity.setGermplasmPUI(request.getGermplasmPUI()); + if (request.getInstituteCode() != null || request.getInstituteName() != null) + entity.setHostInstitute(request.getInstituteCode(), request.getInstituteName()); + entity.setMlsStatus(MlsStatusEnum.EMPTY); + if (request.getPedigree() != null) { + if(entity.getPedigree() == null) { + entity.setPedigree(new PedigreeNodeEntity()); + } + entity.getPedigree().setPedigreeString(request.getPedigree()); + } + if (request.getSeedSource() != null) + entity.setSeedSource(request.getSeedSource()); + if (request.getSeedSourceDescription() != null) + entity.setSeedSourceDescription(request.getSeedSourceDescription()); + if (request.getSpecies() != null) + entity.setSpecies(request.getSpecies()); + if (request.getSpeciesAuthority() != null) + entity.setSpeciesAuthority(request.getSpeciesAuthority()); + if (request.getStorageTypes() != null) + entity.setTypeOfGermplasmStorageCode(request.getStorageTypes().stream().filter(Objects::nonNull) + .map(GermplasmStorageTypes::getCode).collect(Collectors.toList())); + if (request.getSubtaxa() != null) + entity.setSubtaxa(request.getSubtaxa()); + if (request.getSubtaxaAuthority() != null) + entity.setSubtaxaAuthority(request.getSubtaxaAuthority()); + if (request.getSynonyms() != null) + updateSynonymEntities(request.getSynonyms(), entity); + if (request.getTaxonIds() != null) + updateTaxonEntities(request.getTaxonIds(), entity); + + toSave.add(entity); + } + return toSave; + } + private void updateEntity(GermplasmEntity entity, GermplasmNewRequest request) throws BrAPIServerException { UpdateUtility.updateEntity(request, entity); @@ -677,14 +771,54 @@ private void updateSynonymEntities(List synonyms, G } } + public List findByNames(List germplasmNames) { + List foundGerms = new ArrayList<>(); + + if (!germplasmNames.isEmpty()) { + foundGerms = germplasmRepository.findByGermplasmNameIn(germplasmNames); + } + + return foundGerms; + } + + public List findByIds(List germplasmDbIds) + throws BrAPIServerException { + List germsFoundInDb = germplasmRepository.findByIdIn(germplasmDbIds); + + Set germIdsFoundInDB = germsFoundInDb.stream() + .map(BrAPIBaseEntity::getId) + .collect(Collectors.toSet()); + + if (!germIdsFoundInDB.containsAll(germplasmDbIds)) { + throw new BrAPIServerDbIdNotFoundException("Germplasm Ids passed to findByIds were not found in the DB", HttpStatus.BAD_REQUEST); + } + + return germsFoundInDb; + } + + // TODO: Add lookupType param to RQ Germplasm which can short-circuit all the lookup logic to only one query here. public GermplasmEntity findByUnknownIdentity(String germplasmStr) throws BrAPIServerException { + + // First, check to see if the str provided is a real UUID. + boolean tryDBIdLookup = true; + try { + UUID germUUID = UUID.fromString(germplasmStr); + } catch (IllegalArgumentException e) { + tryDBIdLookup = false; + } + List germplasmList = Arrays.asList(germplasmStr); Metadata metadata = new Metadata().pagination(new IndexPagination()); // germplasmDbId GermplasmSearchRequest request = new GermplasmSearchRequest().germplasmDbIds(germplasmList); - Page page = findGermplasmEntities(request, metadata); + Page page = Page.empty(); + + if (tryDBIdLookup) { + page = findGermplasmEntities(request, metadata); + } + if (page.hasContent()) { return page.getContent().get(0); } diff --git a/src/main/java/org/brapi/test/BrAPITestServer/service/germ/PedigreeService.java b/src/main/java/org/brapi/test/BrAPITestServer/service/germ/PedigreeService.java index 0921c89d..613ba5dd 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/service/germ/PedigreeService.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/service/germ/PedigreeService.java @@ -1,19 +1,15 @@ package org.brapi.test.BrAPITestServer.service.germ; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; import java.util.stream.Collectors; -import java.util.Optional; -import java.util.Set; import io.swagger.model.IndexPagination; +import io.swagger.model.Pagination; +import org.apache.commons.lang3.tuple.Pair; import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerDbIdNotFoundException; import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerException; +import org.brapi.test.BrAPITestServer.model.entity.BrAPIBaseEntity; import org.brapi.test.BrAPITestServer.model.entity.germ.CrossingProjectEntity; import org.brapi.test.BrAPITestServer.model.entity.germ.GermplasmEntity; import org.brapi.test.BrAPITestServer.model.entity.germ.PedigreeEdgeEntity; @@ -176,22 +172,73 @@ public Optional getPedigreeNode(String germplasmDbId) { return node; } - public PedigreeNodeEntity findOrCreatePedigreeNode(String germplasmDbId) throws BrAPIServerException { - Optional nodeOpt = getPedigreeNode(germplasmDbId); - PedigreeNodeEntity node; - if (nodeOpt.isPresent()) { - node = nodeOpt.get(); - } else { - GermplasmEntity germplasm = germplasmService.getGermplasmEntity(germplasmDbId); + public List getPedigreeNodes(List germplasmDbIds) { + List nodes = new ArrayList<>(); + + // TODO: Might have to make a custom query for this that fetches the germ eagerly, bc need to compare the germIds. Have to see if this is a significant performance hit. + List dbNodeList = pedigreeRepository.findByGermplasm_IdIn(germplasmDbIds); + + Map> nodesGroupedByGerm = dbNodeList.stream().collect(Collectors.groupingBy(pn -> pn.getGermplasm().getId())); + + nodesGroupedByGerm.forEach((germId, nodesByGerm) -> { + if (nodesByGerm.size() > 1) { + log.error("multiple pedigree nodes found for a single germplasm"); + } + + Optional node = nodesByGerm.stream().findFirst(); + + node.ifPresent(nodes::add); + }); + + return nodes; + } + + public List findOrCreatePedigreeNodesFromGermplasmIds(List germplasmDbIds) throws BrAPIServerException { + + // First, grab nodes from the DB that match the germplasmDbIds passed through. + List resultingNodes = getPedigreeNodes(germplasmDbIds); + + // Next, find out which germIds were not found in the DB. Use a set for improved performance on the contains check. + // TODO: Check if the germEntity is already populated by getPedigreeNodes, and if this block results in more DB transactions. + Set germIdsOfFoundNodes = resultingNodes.stream() + .map(PedigreeNodeEntity::getGermplasm) + .map(BrAPIBaseEntity::getId) + .collect(Collectors.toSet()); + + List germIdsWithNoPedigreeRecord = germplasmDbIds.stream() + .filter(dbId -> !germIdsOfFoundNodes.contains(dbId)) + .collect(Collectors.toList()); + + // Now see if there are germplasm records that exist from the list created above that have a pedigree associated. + // If they do have a pedigree associated, add it to the result list. + // If not, create a Pedigree with that Germplasm record. + // TODO: Might need this to fetch the pedigree records on this query, otherwise a lazy load occurs + List germplasms = new ArrayList<>(); + + if (!germIdsWithNoPedigreeRecord.isEmpty()) { + germplasms = germplasmService.findByIds(germIdsWithNoPedigreeRecord); + } + + List nodesToCreate = new ArrayList<>(); + + for (GermplasmEntity germplasm : germplasms) { + // This is a lazy load if (germplasm.getPedigree() != null) { - node = germplasm.getPedigree(); + resultingNodes.add(germplasm.getPedigree()); } else { + // No pedigree exists for this germplasm. Create one and add the germplasm to it. PedigreeNodeEntity newNode = new PedigreeNodeEntity(); newNode.setGermplasm(germplasm); - node = pedigreeRepository.saveAndFlush(newNode); + nodesToCreate.add(newNode); } } - return node; + + // If any new nodes were made, save them and add them to the result list. + if (!nodesToCreate.isEmpty()) { + resultingNodes.addAll(pedigreeRepository.saveAll(nodesToCreate)); + } + + return resultingNodes; } public PedigreeNode getGermplasmPedigree(String germplasmDbId, Boolean includeSiblings) @@ -237,54 +284,94 @@ public ProgenyNode getGermplasmProgeny(String germplasmDbId) throws BrAPIServerE return result; } - public List savePedigreeNodes(List request) throws BrAPIServerException { + public List savePedigreeNodes(List request) + throws BrAPIServerException { + return savePedigreeNodes(request, true); + } + + public List savePedigreeNodes(List request, + boolean returnValues) throws BrAPIServerException { Map nodesByGermplasm = getExistingPedigreeNodes( - request.stream().map(p -> p.getGermplasmDbId()).collect(Collectors.toList())); + request.stream().map(PedigreeNode::getGermplasmDbId).collect(Collectors.toList())); if (!nodesByGermplasm.isEmpty()) { String errorMsg = "The following germplasmDbIds already have existing pedigree data. Please use PUT /pedigree to update these germplasm. \n" - + nodesByGermplasm.keySet().toString(); + + nodesByGermplasm.keySet(); throw new BrAPIServerException(HttpStatus.BAD_REQUEST, errorMsg); } - List newEntities = new ArrayList<>(); - - for (PedigreeNode node : request) { - PedigreeNodeEntity entity = new PedigreeNodeEntity(); - updateEntity(entity, node); - newEntities.add(entity); - } + List newEntities = createEntitiesInBatch(request); // save all the new nodes without edges - pedigreeRepository.saveAllAndFlush(newEntities); + pedigreeRepository.saveAll(newEntities); Map updateRequest = new HashMap<>(); for (PedigreeNode newNode : request) { updateRequest.put(newNode.getGermplasmDbId(), newNode); } // update the new nodes with requested edges - List saved = updatePedigreeNodes(updateRequest); + if (returnValues) { + return updatePedigreeNodes(updateRequest); + } else { + updatePedigreeNodes(updateRequest, false); + return Collections.emptyList(); + } + } - return saved; + public List updatePedigreeNodes(Map request) + throws BrAPIServerException { + return updatePedigreeNodes(request, true); } - public List updatePedigreeNodes(Map request) throws BrAPIServerException { + public List updatePedigreeNodes(Map request, + boolean returnValues) throws BrAPIServerException { Map nodesByGermplasm = getExistingPedigreeNodes(new ArrayList<>(request.keySet())); - List newEntities = new ArrayList<>(); + + Map> entityDtoPairsByGermplasmId = new HashMap<>(); for (Entry entry : request.entrySet()) { - PedigreeNodeEntity entity = nodesByGermplasm.get(entry.getKey()); + String germId = entry.getKey(); + PedigreeNodeEntity entity = nodesByGermplasm.get(germId); + if (entity != null) { - updateEntityWithEdges(entity, entry.getValue()); - newEntities.add(entity); + entityDtoPairsByGermplasmId.put(germId, Pair.of(entity, entry.getValue())); } else { throw new BrAPIServerDbIdNotFoundException("germplasm", entry.getKey(), HttpStatus.BAD_REQUEST); } } - List savedEntities = pedigreeRepository.saveAllAndFlush(newEntities); - List saved = convertFromEntities(savedEntities, - new PedigreeSearchRequest().includeParents(true).includeProgeny(true).includeSiblings(true)); - return saved; + updateEntitiesWithEdgesInBatch(entityDtoPairsByGermplasmId); + + if (returnValues) { + List savedEntities = pedigreeRepository.saveAll(entityDtoPairsByGermplasmId.values().stream().map(Pair::getLeft).collect(Collectors.toList())); + List saved = convertFromEntities(savedEntities, + new PedigreeSearchRequest().includeParents(true).includeProgeny(true).includeSiblings(true)); + return saved; + } else { + // If no values are required, simply return an empty list. + pedigreeRepository.saveAll(entityDtoPairsByGermplasmId.values().stream().map(Pair::getLeft).collect(Collectors.toList())); + return Collections.emptyList(); + } + } + + // TODO: See if we can get the PUT from the GermplasmApiController to use this code as well instead of updateGermplasmPedigree() + public void updateGermplasmPedigreeForPost(List data) throws BrAPIServerException { + // T = With pedigree, F = Without pedigree + Map> germsWithAndWithoutPedigree + = data.stream().collect(Collectors.partitioningBy(g -> g.getPedigree() != null)); + + if (!germsWithAndWithoutPedigree.get(true).isEmpty()) { + savePedigreeNodes(convertFromGermplasmToPedigreeBatchUsingNames(germsWithAndWithoutPedigree.get(true)), false); + } + + if (!germsWithAndWithoutPedigree.get(false).isEmpty()) { + List createPedigreeNodes = new ArrayList<>(); + + for (Germplasm germplasm : germsWithAndWithoutPedigree.get(false)) { + createPedigreeNodes.add(convertFromGermplasmToPedigree(germplasm)); + } + + savePedigreeNodes(createPedigreeNodes, false); + } } public void updateGermplasmPedigree(List data) throws BrAPIServerException { @@ -320,7 +407,12 @@ private Map getExistingPedigreeNodes(List ge searchReq.setPedigreeDepth(0); searchReq.setProgenyDepth(0); - List nodeEntities = findPedigreeEntities(searchReq, null); + Pagination pagination = new IndexPagination(); + pagination.setPageSize(germplasmDbIds.size()); + Metadata metadata = new Metadata(); + metadata.setPagination(pagination); + + List nodeEntities = findPedigreeEntities(searchReq, metadata); for (PedigreeNodeEntity nodeEntity : nodeEntities) { if (nodeEntity.getGermplasm() != null && nodeEntity.getGermplasm().getId() != null) { @@ -490,74 +582,309 @@ private void updateEntity(PedigreeNodeEntity entity, PedigreeNode node) throws B } } - private void updateEntityWithEdges(PedigreeNodeEntity entity, PedigreeNode node) throws BrAPIServerException { - UpdateUtility.updateEntity(node, entity); - updateEntity(entity, node); - if (node.getParents() != null) { - - SearchQueryBuilder search = new SearchQueryBuilder(PedigreeEdgeEntity.class); - search.appendSingle(node.getGermplasmDbId(), "connectedNode.germplasm.id"); - search.appendEnum(PedigreeEdgeEntity.EdgeType.child, "edgeType"); - Pageable defaultPageSize = PagingUtility.getPageRequest(new Metadata().pagination(new IndexPagination().pageSize(10000000))); - Page existingParentEdges = pedigreeEdgeRepository.findAllBySearchAndPaginate(search, defaultPageSize); - - List edgeIdsToDelete = new ArrayList<>(); - edgeIdsToDelete.addAll(entity.getParentEdges().stream().map(e -> e.getId()).collect(Collectors.toList())); - edgeIdsToDelete.addAll(existingParentEdges.getContent().stream().map(e -> e.getId()).collect(Collectors.toList())); - - if (!edgeIdsToDelete.isEmpty()) { - pedigreeEdgeRepository.deleteAllByIdInBatch(edgeIdsToDelete); - pedigreeEdgeRepository.flush(); + + + // This method should be used in use cases where there are no existing node entities representing the list being passed through. + private List createEntitiesInBatch(List nodes) + throws BrAPIServerException { + List germIds = nodes.stream() + .map(PedigreeNode::getGermplasmDbId) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + List crossingProjIds = nodes.stream() + .map(PedigreeNode::getCrossingProjectDbId) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + Map foundGermsById = germplasmService.findByIds(germIds) + .stream() + .collect(Collectors.toMap(BrAPIBaseEntity::getId, e -> e)); + Map foundCrossingProjsById = crossingProjectService.findCrossingProjectsByIds(crossingProjIds) + .stream() + .collect(Collectors.toMap(BrAPIBaseEntity::getId, e -> e)); + + List result = new ArrayList<>(); + for (PedigreeNode node : nodes) { + PedigreeNodeEntity entity = new PedigreeNodeEntity(); + + if (node.getGermplasmDbId() != null) { + entity.setGermplasm(foundGermsById.get(node.getGermplasmDbId())); + } + + UpdateUtility.updateEntity(node, entity); + + if (node.getCrossingYear() != null) + entity.setCrossingYear(node.getCrossingYear()); + if (node.getFamilyCode() != null) + entity.setFamilyCode(node.getFamilyCode()); + if (node.getPedigreeString() != null) + entity.setPedigreeString(node.getPedigreeString()); + + if (node.getCrossingProjectDbId() != null) { + entity.setCrossingProject(foundCrossingProjsById.get(node.getCrossingProjectDbId())); + } + + result.add(entity); + } + + return result; + } + + // This method should be used in use cases where there are existing node entities that may have edges. + private void updateEntitiesWithEdgesInBatch(Map> entityDtoPairsByGermId) + throws BrAPIServerException { + List germIds = new ArrayList<>(entityDtoPairsByGermId.keySet()); + List crossingProjIds = entityDtoPairsByGermId.values() + .stream() + .map(nodePair -> nodePair.getRight().getCrossingProjectDbId()) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + List germIdsWithParentNodes = entityDtoPairsByGermId.entrySet() + .stream() + .filter(entry -> entry.getValue().getRight().getParents() != null) + .map(Entry::getKey) + .collect(Collectors.toList()); + + List germIdsWithProgenyNodes = entityDtoPairsByGermId.entrySet() + .stream() + .filter(entry -> entry.getValue().getRight().getProgeny() != null) + .map(Entry::getKey) + .collect(Collectors.toList()); + + new HashMap<>(); + + Map foundGermsById = germplasmService.findByIds(germIds) + .stream() + .collect(Collectors.toMap(BrAPIBaseEntity::getId, e -> e)); + + Map foundCrossingProjsById = crossingProjectService.findCrossingProjectsByIds(crossingProjIds) + .stream() + .collect(Collectors.toMap(BrAPIBaseEntity::getId, e -> e)); + + if (!germIdsWithParentNodes.isEmpty()) { + updateParentEdges(germIdsWithParentNodes, entityDtoPairsByGermId); + } + + if (!germIdsWithProgenyNodes.isEmpty()) { + updateChildEdges(germIdsWithProgenyNodes, entityDtoPairsByGermId); + } + + for (Entry> entityDtoPairByGermId: entityDtoPairsByGermId.entrySet()) { + PedigreeNodeEntity entity = entityDtoPairByGermId.getValue().getLeft(); + PedigreeNode node = entityDtoPairByGermId.getValue().getRight(); + + if (node.getGermplasmDbId() != null && entity.getGermplasm() == null) { + entity.setGermplasm(foundGermsById.get(node.getGermplasmDbId())); + } + + UpdateUtility.updateEntityCheckExRefs(node, entity); + + if (node.getCrossingYear() != null) + entity.setCrossingYear(node.getCrossingYear()); + if (node.getFamilyCode() != null) + entity.setFamilyCode(node.getFamilyCode()); + if (node.getPedigreeString() != null) + entity.setPedigreeString(node.getPedigreeString()); + + if (node.getCrossingProjectDbId() != null) { + entity.setCrossingProject(foundCrossingProjsById.get(node.getCrossingProjectDbId())); + } + } + } + + private void updateParentEdges(List germIdsWithParentNodes, + Map> entityDtoPairsByGermId) + throws BrAPIServerException { + + List parentEdgesToDelete = new ArrayList<>(); + + SearchQueryBuilder search = new SearchQueryBuilder<>(PedigreeEdgeEntity.class); + search.appendList(germIdsWithParentNodes, "connectedNode.germplasm.id"); + search.appendEnum(PedigreeEdgeEntity.EdgeType.child, "edgeType"); + List existingParentEdges = pedigreeEdgeRepository.findAllBySearch(search); + + List existingParentEdgesFromPassedEntities = entityDtoPairsByGermId.entrySet() + .stream() + .flatMap(entry -> entry.getValue().getLeft().getParentEdges().stream()) + .map(BrAPIBaseEntity::getId) + .collect(Collectors.toList()); + + parentEdgesToDelete.addAll(existingParentEdgesFromPassedEntities); + parentEdgesToDelete.addAll(existingParentEdges.stream().map(BrAPIBaseEntity::getId).collect(Collectors.toList())); + + if (!parentEdgesToDelete.isEmpty()) { + pedigreeEdgeRepository.deleteAllByIdInBatch(parentEdgesToDelete); + } + + List> nodesWithParents = entityDtoPairsByGermId.values() + .stream() + .filter(p -> !p.getRight().getParents().isEmpty()) + .collect(Collectors.toList()); + + List germIdsOfAllParents = nodesWithParents.stream() + .flatMap(p -> p.getRight().getParents().stream()) + .map(PedigreeNodeParents::getGermplasmDbId) + .collect(Collectors.toList()); + + + List createdOrFoundParentNodes = findOrCreatePedigreeNodesFromGermplasmIds(germIdsOfAllParents); + + for (Pair nodeWithParent : nodesWithParents) { + PedigreeNodeEntity nodeEntity = nodeWithParent.getLeft(); + PedigreeNode nodeDto = nodeWithParent.getRight(); + for (PedigreeNodeParents parentNode : nodeDto.getParents()) { + // Is it possible that more that any of these parents share the same germplasm ID? If so, we need to figure out how to handle that use case. + PedigreeNodeEntity parentEntity = createdOrFoundParentNodes.stream() + .filter(pne -> pne.getGermplasm().getId().equals(parentNode.getGermplasmDbId())) + .findFirst() + .orElse(null); + + // Impossible to be null because of exception thrown in findOrCreatePedigreeNodesFromGermplasmIds(). + // As long as the germIds of all the parents nodes in the rq were supplied, they will be found or created or this code is never executed. + if (parentEntity != null) { + nodeEntity.addParent(parentEntity, parentNode.getParentType()); + parentEntity.addProgeny(nodeEntity, parentNode.getParentType()); + } + } + } + } + + private void updateChildEdges(List germIdsWithProgenyNodes, + Map> entityDtoPairsByGermId) + throws BrAPIServerException { + List progenyEdgesToDelete = new ArrayList<>(); + + SearchQueryBuilder search = new SearchQueryBuilder(PedigreeEdgeEntity.class); + + search.appendList(germIdsWithProgenyNodes, "connectedNode.germplasm.id"); + search.appendEnum(PedigreeEdgeEntity.EdgeType.parent, "edgeType"); + List existingProgenyEdges = pedigreeEdgeRepository.findAllBySearch(search); + + List existingProgenyEdgeFromPassedEntities = entityDtoPairsByGermId.entrySet() + .stream() + .flatMap(entry -> entry.getValue().getLeft().getProgenyEdges().stream()) + .map(BrAPIBaseEntity::getId) + .collect(Collectors.toList()); + + progenyEdgesToDelete.addAll(existingProgenyEdgeFromPassedEntities); + progenyEdgesToDelete.addAll(existingProgenyEdges.stream().map(BrAPIBaseEntity::getId).collect(Collectors.toList())); + + if (!progenyEdgesToDelete.isEmpty()) { + pedigreeEdgeRepository.deleteAllByIdInBatch(progenyEdgesToDelete); + } + + List> nodesWithProgeny = entityDtoPairsByGermId.values() + .stream() + .filter(p -> !p.getRight().getProgeny().isEmpty()) + .collect(Collectors.toList()); + + List germIdsOfAllProgeny = nodesWithProgeny.stream() + .flatMap(p -> p.getRight().getParents().stream()) + .map(PedigreeNodeParents::getGermplasmDbId) + .collect(Collectors.toList()); + + List createdOrFoundProgenyNodes = findOrCreatePedigreeNodesFromGermplasmIds(germIdsOfAllProgeny); + + for (Pair nodeWithProgeny : nodesWithProgeny) { + PedigreeNodeEntity nodeEntity = nodeWithProgeny.getLeft(); + PedigreeNode nodeDto = nodeWithProgeny.getRight(); + // Create a map of Nodes with progeny to its + + for (PedigreeNodeParents childNode : nodeDto.getProgeny()) { + PedigreeNodeEntity childEntity = createdOrFoundProgenyNodes.stream() + .filter(pne -> pne.getGermplasm().getId().equals(childNode.getGermplasmDbId())) + .findFirst() + .orElse(null); + + // Impossible to be null because of exception thrown in findOrCreatePedigreeNodesFromGermplasmIds(). + // As long as the germIds of all the parents nodes in the rq were supplied, they will be found or created or this code is never executed. + if (childEntity != null) { + nodeEntity.addParent(childEntity, childNode.getParentType()); + childEntity.addProgeny(nodeEntity, childNode.getParentType()); + } } + } + } - for (PedigreeNodeParents parentNode : node.getParents()) { - PedigreeNodeEntity parentEntity = findOrCreatePedigreeNode(parentNode.getGermplasmDbId()); - entity.addParent(parentEntity, parentNode.getParentType()); - parentEntity.addProgeny(entity, parentNode.getParentType()); + public List convertFromGermplasmToPedigreeBatchUsingNames(List germplasms) + throws BrAPIServerException{ + List result = new ArrayList(); + + Map germsByPedigreeMother = new HashMap<>(); + Map germsByPedigreeFather = new HashMap<>(); + + for (Germplasm germplasm : germplasms) { + List pedigreeList = Arrays.asList(germplasm.getPedigree().split("/")); + + if (!pedigreeList.isEmpty()) { + germsByPedigreeMother.put(germplasm, pedigreeList.get(0)); + + if (pedigreeList.size() > 1) { + germsByPedigreeFather.put(germplasm, pedigreeList.get(1)); + } } } - if (node.getProgeny() != null) { - SearchQueryBuilder search = new SearchQueryBuilder(PedigreeEdgeEntity.class); - search.appendSingle(node.getGermplasmDbId(), "connectedNode.germplasm.id"); - search.appendEnum(PedigreeEdgeEntity.EdgeType.parent, "edgeType"); - Pageable defaultPageSize = PagingUtility.getPageRequest(new Metadata().pagination(new IndexPagination().pageSize(10000000))); - Page existingProgenyEdges = pedigreeEdgeRepository.findAllBySearchAndPaginate(search, defaultPageSize); + List motherGerms = germplasmService.findByNames(new ArrayList<>(germsByPedigreeMother.values())); + + if (motherGerms.isEmpty() && !germsByPedigreeMother.isEmpty()) { + log.warn("Could not find any germplasms looking up with mother names {}", germsByPedigreeMother.values()); + } + + List fatherGerms = germplasmService.findByNames(new ArrayList<>(germsByPedigreeFather.values())); + + if (fatherGerms.isEmpty() && !germsByPedigreeFather.isEmpty()) { + log.warn("Could not find any germplasms looking up with father names {}", germsByPedigreeFather.values()); + } - List edgeIdsToDelete = new ArrayList<>(); - edgeIdsToDelete.addAll(entity.getProgenyEdges().stream().map(e -> e.getId()).collect(Collectors.toList())); - edgeIdsToDelete.addAll(existingProgenyEdges.getContent().stream().map(e -> e.getId()).collect(Collectors.toList())); + for (Germplasm germplasm : germplasms) { + GermplasmEntity motherGerm = null; + GermplasmEntity fatherGerm = null; - if (!edgeIdsToDelete.isEmpty()) { - pedigreeEdgeRepository.deleteAllByIdInBatch(edgeIdsToDelete); - pedigreeEdgeRepository.flush(); + if (germsByPedigreeMother.containsKey(germplasm)) { + String motherString = germsByPedigreeMother.get(germplasm); + motherGerm = motherGerms.stream().filter(g -> g.getGermplasmName().equals(motherString)).findFirst().orElse(null); } - for (PedigreeNodeParents childNode : node.getProgeny()) { - PedigreeNodeEntity childEntity = findOrCreatePedigreeNode(childNode.getGermplasmDbId()); - entity.addProgeny(childEntity, childNode.getParentType()); - childEntity.addParent(entity, childNode.getParentType()); + if (germsByPedigreeFather.containsKey(germplasm)) { + String fatherString = germsByPedigreeFather.get(germplasm); + fatherGerm = fatherGerms.stream().filter(g -> g.getGermplasmName().equals(fatherString)).findFirst().orElse(null); } + + result.add(createPedigreeFromGermplasm(germplasm, motherGerm, fatherGerm)); } + + return result; } public PedigreeNode convertFromGermplasmToPedigree(Germplasm germplasm) throws BrAPIServerException { - PedigreeNode node = new PedigreeNode(); List pedigreeList = new ArrayList<>(); if (germplasm.getPedigree() != null) { pedigreeList = Arrays.asList(germplasm.getPedigree().split("/")); } - Optional motherOpt = Optional.empty(); - Optional fatherOpt = Optional.empty(); + + GermplasmEntity motherGerm = null; + GermplasmEntity fatherGerm = null; if (pedigreeList.size() > 0) { - motherOpt = Optional.ofNullable(germplasmService.findByUnknownIdentity(pedigreeList.get(0))); + motherGerm = germplasmService.findByUnknownIdentity(pedigreeList.get(0)); if (pedigreeList.size() > 1) { - fatherOpt = Optional.ofNullable(germplasmService.findByUnknownIdentity(pedigreeList.get(1))); + fatherGerm = germplasmService.findByUnknownIdentity(pedigreeList.get(1)); } } + return createPedigreeFromGermplasm(germplasm, + motherGerm, + fatherGerm); + } + + public PedigreeNode createPedigreeFromGermplasm(Germplasm germplasm, + GermplasmEntity motherGerm, + GermplasmEntity fatherGerm) { + PedigreeNode node = new PedigreeNode(); + node.setAdditionalInfo(germplasm.getAdditionalInfo()); node.setBreedingMethodDbId(germplasm.getBreedingMethodDbId()); node.setBreedingMethodName(germplasm.getBreedingMethodName()); @@ -568,17 +895,17 @@ public PedigreeNode convertFromGermplasmToPedigree(Germplasm germplasm) node.setGermplasmPUI(germplasm.getGermplasmPUI()); node.setPedigreeString(germplasm.getPedigree()); - if (motherOpt.isPresent()) { + if (motherGerm != null) { PedigreeNodeParents mother = new PedigreeNodeParents(); - mother.setGermplasmDbId(motherOpt.get().getId()); - mother.setGermplasmName(motherOpt.get().getGermplasmName()); + mother.setGermplasmDbId(motherGerm.getId()); + mother.setGermplasmName(motherGerm.getGermplasmName()); mother.setParentType(ParentType.FEMALE); node.addParentsItem(mother); } - if (fatherOpt.isPresent()) { + if (fatherGerm != null) { PedigreeNodeParents father = new PedigreeNodeParents(); - father.setGermplasmDbId(fatherOpt.get().getId()); - father.setGermplasmName(fatherOpt.get().getGermplasmName()); + father.setGermplasmDbId(fatherGerm.getId()); + father.setGermplasmName(fatherGerm.getGermplasmName()); father.setParentType(ParentType.MALE); node.addParentsItem(father); } diff --git a/src/main/resources/application.properties.template b/src/main/resources/application.properties.template index 8baf2928..2e0166a2 100644 --- a/src/main/resources/application.properties.template +++ b/src/main/resources/application.properties.template @@ -12,8 +12,20 @@ spring.flyway.baselineOnMigrate=true spring.datasource.driver-class-name=org.postgresql.Driver +# This property when set to true makes it so that a DB transaction is open through the body of a request, nullifying the use of @Transactional. +# It is generally recommended that this be set to false, and methods are properly annotated and release the transactions when complete. +# However, many of the endpoints already rely on this kind of connection infrastructure and changing is more trouble than it's worth. +spring.jpa.open-in-view=true + +spring.jpa.properties.hibernate.jdbc.batch_size=50 +spring.jpa.properties.hibernate.order_inserts=true +spring.jpa.properties.hibernate.order_updates=true spring.jpa.hibernate.ddl-auto=validate + +# Use these to help debug queries. +# The stats will tell you how long hibernate transactions are taking, how many queries occur, how many entities are being flushed/accessed, etc. spring.jpa.show-sql=false +spring.jpa.properties.hibernate.generate_statistics=false spring.mvc.dispatch-options-request=true