Sorting a custom JPA query with pageable - spring

So, I've already done this using the standard Spring Data JPA interface which extends PagingAndSortingRepository in order to achieve pagination and sorting for a REST API. The thing is, now I want to achieve the very same thing but now using just vanilla JPA and so far so good I managed to get my API to paginate but the sorting doesn't work at all. Every time I try to set the parameter (from a pageable object using pageable.getSort()) it ends with a query error (either if I just send a string as parameter like "name" or just send the sort object, it shows errors).
Here's some code:
My repo implementation:
#Override
public List<Project> findByAll(Pageable pageable) {
Query query = em.createQuery("SELECT project FROM Project project ORDER BY :sort");
query.setParameter("sort", pageable.getSort());
query.setMaxResults(pageable.getPageSize());
query.setFirstResult(pageable.getPageSize() * pageable.getPageNumber());
return query.getResultList();
}
My service:
#Override
public Page<Project> findAll(Pageable pageable) {
objects = Lists.newArrayList(repository.findByAll(pageable));
PageImpl<Project> pages= new PageImpl<Project>(objects, pageable, repository.count());
return pages;
}
To be clear, I'm filling the Pageable object via URI and from the console I can say it's actually getting the data, so I assume the problem is with the repo.
Edit: This is the error I get when I replace the setParameter("sort", ...) for a hardcoded string aka query.setParameter("sort", "name"):
java.lang.NumberFormatException: For input string: "name"
And I think this method should stand for strings as well. If I use query.setParameter("sort", pageable.getSort()), the error is the same.

The order by cannot be set as a query parameter. Also, the Pageable.getSort().toString() likely won't return a string suitable for use in an order by clause as it will result in a String that represents the Order as property: ORDER, note the colon.
Here are some modifications that will work, assuming Java 8...
String order = StringUtils.collectionToCommaDelimitedString(
StreamSupport.stream(sort.spliterator(), false)
.map(o -> o.getProperty() + " " + o.getDirection())
.collect(Collectors.toList()));
Query query = em.createQuery(
String.format("SELECT project FROM Project project ORDER BY %s", order));

Related

Neo4j 6 Spring boot 2.4 migrate driver session query

I am trying to migrate to neo4j 6. Whats the equivalent of this method in neo4j6?
The Result here contains: {ref=Employee.... etc, so the actual Java objects.
//org.neo4j.ogm.session.Session is autowired
#GetMapping("/companies/{companyId}/refs")
public Result getCompanyRefs(#PathVariable final String companyId)
{
String query = "MATCH (company:Company)-[ref]-(refObj) where company.id=\"" + companyId + "\" RETURN company,ref,refObj";
return this.session.query(query, Collections.emptyMap());
}
I tried with the new neo4j driver like so:
//org.neo4j.driver.Driver is autowired
#GetMapping("/persons/{personId}/refs")
public Result getPersonRefs(#PathVariable final String personId)
{
String query = "MATCH (person:Person)-[ref]-(refObj) where person.id=\"" + personId + "\" RETURN person,ref,refObj";
return this.driver.session().run(query, Collections.emptyMap());
}
but this gives a Result which is not convertable to my #Node (entity) classes. The previous version gave a Result which contained the actual java objects(mapped to classes).
the result here is:Record<{person: node<7>, ref: relationship<8>, refObj: node<0>}>
Basically the main thing is: i need the nodes mapped to java objects. But i need them via a cypher query, because i need to do some things on the (Result) Nodes before deleting the relationships between them.
so it turns out it does give back the things i need.
Result result = this.getPersonRefs(id);
result.list().forEach((entry) -> {
The problem was that for example neither entry.get("refObj").asObject(), nor asNode() actually gave back what i thought it was supposed to give back.
The Solution:
entry.get("refObj").asMap()
this gives back the actual properties of the object. Then you just need to convert it to MyClass.class with an ObjectMapper.

Spring data JDBC query creation with pagination complains IncorrectResultSizeDataAccessException: Incorrect result size

I'm struggling to trying the pagination feature, as described in the reference document.
This is my table schema:
CREATE TABLE cities
(
id int PRIMARY KEY,
name varchar(255),
pref_id int
);
Repository:
public interface CityRepository extends CrudRepository<CityEntity, Integer> {
Page<CityEntity> findAll(Pageable pageable);
// get all cities in the prefecture
Page<CityEntity> findByPrefId(Integer prefId, Pageable pageable);
}
Test code:
Page<CityEntity> allCities = repository.findAll(PageRequest.of(0, 10));
Page<CityEntity> cities = repository.findByPrefId(1, PageRequest.of(0, 10));
findAll works well, but findByPrefId throws the following error:
Incorrect result size: expected 1, actual 10
org.springframework.dao.IncorrectResultSizeDataAccessException: Incorrect result size: expected 1, actual 10
at org.springframework.dao.support.DataAccessUtils.nullableSingleResult(DataAccessUtils.java:100)
at org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.queryForObject(NamedParameterJdbcTemplate.java:237)
at org.springframework.data.jdbc.repository.query.AbstractJdbcQuery.lambda$singleObjectQuery$1(AbstractJdbcQuery.java:115)
at org.springframework.data.jdbc.repository.query.PartTreeJdbcQuery.execute(PartTreeJdbcQuery.java:98)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor$QueryMethodInvoker.invoke(QueryExecutorMethodInterceptor.java:195)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:152)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:130)
...
If I change the method signature into List<CityEntity> findByPrefId(Integer prefId, Pageable pageable), it works.
Am I missing something? I'm using the latest version of spring-data-jdbc (2.0.2.RELEASE).
I don't know about the technicality, but this is what I learned from experience.
In your case, if the total number of cities is lesser than the pageable.getPageSize(), then your repository will return a List<>.
But if total number of cities is bigger than the pageable.getPageSize() then your repository will return a Page<>.
Knowing that, this is what I did to work around it.
Long amount = repository.countByPrefId(prefId);
if(pagination.getPageSize()>amount ) {
List<CityEntity> list = repository.findByPrefId(prefId);
} else {
Page<CityEntity> pages = repository.findByPrefId(person, PageRequest.of(0, 10));
}
This also means that in your repository you'll have two differents methods, one with Pageable as a parameter and one with only PrefId as a parameter.
I believe the accepted answer is referring to Spring Data JPA which does work by returning pages based on a count query derived from the custom query OR manually set via countQuery, no reason for the if/else.
However this flat out does not work in Spring Data JDBC.
https://jira.spring.io/browse/DATAJDBC-554
Workaround provided in link but for reference:
interface FooRepository extends PagingAndSortingRepository<FooEntity, Long> {
List<FooEntity> findAllByBar(String bar, Pageable pageable);
Long countAllByBar(String bar);
}
And then combining those 2 queries like this:
List<FooEntity> fooList = repository.findAllByBar("...", pageable);
Long fooTotalCount = repository.countAllByBar("...");
Page<FooEntity> fooPage = PageableExecutionUtils.getPage(fooList, pageable, () -> fooTotalCount);

How to get Spring Boot to map query parameters separately from form data?

#RequestMapping(value = "/**", consumes = MediaType.ALL_VALUE)
#ResponseBody
public Object mirror(HttpServletRequest req, #Nullable #RequestBody Map<String, String> form) {
...
}
I just want the plain key and value for all form data entries here but it also includes query parameters in the map.
I need to be able to tell the difference between what came from the form and what came from the query.
Getting the query parameters separately is easily done using URI parsing but it's not so easy to remove the query parameters from the form map. Especially in the case they have the same keys.
Changing the parameter to MultiValueMap adds values with the same key into an array. Using just a Map causes the query parameters to overwrite the form data with equal keys.
I found where this is happening, for the MockHttpServletRequest at least: buildRequest method:
String query = this.url.getRawQuery();
if (!this.queryParams.isEmpty()) {
String s = UriComponentsBuilder.newInstance().queryParams(this.queryParams).build().encode().getQuery();
query = StringUtils.isEmpty(query) ? s : query + "&" + s;
}
if (query != null) {
request.setQueryString(query);
}
addRequestParams(request, UriComponentsBuilder.fromUri(this.url).build().getQueryParams());
this.parameters.forEach((name, values) -> {
for (String value : values) {
request.addParameter(name, value);
}
});
It's combining the form data and query data into one map. So is there an alternative way to parse the form data ONLY or exclude query params from the map!?
From the javadoc for #RequestParam:
In Spring MVC, "request parameters" map to query parameters, form data, and parts in multipart requests. This is because the Servlet API combines query parameters and form data into a single map called "parameters", and that includes automatic parsing of the request body.
Not sure if there's a more elegant way, but you could possibly use Spring's UriComponentsBuilder class to parse the URI string and get back the query parameters.

LDAP template search by multiple attributes

Trying to search for users details by using userid,emailid,firstname,lastname,GUID,etc...many more values that need to be added in future
The search should be performed using all the attributes which are not null.
Found this piece of code online *
String filter = "(&(sn=YourName)(mail=*))";
*
Is there any other predefined template or such to do the search, more optimal way without directly specifying values to be Null or using if else statements for each and every attribute? All values must be passed to the method and those not null must be used for search using LDAP. Anything? Please help.
You can effectively use the Filters at run time to specify what to use for search and what not depending on some rules or your NULL validations on attributes. Pls find sample code which fetches person name using filters in ldapTemplate :-
public static final String BASE_DN = "dc=xxx,dc=yyy";
private LdapTemplate ldapTemplate ;
public List getPersonNames() {
String cn = "phil more";
String sn = "more";
AndFilter filter = new AndFilter();
filter.and(new EqualsFilter("objectclass", "person"));
filter.and(new EqualsFilter("sn", sn));
filter.and(new WhitespaceWildcardsFilter("cn", cn));
return ldapTemplate.search(
BASE_DN,
filter.encode(),
new AttributesMapper() {
public Object mapFromAttributes(Attributes attrs)
throws NamingException {
return attrs.get("cn").get();
}
});
}
As name suggests the AndFilters joins all individual filters used in lookup like EqualFilter which checks for equality of attributes while WhitespaceWildcardsFilter to perform wildcard search. So here like we got cn = phil more, it in turn uses *phil*more* for search.

Spring Repository method complains about field not found

I'm using Spring Data Neo4j in my project and I'm having issues with naming conventions for repositories.
This is a simple class containing only one field and the getters/setters
#RelationshipEntity
public class ScoredRelationship
{
protected Float score;
}
and the class below extends it with other kind of fields
#RelationshipEntity(
type = RecommenderRelTypes.GOV_CONSUMER_TO_GOV_CONSUMER_SIMILARITY)
public class GovConsumerToGovConsumerSimilarity extends ScoredRelationship
{ // Other fields}
To access the relationship, I'm using the usual repository class
public interface GovConsumerToGovConsumerSimilarityRepository extends
GraphRepository<GovConsumerToGovConsumerSimilarity>
{
public Set<GovConsumerToGovConsumerSimilarity> findByScoreGreaterThan(Float value);
public Set<GovConsumerToGovConsumerSimilarity>
findByScoreGreaterThanOrderByScoreDesc(Float value);
public Set<GovConsumerToGovConsumerSimilarity>
findTopXByScoreGreaterThanOrderByScoreDesc(int limit, Float score);
}
This code compiles well. However, whenever I'm trying to use one of the methods, Spring return a series of exception or doesn't act as intended.
F.e. #findByScoreGreaterThan(0.3f) always returns an empty set. However, invoking a findAll() and printing all the scores it actually has a lot of objects with a score greater than 0.3.
In the second and third case, it always throws an exception saying
Caused by: Unknown identifier `score`.
at org.neo4j.cypher.internal.symbols.SymbolTable.evaluateType(SymbolTable.scala:60)
at org.neo4j.cypher.internal.commands.expressions.Identifier.evaluateType(Identifier.scala:51)
at org.neo4j.cypher.internal.commands.expressions.Expression.assertTypes(Expression.scala:53)
at org.neo4j.cypher.internal.pipes.SortPipe$$anonfun$assertTypes$1.apply(SortPipe.scala:34)
at org.neo4j.cypher.internal.pipes.SortPipe$$anonfun$assertTypes$1.apply(SortPipe.scala:33)
at scala.collection.LinearSeqOptimized$class.foreach(LinearSeqOptimized.scala:59)
at scala.collection.immutable.List.foreach(List.scala:45)
at org.neo4j.cypher.internal.pipes.SortPipe.assertTypes(SortPipe.scala:33)
at org.neo4j.cypher.internal.pipes.PipeWithSource.<init>(PipeWithSource.scala:27)
at org.neo4j.cypher.internal.pipes.SortPipe.<init>(SortPipe.scala:29)
at org.neo4j.cypher.internal.executionplan.builders.SortBuilder.apply(SortBuilder.scala:33)
at org.neo4j.cypher.internal.executionplan.ExecutionPlanImpl.prepareExecutionPlan(ExecutionPlanImpl.scala:49)
at org.neo4j.cypher.internal.executionplan.ExecutionPlanImpl.<init>(ExecutionPlanImpl.scala:33)
at org.neo4j.cypher.ExecutionEngine$$anonfun$prepare$1.apply(ExecutionEngine.scala:67)
at org.neo4j.cypher.ExecutionEngine$$anonfun$prepare$1.apply(ExecutionEngine.scala:67)
at org.neo4j.cypher.internal.LRUCache.getOrElseUpdate(LRUCache.scala:37)
at org.neo4j.cypher.ExecutionEngine.prepare(ExecutionEngine.scala:67)
at org.neo4j.cypher.ExecutionEngine.execute(ExecutionEngine.scala:59)
at org.neo4j.cypher.ExecutionEngine.execute(ExecutionEngine.scala:63)
at org.neo4j.cypher.javacompat.ExecutionEngine.execute(ExecutionEngine.java:79)
at org.springframework.data.neo4j.support.query.CypherQueryEngine.parseAndExecuteQuery(CypherQueryEngine.java:61)
How could be possible? I mean, the class obviously has the score field. Also, executing the simple #findByScoreGreaterThan(float value) doesn't throw any exception, but at the same the latter method always returns an empty set.
EDIT:
These are the queries used by Spring. Actually, they seems right
Executing cypher query: START `govConsumerToGovConsumerSimilarity`=node:__types__(className="it.cerict.recommender.persistence.neo4j.GovConsumerToGovConsumerSimilarity") WHERE `govConsumerToGovConsumerSimilarity`.`score`! > {0} RETURN `govConsumerToGovConsumerSimilarity` params {0=0.3}
Executing cypher query: START `govConsumerToGovConsumerSimilarity`=node:__types__(className="it.cerict.recommender.persistence.neo4j.GovConsumerToGovConsumerSimilarity") WHERE `govConsumerToGovConsumerSimilarity`.`score`! > {0} RETURN `govConsumerToGovConsumerSimilarity` ORDER BY score DESC params {0=0.3}
EDIT2: I've also tried to change the score type from Float to float with no further improvements.
This seems to be a bug related to Spring Data Neo4j. Looking to the query executed, it is clear that it searches for nodes, while it should search for relationships.
I changed the #findByScoreGreaterThanOrderByScoreDesc() method using the #Query annotation that specifies the following Cypher query
START `govConsumerToGovConsumerSimilarity`=rel:__rel_types__(className="it.cerict.recommender.persistence.neo4j.GovConsumerToGovConsumerSimilarity") WHERE `govConsumerToGovConsumerSimilarity`.`score`! > 0.3 RETURN `govConsumerToGovConsumerSimilarity` ORDER BY `govConsumerToGovConsumerSimilarity`.`score` DESC;

Resources