Elasticsearch QueryBuilder: find by ids and prefix - java

I'm implementing custom repository based on Spring Data Elasticsearch:
#Document(indexName = "product", createIndex = false)
public class ProductDocument implements Serializable {
#Id
private String id;
#NotNull
private String name;
}
#Override
public List<ProductDocument> findByIdsAndName(List<String> productIds, String name) {
Query query = new NativeSearchQueryBuilder()
.withIds(productIds)
.withQuery(QueryBuilders.prefixQuery("name", name))
.build();
return operations.search(query, clazz, indexCoordinates)
.stream()
.map(SearchHit::getContent)
.collect(Collectors.toList());
}
The problem I'm facing is that the query is not working, e.g. I save into ES items like
productRepository.saveAll(Arrays.asList(
ProductDocument.builder().id("1").name("Pikachu1").build(),
ProductDocument.builder().id("2").name("Pikachu2").build(),
ProductDocument.builder().id("3").name("Pikachu3").build(),
ProductDocument.builder().id("4").name("Pikachu4").build(),
ProductDocument.builder().id("5").name("Pikachu5").build()
)
and then call the method
assertThat(productRepository.findByIdsAndName(List.of("1", "2"), "Pika")).hasSize(2);
repository method returns an empty list and assertion fails. I've also tried to implement query like
Query query = new NativeSearchQueryBuilder()
.withFilter(QueryBuilders.idsQuery().addIds(productIds.toArray(String[]::new)))
.withQuery(QueryBuilders.prefixQuery("name", name))
.build();
but it fails either. The problem seems to be about prefixQuery because when I remove it the query works.
What am I doing wrong?

Are you sure the id is the _id elasticsearch also searches for. It could be that it is a property of the document but the actual id is something else.
I had this issue. So I stored a document with an id attribute but elasticsearch assigned an id itself which had nothing to do with the id of the document.
Maybe try with postman or a restcall to check what exactly is inside elasticsearch after saveAll.

I've found a work-around:
#Override
public List<ProductDocument> findByIdsAndName(Collection<String> productIds, String name) {
BoolQueryBuilder queryBuilder = new BoolQueryBuilder();
queryBuilder.must(QueryBuilders.idsQuery().addIds(productIds.toArray(String[]::new)));
queryBuilder.must(QueryBuilders.matchBoolPrefixQuery("name", name));
Query query = new NativeSearchQueryBuilder().withQuery(queryBuilder).build();
return operations.search(query, clazz, indexCoordinates)
.stream()
.map(SearchHit::getContent)
.collect(Collectors.toList());
}
Still I think that this is a bug. I've filed an issue here: https://github.com/spring-projects/spring-data-elasticsearch/issues/2188
Let's wait for maintainer's response.

Related

Custom #Query annotated method for update query with spring data MongoRepository

I've read the question Custom method for update query with spring data MongoRepository and my result would be the very same that it is in que previously mentioned question, the only difference would be that I want to do it by using a #Query annotated method. Is it possible? If so, how?
I have a entity and I what to update all documents with a value if a determined criteria has been match.
My entity:
#Document("accrual")
public class Accrual extends AbstractEntity {
#Serial
private static final long serialVersionUID = 1175443967269096002L;
#Indexed(unique = true)
private Long numericUserId;
#Indexed(unique = true)
private Long orderIdentifier;
private boolean error;
// sets, gets, equals, hashCode and toString methods
}
I would like to update the boolean error in every document found by searching for them using a list of Longs matching orderIdentifier attribute.
If it was in a relational database I would have a method like:
#Query(value = "update accrual set error = 0 where order_identifier in :list", nativeQuery = true)
updateErrorByOrderIdentifier(#Param("list") List<Long> orderIdentifiers)
You can try to use #Query annotation for filtering documents to be updated and #Update for providing actual update query.
#Query("{ 'orderIdentifier' : { '$in': ?0 } }")
#Update("{ '$set' : { 'error' : 'false' } }")
void updateAllByOrderIdentifier(List<Long> orderIdentifiers);
More details can be found here

MongoDB and Spring Data - Aggregation with group returns incorrect id

I have following MongoDB document:
#Data
#EqualsAndHashCode(callSuper = true)
#NoArgsConstructor
#AllArgsConstructor
#Accessors(chain = true)
#SuperBuilder
#Document(collection = ReasonDocument.COLLECTION)
public class ReasonDocument extends BaseDocument<ObjectId> {
public static final String COLLECTION = "reasons";
#Id
private ObjectId id;
#Indexed
private ObjectId ownerId;
#Indexed
private LocalDate date;
private Type type;
private String reason;
}
I would like to get all rows for ownerId with latest date and additionally filter some of them out. I wrote custom repository for that, where I use aggregation with a group statement:
public class ReasonsRepositoryImpl implements ReasonsRepository {
private final MongoTemplate mongoTemplate;
#Autowired
public ReasonsRepositoryImpl(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
public List<ReasonDocument> findReasons(LocalDate date) {
final Aggregation aggregation = Aggregation.newAggregation(
sort(Direction.DESC, "date"),
group("ownerId")
.first("id").as("id")
.first("reason").as("reason")
.first("type").as("type")
.first("date").as("date")
.first("ownerId").as("ownerId"),
match(Criteria.where("date").lte(date).and("type").is(Type.TYPE_A))
);
return mongoTemplate.aggregate(aggregation, "reasons", ReasonDocument.class).getMappedResults();
}
}
It is smart query but unfortunately it returns corrupted rows while testing:
java.lang.AssertionError:
Expecting:
<[ReasonDocument(id=5dd5500960483c1b2d974eed, ownerId=5dd5500960483c1b2d974eed, date=2019-05-14, type=TYPA_A, reason=14),
ReasonDocument(id=5dd5500960483c1b2d974ee8, ownerId=5dd5500960483c1b2d974ee8, date=2019-05-15, type=TYPA_A, reason=1)]>
to contain exactly in any order:
<[ReasonDocument(id=5dd5500960483c1b2d974eef, ownerId=5dd5500960483c1b2d974ee8, date=2019-05-15, type=TYPA_A, reason=1),
ReasonDocument(id=5dd5500960483c1b2d974efc, ownerId=5dd5500960483c1b2d974eed, date=2019-05-14, type=TYPA_A, reason=14)]>
elements not found:
<[ReasonDocument(id=5dd5500960483c1b2d974eef, ownerId=5dd5500960483c1b2d974ee8, date=2019-05-15, type=TYPA_A, reason=1),
ReasonDocument(id=5dd5500960483c1b2d974efc, ownerId=5dd5500960483c1b2d974eed, date=2019-05-14, type=TYPA_A, reason=14)]>
and elements not expected:
<[ReasonDocument(id=5dd5500960483c1b2d974eed, ownerId=5dd5500960483c1b2d974eed, date=2019-05-14, type=TYPA_A, reason=14),
ReasonDocument(id=5dd5500960483c1b2d974ee8, ownerId=5dd5500960483c1b2d974ee8, date=2019-05-15, type=TYPA_A, reason=1)]>
The id returned is the same as ownerId.
Could anyone say what is wrong with the query?
Im not entirely sure whether or not this may be the problem. But did you check how mongo has saved the ID? because even if you're grouping by ownerID. IF mongo has saved the item under the _id header in your Json. Then you need to refer it as _id
Ex: If it looks like this
{
"_id" : "2893u4jrnjnwfwpfn",
"name" : "Jenkins"
}
then your groupBy should be groupBy(_id) and not what you've written.
This happens to be limitation of MongoDB and ORM, unless I'm not aware of something.
According to documentation https://docs.mongodb.com/manual/reference/operator/aggregation/group/, native mongo query looks like this:
{
$group:
{
_id: <expression>, // Group By Expression
<field1>: { <accumulator1> : <expression1> },
...
}
}
So grouping itself creates new _id - if I group by ownerId that value will end up in _id field.
One way of solving this is by using:
.first("_id").as("oldId")
and creating a new type with oldId as a field that can be later used to map back to original Document.

Spring Data JPA Query method to find all objects with empty list property

I have an entity which contains a property that is a list:
#Entity
#Table(name="sales.jobs")
public class Job {
...
private List<Shipment> jobShipments;
...
#OneToMany(cascade=CascadeType.ALL,orphanRemoval=true, fetch=FetchType.LAZY)
#JoinTable(name="sales.jobs_shipments",
joinColumns = #JoinColumn(name="fk_jobid", referencedColumnName="pk_jobid"),
inverseJoinColumns = #JoinColumn(name="fk_shipmentid", referencedColumnName="pk_shipmentid"))
public List<Shipment> getJobShipments() {
return jobShipments;
}
public void setJobShipments(List<Shipment> jobShipments) {
this.jobShipments = jobShipments;
}
...
}
In the repository, I would like to have a query method which returns all jobs where jobShipments is an empty list. I had first tried the IsNull keyword as shown below, but that doesn't work because if there are no shipments it is an empty list rather than null.
#Repository
public interface JobRepository extends CrudRepository<Job, Integer> {
Page<Job> findByJobShipmentsIsNull(Pageable pageable);
}
I looked through the keywords in the documentation hoping to find something like IsEmpty, but I couldn't find anything that looked right.
I am looking for an answer that uses keywords rather than writing out a query, but I suppose that would be the fallback. Thanks!
Actually, there're keywords IsEmpty and Empty for collections:
#Repository
public interface JobRepository extends CrudRepository<Job, Integer> {
Page<Job> findByJobShipmentsIsEmpty(Pageable pageable);
}
#Entity
#Table(name="sales.jobs")
public class Job {
...
#OneToMany(cascade=CascadeType.ALL,orphanRemoval=true, fetch=FetchType.LAZY)
#JoinTable(name="sales.jobs_shipments",
joinColumns = #JoinColumn(name="fk_jobid", referencedColumnName="pk_jobid"),
inverseJoinColumns = #JoinColumn(name="fk_shipmentid", referencedColumnName="pk_shipmentid"))
private List<Shipment> jobShipments = new ArrayList<Shipment>();
}
See Appendix C: Repository Query Keywords.
As it turns out, IsNull is actually working. I checked IsNotNull for a point of reference, and it gave me all the jobs that did have shipments. I then ran this query
SELECT COUNT(*) FROM sales.jobs AS jobs
LEFT JOIN sales.jobs_shipments AS ship ON jobs.PK_JobID = ship.FK_JobID
WHERE ship.FK_ShipmentID IS NULL;
The count from that query matched the result from the IsNull repository query. So even though it is returning an empty array, it is still finding the null entries just like my query.

Spring Pageable doesn't work for ordering

On the internet I found that Spring can do pagination as well as ordering for a list of data retrieved from the database. Accordingly, I created my test class as following:
#Test
public void testPageable() {
int pageSize = 5;
Sort sort = new Sort( Direction.DESC, "someColumnA" );
Pageable pageable = new PageRequest( 0, pageSize, sort );
List<SomeObject> listOFSomeObject = getDao().getListData( "paramOne", pageable );
}
When I analyze the List I never get ordering of someColumnA in a DESC fashion, although I get back only 5 records which is correct.
Can someone please let me know what I might be doing wrong? Just as an FYI, I am using Hibernate for database access and Spring named query.
EDIT:
Code for getListData()->
public interface SomeRepository
extends JpaRepository<EntityMappedViaHibernate, String> {
List<Object[]> getListData( #Param(value = PARAM_ONE) final String paramOne, Pageable pageable );
}
My Hibernate entity is as follows:
#NamedQueries(value = {
#NamedQuery(name = "SomeRepository.getListData", query = "select id, someColumnA from EntityMappedViaHibernate where id = :paramOne")
})
#Entity
#Table(name = "entity_mapped_via_hibernate")
public class EntityMappedViaHibernate implements Serializable {
// Code Omitted on purpose
}
So those of you who are struggling like me the solution to this problem is that you need to have the query inside the Repository. In my case it has to be inside the SomeRepository. So in the code of the repo do the following:
public interface SomeRepository
extends JpaRepository<EntityMappedViaHibernate, String> {
#Query("select id, someColumnA from EntityMappedViaHibernate where id = :paramOne")
List<Object[]> getListData( #Param(value = PARAM_ONE) final String paramOne, Pageable pageable );
}
No need to have the NamedQuery inside the EntityMappedViaHibernate. That's it!!
Hope someone find the answer and do not struggle like I did.

How do I get distinct fields from MongoRepository/QueryDSL?

My Document is
#QueryEntity #Data #Document(collection = "MyCol") public class MyCol {
#Id private String _id;
private String version;
I want to get all distinct version stored in the db.
My attempts:
public interface MyColDao extends MongoRepository<MyCol, String>, QueryDslPredicateExecutor<MyCol> {
#Query("{ distinct : 'MyCol', key : 'version'}")
List<String> findDistinctVersion();
}
Or just findDistinctVersion without the query annotation.
Most of the examples of github have a By-field like
List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
I don't need a By field.
Another example I found here.
#Query("{ distinct : 'channel', key : 'game'}")
public JSONArray listDistinctGames();
This doesn't seem to work for me.
I can't seem to find queryDSL/Morphia's documentation to do this.
public interface MyColDao extends MongoRepository<MyCol, String>, QueryDslPredicateExecutor<MyCol> {
#Query("{'yourdbfieldname':?0}")
List<String> findDistinctVersion(String version);
}
here version replaces your your db field name
more you can see here
This spring documentation provide the details, how to form a expression when you are want to fetch distinct values.
Link
I had a similar problem, but I couldn't work out how to do it within the MongoRepository (as far as I can tell, it's not currently possible) so ended up using MongoTemplate instead.
I believe the following would meet your requirement.
#AutoWired
MongoTemplate mongoTemplate
public List<String> getVersions(){
return mongoTemplate.findDistinct("version", MyCol.class, String.class);
}

Categories

Resources