Searching multiple fields with multiple queries in Spring Elasticsearch - spring

I am using Spring Elasticsearch. This is my java class :
#Entity
#Document(indexName = "shopindex")
public class Shop implements Serializable {
private #Id #GeneratedValue(strategy = GenerationType.IDENTITY) Long id;
private String imagePath;
#Field(type = FieldType.Text, name = "name")
private String name;
#Field(type = FieldType.Text, name = "description")
private String description;
#Field(type = FieldType.Text, name = "address")
private String address;
#Field(type = FieldType.Text, name = "locality")
private String locality;
#Field(type = FieldType.Keyword, name = "city")
private String city;
#Field(type = FieldType.Keyword, name = "state")
private String state;
private String timing;
#Field(type = FieldType.Nested, includeInParent = true)
private ArrayList<Listing> listings;
Shop () {}
}
I want to have two query strings, location and query. I want query to search through fields name, description, and listing and location to search through fields address, location, city, and state. I am using this query for search but I am getting exception :
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(boolQuery.must(QueryBuilders.multiMatchQuery(location, "address", "locality", "city", "state"))
.must(boolQuery.should(QueryBuilders.multiMatchQuery(query, "name", "description"))
.should(QueryBuilders
.nestedQuery("listings",
QueryBuilders.multiMatchQuery(query, "listings.name", "listings.description"),ScoreMode.Avg))))
.build();
Iterable<Shop> itr = searchRepository.search(searchQuery);

Related

MapStruct - mapping method from iterable to non-iterable

I have been working with MapStruct some days now and haven't yet achieved what i need.
As part of the exercises with Spring, I am writing a small app that will display information about the movies (title, description, director, etc.) and additionally the movie category.
Therefore, I created an additional Entity called Category, so that (e.g. an admin) could add or remove individual category names.
Movie Entity:
public class Movie {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
private String director;
private int year;
#ManyToMany
#Column(nullable = false)
private List<Category> category;
private LocalDate createdAt;
}
Category Entity
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String categoryName;
private LocalDate createdAt;
}
I packed it all into MapStruct and DTOs.
MovieDTORequest.java
public class MovieDTORequest {
private String title;
private String content;
private String director;
private List<Category> category;
private int year;
}
MovieDTOResponse.java
public class MovieDTOResponse {
private String title;
private String content;
private String director;
private String categoryName;
private int year;
private LocalDate createdAt;
}
And MovieMapper.java
#Mapper(componentModel = "spring")
public interface MovieMapper {
#Mapping(target = "categoryName", source = "category")
MovieDTOResponse movieToMovieDTO(Movie movie);
#Mapping(target = "id", source = "title")
#Mapping(target = "createdAt", constant = "")
Movie movieRequestToMovie(MovieDTORequest request);
#Mapping(target = "id", source = "title")
#Mapping(target = "createdAt", constant = "")
void updateMovie(MovieDTORequest request, #MappingTarget Movie target);
String map(List<Category> value);
}
However, I have a problem with Mapper. First, I got the error:
"Can't map property "List<Category> category" to "String categoryName". Consider to declare/implement a mapping method: "String map(List<Category> value)"
and when I wrote it in Mapper, I have one more error:
Can't generate mapping method from iterable type from java stdlib to non-iterable type.
I am asking for help, because I am already lost.
You should define default implementation for String map(List<Category> value) inside MovieMapper interface, what would Mapstruct use to map property List<Category> category to String categoryName. For example:
#Mapper(componentModel = "spring")
public interface MovieMapper {
#Mapping(target = "categoryName", source = "category")
MovieDTOResponse movieToMovieDTO(Movie movie);
default String map(List<Category> value){
//TODO: Implement your own logic that determines categoryName
return "Movie Categories";
}
}

How to search float fields as text in elastic using QueryBuilder

I ve document named plan that correspond plan entity
#Entity
#Table(name = "plan")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#org.springframework.data.elasticsearch.annotations.Document(indexName = "plan")
public class Plan extends AbstractAuditingEntity implements Serializable {
#Id
#GeneratedValue
#Column(name = "id")
#Field(type = FieldType.Text , fielddata = true)
private UUID id;
#NotNull
#Column(name = "name", nullable = false, unique = true)
private String name;
#Column(name = "description")
private String description;
#Column(name = "current_price")
#Field(type = FieldType.Text , fielddata = true )
private Float currentPrice;
}
Here my method search implementation
public Page<Plan> search(String query, Pageable pageable) {
NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(queryStringQuery("*"+query+"*").defaultOperator(Operator.AND));
nativeSearchQuery.setPageable(pageable);
List<Plan> hits = elasticsearchTemplate
.search(nativeSearchQuery, Plan.class)
.map(SearchHit::getContent)
.stream()
.collect(Collectors.toList());
return new PageImpl<>(hits, pageable, hits.size());
}
Name and Description are searchable but float field isn't .
Marking as FieldType.Float doesn't give expected result .

Error while indexing in Hibernate Search - Could not get property value

I am using Hibernate Search with Spring Boot to create a searchable rest api. Trying to POST an instance of "Training", I receive the following stack traces. None of the two are very insightful to me which is why I am reaching out for help.
Stack trace:
https://pastebin.com/pmurg1N3
It appears to me that it is trying to index a null entity!? How can that happen? Any ideas?
The entity:
#Entity #Getter #Setter #NoArgsConstructor
#ToString(onlyExplicitlyIncluded = true)
#Audited #Indexed(index = "Training")
#AnalyzerDef(name = "ngram",
tokenizer = #TokenizerDef(factory = StandardTokenizerFactory.class ),
filters = {
#TokenFilterDef(factory = StandardFilterFactory.class),
#TokenFilterDef(factory = LowerCaseFilterFactory.class),
#TokenFilterDef(factory = StopFilterFactory.class),
#TokenFilterDef(factory = NGramFilterFactory.class,
params = {
#Parameter(name = "minGramSize", value = "2"),
}
)
}
)
#Analyzer(definition = "ngram")
public class Training implements BaseEntity<Long>, OwnedEntity {
#Id
#GeneratedValue
#ToString.Include
private Long id;
#NotNull
#RestResourceMapper(context = RestResourceContext.IDENTITY, path = "/companies/{id}")
#JsonProperty(access = Access.WRITE_ONLY)
#JsonDeserialize(using = RestResourceURLSerializer.class)
private Long owner;
#NotNull
#Field(index = Index.YES, analyze = Analyze.YES, store = Store.YES)
private String name;
#Column(length = 10000)
private String goals;
#Column(length = 10000)
private String description;
#Enumerated(EnumType.STRING)
#Field(index = Index.YES, store = Store.YES, analyze = Analyze.NO, bridge=#FieldBridge(impl=EnumBridge.class))
private Audience audience;
#Enumerated(EnumType.STRING)
#Field(index = Index.YES, store = Store.YES, analyze = Analyze.NO, bridge=#FieldBridge(impl=EnumBridge.class))
private Level level;
#ManyToMany
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#NotNull #Size(min = 1)
#IndexedEmbedded
private Set<ProductVersion> versions;
#NotNull
private Boolean enabled = false;
#NotNull
#Min(1)
#IndexedEmbedded
#Field(index = Index.YES, store = Store.YES, analyze = Analyze.NO)
#NumericField
private Integer maxStudents;
#NotNull
#ManyToOne(fetch = FetchType.LAZY)
private Agenda agenda;
#NotNull
#Min(1)
#Field(index = Index.YES, store = Store.YES, analyze = Analyze.NO)
#NumericField
private Integer durationDays;
#IndexedEmbedded
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#ManyToMany(cascade = CascadeType.PERSIST)
private Set<Tag> tags = new HashSet<>();
I'd say either your versions collection or your tags collection contains null objects, which is generally not something we expect in a Hibernate ORM association, and apparently not something Hibernate Search expects either.
Can you check that in debug mode?

How do I add foreign keys into a new table in SpringBoot

I want to add the primary key from 2 tables (project and book mark) into a new table called ProjectBookmark which contains the primary key from my other two tables as foreign keys with the relationships shown below in springbok.
ERD Diagram
Below are my tables for Project and bookmark
Table 1
#Entity
public class Project {
#Id
#GeneratedValue
private long id;
#Column(name = "Project_Name", unique = true)
private String name;
#Column(name = "Description", unique = true)
private String description;
public Project(String name, String description) {
super();
this.name = name;
this.description = description;
}
Table 2
#Entity
public class Bookmark {
#Id
#GeneratedValue
private long id;
#Column(name = "Name", unique = true)
private String name;
#Column(name = "Type_of_resource", unique = true)
private String type;
#Column(name = "Description", unique = true)
private String description;
#Column(name = "URL", unique = true)
private String url;
public Bookmark(String name, String type, String description, String url) {
super();
this.name = name;
this.type = type;
this.description = description;
this.url = url;
}
Im not sure how to do the relationships and import in the primary keys as foreign keys to my table 3.
#Entity
public class ProjectBookmark {
}
If I understand your question correctly you need a ManyToMany relationship between Project and Bookmark.
You'll need to add the following code to your Project entity class.
#ManyToMany(fetch=FetchType.LAZY)
#JoinTable(name="project_bookmark",
joinColumns = {#JoinColumn(name="project_id", referencedColumnName="id")},
inverseJoinColumns = {#JoinColumn(name="bookmark_id", referencedColumnName="id")}
)
private Set<Bookmark> bookmarks = new HashSet<>();
You don't need an additional id column in your ProjectBookmark table.

Elastic search case insensitive

I have the following annotation based elastic search configuration, I've set the index not to be analyzed because I don't want these fields to be tokenized:
#Document(indexName = "abc", type = "efg")
public class ResourceElasticSearch {
#Id
private String id;
#Field(type = FieldType.String, index = FieldIndex.not_analyzed)
private String name;
#Field(type = FieldType.String, store = true)
private List<String> tags = new ArrayList<>();
#Field(type = FieldType.String)
private String clientId;
#Field(type = FieldType.String, index = FieldIndex.not_analyzed)
private String virtualPath;
#Field(type = FieldType.Date)
private Date lastModifiedTime;
#Field(type = FieldType.Date)
private Date lastQueryTime;
#Field(type = FieldType.String)
private String modificationId;
#Field(type = FieldType.String)
private String realPath;
#Field(type = FieldType.String)
private String extension;
#Field(type = FieldType.String)
private ResourceType type;
Is it possible by using annotations to make the searches on the name, virtualPath and tags to be case-insensitive?
The search looks like this, search by wildcard is required:
private QueryBuilder getQueryBuilderForSearch(SearchCriteria criteria) {
String virtualPath = criteria.getPath();
return boolQuery()
.must(wildcardQuery("virtualPath", virtualPath))
.must(wildcardQuery("name", criteria.getName()));
}
Not really possible what you want to do and it's not about Spring Data configuration, it's about Elasticsearch itself: you indexed data as not_analyzed and it will stay that way.
Also, if you wanted case insensitive data I suggest indexing with keyword analyzer combined with a lowercase token filter.
I've found something based on Andrei Stefan's suggestion which has a similar result to using the annotations:
#Bean
public Client client() throws IOException {
TransportClient client = new TransportClient();
TransportAddress address = new InetSocketTransportAddress(env.getProperty("elasticsearch.host"), Integer.parseInt(env.getProperty("elasticsearch.port")));
client.addTransportAddress(address);
XContentBuilder settingsBuilder = XContentFactory.jsonBuilder()
.startObject()
.startObject("analysis")
.startObject("analyzer")
.startObject("keyword")
.field("tokenizer", "keyword")
.array("filter", "lowercase")
.endObject()
.endObject()
.endObject()
.endObject();
if (!client.admin().indices().prepareExists("abc").execute().actionGet().isExists()) {
client.admin().indices().prepareCreate("abc").setSettings(settingsBuilder).get();
}
return client;
}
You can add #Setting, which consumes file path, after #Document, settings file should contain json like this:
{"analysis":{"analyzer":{"case_insensitive":{"type":"custom","tokenizer":"whitespace","char_filter":["html_strip"],"filter":["lowercase","asciifolding"]}}}}
and field annotation with analyzer #Field(type = FieldType.Keyword, analyzer = "case_insensitive")

Resources