When saving an audited (#CreatedDate, #LastModifiedDate) document with nested audited documents, the two dates will be also reflected in nested docs.
This is the scenario:
DocumentA.java
public class DocumentA {
#Id
private String id;
#Version
private Long version;
#CreatedDate
private Long createdDate;
#LastModifiedDate
private Long lastModifiedDate;
// getters and setters
}
DocumentB.java
public class DocumentB {
#Id
private String id;
#Version
private Long version;
#CreatedDate
private Long createdDate;
#LastModifiedDate
private Long lastModifiedDate;
private DocumentA docA;
// getters and setters
}
DocumentA is already stored in db having its createdDate and lastModifiedDate set. Then, when saving new DocumentB with nested DocumentA, the 2 dates of nested DocumentA will be modified to the same values just set for DocumentB. This only happen in nested document, while stored DocumentA is not touched (luckily!). The expected behaviour is that nested document will remain exactly the same just set via code (it means the same of the original documentA)
This is working like it is design.
Embedding the document A is not same as having the reference to document A. Embedding document is managed as part of main document means all the changes are tracked as they are top level fields in document B. Referencing document it is tracked and managed separately.
If you are only referencing you should either use manual reference and load using separate call or using $lookup aggregation query. Other alternative would be to use dbref to have the driver load the referenced document when it is loading the main document.
Related
I have a question about elasticsearch with spring data.
#Data
#NoArgsConstructor
#AllArgsConstructor
#Document(indexName = "my_es_index")
public class MyEsIndex {
private String id;
private Long counter;
private Long timestamp;
}
and repository
public interface MyEsIndexRepository extends ElasticsearchRepository<MyEsIndex, String> {
Optional<MyEsIndex> findFirstByIdOrderByTimestampDesc(String id);
}
so I have a service where I have to search first for previous one saved record to retrieve previous value, always doing search ordered by timestamp.
#Service
#RequiredArgsConstructor
public class MyEsService {
private final MyEsIndexRepository repository;
public MyEsIndex insert(String previousId) {
Long previousCounter =
repository.findFirstByIdOrderByTimestampDesc(previousId).map(MyEsIndex::getCounter).orElse(0L);
var index = new MyEsIndex(UUID.randomUUID().toString(), ++previousCounter,
Instant.now().toEpochMilli());
return repository.save(index);
}
}
and when trying to do the operation receiving
{"error":{"root_cause":[{"type":"query_shard_exception","reason":"No mapping found for [timestamp] in order to sort on","index":"my_es_index"}
is it possible to do initialization for fields in elasticsearch on empty index?
because the solution of init config is not that clear because it will be used only once when starting working with empty index where never saved a record
#Configuration
public class InitElasticsearchConfig {
private final MyEsIndexRepository repository;
#EventListener(ApplicationReadyEvent.class)
public void initIndex() {
if (repository.findAll(PageRequest.of(0, 1)).isEmpty()) {
var initIndex = new MyEsIndex("initId", 0L, 0L);
repository.save(initIndex);
repository.delete(initIndex);
}
}
is it possible to delegate this solution to spring? I didn't find any
When using Spring Data Elasticsearch repositories - as you do - the normal behaviour is that the mapping is written to Elasticsearch after index creation on application startup when the index does not yet exist.
The problem in your code is that you do not define to what types the properties of your entity should be mapped; you need to add #Field annotations to do that:
#Document(indexName = "my_es_index")
public class MyEsIndex {
private String id;
#Field(type = FieldType.Long)
private Long counter;
#Field(type = FieldType.Long)
private Long timestamp;
}
Properties that are not annotated with a #Field annotation are not written to the mapping but left for automatic mapping by Elasticsearch, that's the cause for the sort not working. As there is no document written to the index, Elasticsearch does not know what type it is and how to sort on that.
In your code there is another thing that might probably not match your desired application logic. In Spring Data Elasticsearch an entity needs to have an id property, that's the property that will be used as the document's id in Elasticsearch. This is normally defined by annotating the property with #Id, if that is missing - as in your case - a property with the name of "id" or "document" is used. So in your case the property id is used.
A document's id is unique in Elasticsearch, if you store a new document under an existing id, the previous content will be overwritten. If that's what you want, the you should add the #Id annotation to your property to make it clear that this is the unique id. But in this case then your code findFirstByIdOrderByTimestamp does not make sense, as a find by id will always return at most one document, so the order by is irrelevant, you could just use a findById() then. I assume that the id should be unique as you initialize it with a UUID.
If your id is not unique and you have multiple documents with the same id and different timestamps, the you'll need to add a new unique property to your entity and annotate that with #Id to prevent id to be used as a unique identifier.
I have a collection in mongo containing both new data with the version field and old data imported from an old couchbase bucket which doesn't contain the version field.
How can I update an existing record? i.e. adding the version=0 automatically on save?
public class Entity {
private String someField;
#CreatedDate
private Instant createdAt;
#LastModifiedDate
private Instant updatedAt;
#Version
private Long version;
}
My base entity that is common part for many other entity:-
#MappedSuperclass
public abstract class IdBase {
#GeneratedValue
#Column(name = "id")
private Long id;
#Version
private Long version;
#CreatedBy
#Column(name = "created_by")
private String createdBy;
#CreatedDate
private Instant created;
#LastModifiedBy
#Setter(AccessLevel.PRIVATE)
#Column(name = "updated_by")
private String updatedBy;
#LastModifiedDate
#Setter(AccessLevel.PRIVATE)
private Instant updated;
}
One of the entity as follows:-
#Entity
#Table(name="TBL_SUB_EMPLOYEES")
public class SubEmployeeEntity extends IdBase {
#Column(name="sub_title")
private String subTitle;
#Column(name="sub_role")
private String subRole;
}
My generic repository as:-
#Repository
public interface AuditRepository<E extends IdBase> extends JpaRepository<E, Long>, JpaSpecificationExecutor<E> {
}
When I try to query SubEmployeeEntity by the generic repository I got error:-
Unable to locate Attribute with the the given name [subTitle] on this
ManagedType [com.test.IdBase]; nested exception is
java.lang.IllegalArgumentException: Unable to locate Attribute with
the the given name [subTitle] on this ManagedType [com.test.IdBase]
IdBase is common class for many entity and I kept only the common column here. I only showed SubEmployeeEntity. I have same kind of entity inherited form IdBase as well. Why is it looking for subTitle in IdBase. How do I fulfill my requirements?
Seems like you are using JPA specifications and within the specification try to access subtype properties although the repository you are using has the type bound IdBase. To make this work, you would have to subclass the repository for every concrete type and also inject that subtype of repository which I guess you don't want to do. If that's the case, you should use TREAT in your specifications to access the subtype properties.
You have to imagine that the type variabbe has no effect as Spring can never observe what you use on your use-site. Only if create a subtype and inject that subtype, Spring can resolve the type variable to the concrete subtype. This leads to the fact that Spring creates a JPA root for the type bound IdBase so now you have to treat the alias to be able to access properties through JPA Criteria.
This question is very similar to Get ID of last inserted document in a mongoDB w/ Java driver with one difference: I'm using a typed / generic collection.
Example DTO:
public class ForumMessageDTO {
#Expose
#BsonId
private ObjectId id;
private Long forumId;
#Expose
private Long userId;
#Expose
private Date created;
#Expose
private String message;
/* getters and setters are not shown here but they are implemented.... */
}
Example code for inserting a document:
public ForumMessageDTO addMessage(Long forumId, Long userId, String message) {
ForumMessageDTO dto = new ForumMessageDTO(forumId, userId, new Date(), message);
messages.insertOne(dto);
return dto; /* dto.id is null here!!! But why? */
}
The returned dto should have its id field filled in, because it was annotated with #BsonId and it has ObjectId type. In reality, it remains null and I don't see how I could access the ObjectId of the inserted document.
This version of collection.insertOne does not return anything, and apparently it does not change the id field of the dto.
Probably I could manually convert the DTO into a Document and use that version of collection.insertOne, and then get the object id and put it back into the DTO but this is very inefficient. Considering the fact that I'm going to use many collections with many different DTO classes, and I do not want to write manual conversions for all of them.
So how can I retrieve the object id of the document that was just inserted?
I want to get a page of results using follwing Java code:
Page<MyDTO> page = repo.findAllOrderByCreatedDate(new PageRequest(pageNumber,pageSize));
In MyDTO I have:
#Entity
class MyDTO{
#Id
private Long id;
private LocalDateTime createdDate;
//getters setters
}
What I get is:
No parameter available for part createdDate SIMPLE_PROPERTY (1):
[Is, Equals].; nested exception is java.lang.IllegalArgumentException:
How to combine paging and sortig with Spring Data?
You can use another constructor of class PageRequest:
Page<MyDTO> page = repo.findAll(new PageRequest(pageNumber,pageSize, new Sort("createdDate")));