How to make an advanced search with Spring Data REST? - java

My task is to make an advanced search with Spring Data REST.
How can I implement it?
I managed to make a method to do a simple search, like this one:
public interface ExampleRepository extends CrudRepository<Example, UUID>{
#RestResource(path="searchByName", rel="searchByName")
Example findByExampleName(#Param("example") String exampleName);
}
This example works perfectly if I have to go simply to the url:
.../api/examples/search/searchByName?example=myExample
But what I have to do if there are more than one field to search?
For example, if my Example class has 5 fields, what implementation should I have to make an advanced search with all possibiles fileds?
Consider this one:
.../api/examples/search/searchByName?filed1=value1&field2=value2&field4=value4
and this one:
.../api/examples/search/searchByName?filed1=value1&field3=value3
What I have to do to implement this search in appropriate way?
Thanks.

Spring Data Rest has integrated QueryDSL with web support as well which you can use for your advanced search requirement. You need to change your repository to implement QueryDslPredicateExecutor and things will work out of the box.
Here is a sample from the blog article about the feature:
$ http :8080/api/stores?address.city=York
{
"_embedded": {
"stores": [
{
"_links": {
…
},
"address": {
"city": "New York",
"location": { "x": -73.938421, "y": 40.851 },
"street": "803 W 181st St",
"zip": "10033-4516"
},
"name": "Washington Hgts/181st St"
},
{
"_links": {
…
},
"address": {
"city": "New York",
"location": { "x": -73.939822, "y": 40.84135 },
"street": "4001 Broadway",
"zip": "10032-1508"
},
"name": "168th & Broadway"
},
…
]
},
"_links": {
…
},
"page": {
"number": 0,
"size": 20,
"totalElements": 209,
"totalPages": 11
}
}

I managed to implement this using Query by Example.
Let's say you have the following models:
#Entity
public class Company {
#Id
#GeneratedValue
Long id;
String name;
String address;
#ManyToOne
Department department;
}
#Entity
public class Department {
#Id
#GeneratedValue
Long id;
String name;
}
And the repository:
#RepositoryRestResource
public interface CompanyRepository extends JpaRepository<Company, Long> {
}
(Note that JpaRepository implements QueryByExampleExecutor).
Now you implement a custom controller:
#RepositoryRestController
#RequiredArgsConstructor
public class CompanyCustomController {
private final CompanyRepository repository;
#GetMapping("/companies/filter")
public ResponseEntity<?> filter(
Company company,
Pageable page,
PagedResourcesAssembler assembler,
PersistentEntityResourceAssembler entityAssembler
){
ExampleMatcher matcher = ExampleMatcher.matching()
.withIgnoreCase()
.withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING);
Example example = Example.of(company, matcher);
Page<?> result = this.repository.findAll(example, page);
return ResponseEntity.ok(assembler.toResource(result, entityAssembler));
}
}
And then you can make queries like:
localhost:8080/companies/filter?name=google&address=NY
You can even query nested entities like:
localhost:8080/companies/filter?name=google&department.name=finances
I omitted some details for brevity, but I created a working example on Github.

The implementation of query methods is widely documented in Spring reference documentation and tons of technical blogs, though quite a bunch are outdated.
Since your question is probably "How can I perform a multi-parameter search with any combination of fields without declaring an awful lot of findBy* methods?", the answer is Querydsl, which is supported by Spring.

I have found a working solution for this task.
#RepositoryRestResource(excerptProjection=MyProjection.class)
public interface MyRepository extends Repository<Entity, UUID> {
#Query("select e from Entity e "
+ "where (:field1='' or e.field1=:field1) "
+ "and (:field2='' or e.field2=:field2) "
// ...
+ "and (:fieldN='' or e.fieldN=:fieldN)"
Page<Entity> advancedSearch(#Param("field1") String field1,
#Param("field2") String field2,
#Param("fieldN") String fieldN,
Pageable page);
}
With this solution, using this base url:
http://localhost:8080/api/examples/search/advancedSearch
We can make advanced searches with all the fields that we need.
Some examples:
http://localhost:8080/api/examples/search/advancedSearch?field1=example
// filters only for the field1 valorized to "example"
http://localhost:8080/api/examples/search/advancedSearch?field1=name&field2=surname
// filters for all records with field1 valorized to "name" and with field2 valorized to "surname"

I guess You can try following:
List<Person> findDistinctPeopleByLastnameOrFirstname(#Param("lastName")String lastname, #Param("firstName")String firstname);
and examples/search/searchByLastnameOrFirstname?firstName=value1&lastName=value2
Check out: http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation

Related

Spring data: MongoDB: Aggregation: group by nested object

I'm working with MongoDB 3.2.2 and Spring Data 2.1.8. I have the following document model with dynamic data field:
#Data
#Accessors(chain = true)
#Document(collection = "someCollection")
public class SomeEntity implements Serializable {
#Id
String id;
//some fields
Map<String, Object> data;
}
My goal is grouping my documents by specific key from the data field. For example, i have the following db content:
{
"_id": "5e5f8a89b70e4123a8285aa3",
"data": {
"someField": "someValue",
}
},
{
"_id": "5e5f72fcb70e4123a8285aa2",
"data": {
"someField": "someValue",
}
},
{
"_id": "5e5d22939ce87e2fccd80973",
"data": {
"someField": "otherValue",
}
}
I'd like to build the grouping aggregation using Spring Data like the following query for MongoDB:
$group: {
{
_id: "$data.someField",
count: {
$sum: 1
}
}
}
And I'd like to receive the following result:
{
_id: "someValue",
count: 2
},
{
_id: "otherValue",
count: 1
}
For this goal i'm using the next grouping with org.springframework.data.mongodb.core.aggregation.Aggregation:
Aggregation.group("$data.someField").count().as("count")
But i've got an error during execution of aggregation:
org.springframework.data.mapping.PropertyReferenceException: No property someField found for type Object! Traversed path: SomeEntity.data.
What was wrong? Could someone help me, please?
P.S.: i've also tried to use $replaceRoot for data field, so i could group documents by someField, but it's newer db version (New in version 3.4)
Could it be that you only have a little typo: No property someFiled?
Try the following:
group("$data.someField").count().as("count")

How can I produce a JSON_OBJECT instead of JSON_ARRAY as result to my API queries

For some time I have Struggled with this problem
I have distilled a short sample to produce my results
When Spring-Boot produces api output and there is more than one record it is enclosed as a JSON_ARRAY starting with [ and ending with ] with comma-seperated JSON_OBJECTS inside
I Want the result to be surrounded by another JSON_OBJECT like
{entity:[{.....},{.....}]}
e.g.
[
{
"locationId": "l1",
"locationName": "New York"
},
{
"locationId": "l3",
"locationName": "London"
}
]
must become
{
location:{
[{
"locationId": "l1",
"locationName": "New York"
},
{
"locationId": "l3",
"locationName": "London"
}]
}}
public class Location {
private String id;
private String name;
}
// Getter & Setters etc ommited
Obvously I have googled for this and have tried many solution and could not find any
If I add "org.springframework.boot:spring-boot-starter-data-rest"
to my project spring will create some endpoints for my entities that actually produces the desired results, however I have some non-standars queries that is excluded and I need to do them myself
If you can teach me to overcome this difficulty I will apreciate it
Create a class as follows:
public class LocationWrapper {
private List<Location> location;
}
and return an instance of LocationWrapper from the #RestController method.

Why spring data rest maintains arbitrary order of inherited entities

I am facing a challenge with spring data rest where sorting only applies to instances within entity implementations, not across whole result set.
Entities:
#Entity
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "Entity_Type")
public abstract class BaseEntity implements Identifiable<Long> {
public Long getId();
#ManyToOne
RelatedEntity related;
}
#Entity
#DiscriminatorValue("Entity1")
public class Entity1 extends BaseEntity { }
#Entity
#DiscriminatorValue("Entity2")
public class Entity2 extends BaseEntity { }
Repositories:
#NoRepositoryBean
public interface AbstractEntityRepository<T extends BaseEntity> extends PagingAndSortingRepository<T, Long> {
List<T> findByRelatedEntityId(#Param("id") Long id, Sort sort);
}
#RepositoryRestResource
public interface BaseEntityRepository extends AbstractEntityRepository<BaseEntity> { }
+ repository for each "T extends BaseEntity"
Now the problem:
search method baseEntityRepository/search/findByRelatedEntity?id=... with specified &sort=id,asc or &sort=id,desc correctly orders the entities within the entity "groups" but the order of the entity "groups" is presented in arbitrary fashion. The arbitrary order seems to be different for findAll but it's difficult to confirm without knowing what exactly is going on. Example output:
Examples:
sort=id,asc
{
"_embedded": {
"entity1s": [ "id": 1... ],
"entity2s": [ "id": 3... ],
"entity3s": [ "id": 2,10,20,... ], <--- illustrates order is correct within group
"entity4s": [ "id": 4... ]
}
}
sort=id,desc
{
"_embedded": {
"entity4s": [ "id": 4... ],
"entity1s": [ "id": 1... ],
"entity3s": [ "id": 20,10,2... ], <--- illustrates order is correct within group
"entity2s": [ "id": 3]
}
}
Questions:
1 - How are the "groups" called in spring data rest?
2 - What is the cause of the arbitrary order? Why doesn't entity3s (lowest ID = 2) come before entity2s (lowest ID = 3) in the first example?
3 - Can I get entities sorted purely by ID, even if it mixed up the entities?
4 - For findAll, why is the result different for paginated output (first 20 records) and full output? (&size=1000)

Deserialization issue while linking two ArangoDB collection in ArrayList

I have two db collections Agency & Program where an Agency can have many programs and all the further concept implementation is using Programs only. So I have created two POJO
public class Agency implements Serializable {
#DocumentField(DocumentField.Type.ID)
private String agencyId;
#DocumentField(DocumentField.Type.KEY)
#SerializedName("AGENCYNAME")
private String agencyName;
#SerializedName("SHORTNAME")
private String shortName;
#Expose(serialize = false, deserialize = true)
#SerializedName("PROGRAMS")
private List<Program> programs;
// Other fields with Getter & Setters
}
public class Program implements Serializable {
#DocumentField(DocumentField.Type.ID)
private String programId;
#SerializedName("PROGRAMNAME")
private String programName;
#DocumentField(DocumentField.Type.KEY)
#SerializedName("SHORTNAME")
private String shortName;
#SerializedName("AGENCY")
private Agency agency;
// Other fields with Getter & Setters
}
When I run AQL : for a in AGENCY return merge(a, {PROGRAMS: (for p in PROGRAMS FILTER p.AGENCY == a._id return p)})
I get following JSON
[
{
"AGENCYNAME": "Dummy Agency 1",
"SHORTNAME": "DA1",
"_id": "AGENCY/1062620",
"_key": "1062620",
"_rev": "_URnzj-C---",
"PROGRAMS": [
{
"_key": "DA1DP1",
"_id": "PROGRAMS/DA1DP1",
"_rev": "_UQ6dGOG---",
"AGENCY": "AGENCY/1062620",
"PROGRAMNAME": "DA1 Dummy Program 1",
"SHORTNAME": "DA1DP1"
}
]
},
{
"AGENCYNAME": "Dummy Agency 2",
"SHORTNAME": "DA2",
"_id": "AGENCY/1062358",
"_key": "1062358",
"_rev": "_URnzj-C---",
"PROGRAMS": [
{
"_key": "DA2DP1",
"_id": "PROGRAMS/DA2DP1",
"_rev": "_UQ6dGOG---",
"AGENCY": "AGENCY/1062358",
"PROGRAMNAME": "DA2 Dummy Program 1",
"SHORTNAME": "DA2DP1"
}
]
}
]
When I run this query from arangodb-java-driver 4.1 it throws an exception while deserialization
com.arangodb.velocypack.exception.VPackValueTypeException: Expecting type OBJECT
and if I comment these lines from Agency.java it works fine, But I need to have List in agency.
#Expose(serialize = false, deserialize = true)
#SerializedName("PROGRAMS")
private List<Program> programs;
Is there a way to handle lists in such cases ?
Please suggest a way to overcome this..
Thanks :)
in your POJO Program you have a field agency which is from type Agency but in the JSON you get from the DB this field is from type string. Change type Agency to String and your code works.
update:
To get the result from your query which fits into your current java bean, run:
for a in AGENCY return merge(a, {PROGRAMS: (for p in PROGRAMS FILTER p.AGENCY == a._id return merge(p, {AGENCY: a}))})

Is there a way to return association objects with id in spring data rest without Projection

I am using spring-boot-starter-parent 1.4.1.RELEASE.
Spring boot #ResponseBody doesn't serialize entity id by default.
If I use exposeIdsFor returns the id but I need to configure this for each class
e.g
RepositoryRestConfiguration.exposeIdsFor(User.class);
RepositoryRestConfiguration.exposeIdsFor(Address.class);
Is there a simpler configuration to do this without configuring each entity class.
Refer: https://jira.spring.io/browse/DATAREST-366
The REST resource will render the attribute as a URI to it’s corresponding associated resource. We need to return the associated object instead of URI.
If I use Projection, it will returns the associated objects but I need to configure this for each class
e.g
#Entity
public class Person {
#Id #GeneratedValue
private Long id;
private String firstName, lastName;
#ManyToOne
private Address address;
…
}
PersonRepository:
interface PersonRepository extends CrudRepository<Person, Long> {}
PersonRepository returns,
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons/1"
},
"address" : {
"href" : "http://localhost:8080/persons/1/address"
}
}
}
My Projection:
#Projection(name = "inlineAddress", types = { Person.class })
interface InlineAddress {
String getFirstName();
String getLastName();
Address getAddress();
}
After adding projection, it returns..
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"address" : {
"street": "Bag End",
"state": "The Shire",
"country": "Middle Earth"
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons/1"
},
"address" : {
"href" : "http://localhost:8080/persons/1/address"
}
}
}
If some other classes having the association as an address, then I need to add projection for those classes also.
But I don't want to configure for each classes. How to implement the dynamic Projection? So that all my entities will return the nested object in response.
Refer: https://jira.spring.io/browse/DATAREST-221
Regarding your first question, which was already answered here, exposeIdsFor accepts an arbitrary number of arguments. Also, as mentionned in this thread If all your entity classes are located in the same package, you could get a list of your classes using the Reflections library and feed it to exposeIdsFor().

Categories

Resources