Javers - Can't track mongo updates inside a document - mongodb

I am trying to keep a track of changes made on specific fields inside a document.
This is my user class. I want to keep a track of any change made in count using Javers.
#Document
public class User {
#Id
private String userId;
private String name;
private int count;
private Date creationDate = new Date();
private Map<String, String> userSettings = new HashMap<>();
}
I have added the following code to keep a track of whenever a new Object of User is created.
#Override public User addNewUser(User user) {
mongoTemplate.save(user);
javers.commit("Added", user);
return user;
}
This is the updateCount method using MongoTemplate:
#Override public boolean updateCount(String name) {
Query query = new Query();
query.addCriteria(Criteria.where("name").is(name));
query.addCriteria(Criteria.where("count").gt(0));
Update update = new Update();
update.inc("count", -1);
return Objects
.requireNonNull(reactiveMongoTemplate.updateFirst(query, update, User.class).block())
.getModifiedCount() == 1;
}
Now what I want to track is to commit some kind of change at the end of updateCount method call.
But these changes can't be committed unless I have an entity object. How do I go about this?

Javers is the object auditing framework, you can't track changed made by direct db updates in Javers. All you can do is to take some Java object (an entity), and call javers.commit() on it

Related

Best Practice for handling collection updates in .net-maui (MVVM)

I have been working with MVVM and ObservableCollections for some time now, but it is still not clear to me what is the best practice for handling an update of a collection. When I add an item to the collection the UI gets notified and shows the new item in e.g. a ListView.
But I cannot see how this process works for the update of an item in the Collection. What I do now is to completely re-assign the collection and raise an OnPropertyChanged event but this updates the whole collection which seems like overkill and not really efficient.
Example use-case: The user edits an item and I want the change to be presented in the List or the Collection receives an update from a different service like a SignalR message.
I tried to assign new Values to an item of the ObservableCollection but it seems not to update the View even if I raise the OnPropertyChanged Event
From document From Data Bindings to MVVM,we could know that:
ViewModels generally implement the INotifyPropertyChanged interface,
which means that the class fires a PropertyChanged event whenever one
of its properties changes. The data binding mechanism in Xamarin.Forms
attaches a handler to this PropertyChanged event so it can be notified
when a property changes and keep the target updated with the new
value.
So if you want the UI updates automatically once changing the value of the property of the Item model in your List, you can implement interface INotifyPropertyChanged for your item model.
You can refer to the following code:
public class Item: INotifyPropertyChanged
{
private string _numType;
public string NumType
{
get => _numType;
set
{
SetProperty(ref _numType, value);
}
}
public string Name { get; set; }
bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Object.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Note:
1.Suppose you want to update UI automatically while modifying the value of NumType,you can add following code:
private string _numType;
public string NumType
{
get => _numType;
set
{
SetProperty(ref _numType, value);
}
}
2.If you want UI update automatically after adding or remove item from your datalist, you just define your list as follows:
public ObservableCollection<ItemModel> Items { get; set; }
For more information, check ObservableCollection Class.

How to get last record in spring mongo?

I have a transaction class which stores the each transaction of a customer,Following are the fields in this class.
class Transaction{
#Id
private String id;
private Date date;
private String customerId;
private double openBalance;
private double transctionAmount;
private double finalAmount;
}
I need to fetch only the last inserted record of a customer (let say for customerId = cust123).
I defined following function in repository.
public interface TranscationRepository extends MongoRepository<Transaction, String> {
Optional<Transaction> findTopByCustomerIdOrderByIdDesc(String id);
}
This method giving last entry not by customerId but overall. I tried few modifications to it but did not get success.
I know I can findAllByCustomer but I don't want to pull huge list of transaction which is of no use in this use case. What is correct signature in spring mongo to get last inserted record by a field? I am ok to use custom #Query also.
Thank you.

Spring Data update mongodb Document with DBRF lazy attribute

I want to update a MongoDB document containing a dbrf lazy attribute using spring data.
First of all, I load the existing document, I change the attributes I want and after that, I call #Repository save method, but when I check the document in MongoDB the dbrf lazy attribute is null.
I tried to load the attribute before by calling getAttribute, but that doesn't fix the problem.
Someone could help me?
Thanks
I have the Collection below:
#Data
#Document(collection = "calendriers")
public class CalendrierEntity {
#Id
#AutoGenerate(SequanceKey.CALENDRIER)
private Long id;
#NotNul
private String label;
#NotNull
private HorairesEntity horairesEntity;
#DBRef(lazy = true)
#CascadeSave
#Getter(AccessLevel.NONE)
private List<AbsenceEntity> absenceEntities;
}
and the repository bellow :
#Repository
public interface AbsenceRepository extends MongoRepository<CalendrierEntity, Long> {
AbsenceEntity findById(Long enfantId, LocalDate localDate);
}
I have calendrier document with Id 1L and want to update his label.
The calendrier document have allready a list of Absences.
this my code to update the label.
#Transactional
public void updateLabelCalendrier(Long id, String label){
CalendrierEntity calendrier = calenderRepository.findById(1L);
calendrier.setLabel(label);
calenderRepository.save(calendrier);
}
but when i check data in mongodb, i have the new label but my list of absences became null.

Creating an "IN" query with JPA 2.0 Criteria api

I am using tje JPA criteria API to create an "IN" query. I want to select Courses that are in certain Categories. The Categories are supposed to end up in the IN part of the query.
This is the Course entity. It has a reference to a Category entity, because each Course is in one Category.
#Entity
public class Course implements DomainObject {
private Long id;
private Integer version;
private String name;
private Category category;
#Override
#Id
#GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#ManyToOne
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
}
In my service I want to select Courses that are belong to certain (a list) of Categories.
public List<Course> findCourses(CourseFilter filter) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Course> criteriaQuery = criteriaBuilder.createQuery(Course.class);
Root<Course> root = criteriaQuery.from(Course.class);
List<Predicate> predicateList = new ArrayList<Predicate>();
if (!filter.getCategories().isEmpty()) {
Predicate predicate = root.get(Course_.category).in(filter.getCategories());
predicateList.add(predicate);
}
Predicate[] predicates = new Predicate[predicateList.size()];
predicateList.toArray(predicates);
criteriaQuery.where(predicates);
TypedQuery<Course> typedQuery = entityManager.createQuery(criteriaQuery);
return typedQuery.getResultList();
}
When the query executes on the last line of the method it throws an error:
HTTP Status 500 - Request processing failed; nested exception is
org.springframework.dao.InvalidDataAccessApiUsageException:
org.hibernate.TransientObjectException: object references an unsaved transient instance
save the transient instance before flushing:nl.codebasesoftware.produx.domain.Category;
nested exception is java.lang.IllegalStateException:
org.hibernate.TransientObjectException: object references an unsaved transient instance
save the transient instance before flushing: nl.codebasesoftware.produx.domain.Category
I am not even sure I am using the right way to create an IN query. I think the criteria API is terribly complicated. But before I worry about the IN query I would like to know why Hibernate is throwing this TransientObjectException. The filter.getCategories() call results in actual categories, filled with a primary key id, etc.
Added:
Here is how I get the Category instance that I use to later fetch Courses with. This is also a DAO method that is called via a #Service from a #Controller method.
public Category findByName(String name) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Category> query = builder.createQuery(Category.class);
Root<Category> root = query.from(Category.class);
Predicate predicate = builder.equal(root.get(Category_.urlTitle), name);
query.where(predicate);
TypedQuery<Category> typedQuery = entityManager.createQuery(query);
return getSingleResult(typedQuery);
}
So, Hibernate is telling me I am using Category objects that somehow reference an unsaved entity, but I don't see how. The Category that is returned from this method is just a Category that if fetched by Hibernate. I am not doing anything with it before I send it to the method that fetches Courses.
Here is my the controller method:
#RequestMapping(method = RequestMethod.GET, value = "/{categoryUrlName}")
public String setup(#PathVariable("categoryUrlName") String categoryUrlName, Model model){
// Fetch the category
Category category = categoryService.findByName(categoryUrlName);
// if no category found, throw a 404
if(category == null){
throw new ResourceNotFoundException();
}
// Fetch courses in this category
List<Course> courses = courseService.findCourses(category);
model.addAttribute("courses", courses);
model.addAttribute("category", category);
model.addAttribute("mainContent", "content/category");
return "main";
}
Before executing a query, Hibernate flushes the changes you made to persistent entities in the session. This ensures that the query will search on the latest state of all the entities. Unfortunately, one of the dirty entities that Hibernate tries to flush references a transient entity, and thus can't be flushed, which causes the exception. The exception doesn't come from the query itself, but from the flush before the execution of the query.
You probably did something like the following before executing the query:
Cat cat = em.find(Cat.class, catId); // cat is a persistent persistent entity
cat.setMate(new Mouse()); // the mouse has not been persisted, and cat references it.

JPA merge readonly fields

We have the simplest CRUD task with JPA 1.0 and JAX-WS.
Let's say we have an entity Person.
#Entity
public class Person
{
#Id
private String email;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(insertable = false, updatable = false)
private ReadOnly readOnly;
#Column
private String name;
#XmlElement
public String getEmail()
{
return email;
}
public void setEmail(String email)
{
this.email = email;
}
#XmlElement
public Long getReadOnlyValue()
{
return readOnly.getValue();
}
// more get and set methods
}
Here is scenario.
Client make Web Service request to create person. On the server side everything is straightforward.
And it does work as expected.
#Stateless
#WebService
public class PersonService
{
#PersistenceContext(name = "unit-name")
private EntityManager entityManager;
public Person create(Person person)
{
entityManager.persist(person);
return person;
}
}
Now client tries to update person and this is where, as for me, JPA shows its inconsistence.
public Person update(Person person)
{
Person existingPerson = entityManager.find(Person.class, person.getEmail());
// some logic with existingPerson
// ...
// At this point existingPerson.readOnly is not null and it can't be null
// due to the database.
// The field is not updatable.
// Person object has readOnly field equal to null as it was not passed
// via SOAP request.
// And now we do merge.
entityManager.merge(person);
// At this point existingPerson.getReadOnlyValue()
// will throw NullPointerException.
// And it throws during marshalling.
// It is because now existingPerson.readOnly == person.readOnly and thus null.
// But it won't affect database anyhow because of (updatable = false)
return existingPerson;
}
To avoid this problem I need to expose set for readOnly object and do something like this before merge.
Person existingPerson = entityManager.find(Person.class, person.getEmail());
person.setReadOnlyObject(existingPerson.getReadOnlyObject()); // Arghhh!
My questions:
Is it a feature or just
inconsistence?
How do you (or would
you) handle such situations? Please
don't advice me to use DTOs.
Is it a feature or just inconsistence?
I don't know but I'd say that this is the expected behavior with merge. Here is what is happening when calling merge on a entity:
the existing entity gets loaded in the persistence context (if not already there)
the state is copied from object to merge to the loaded entity
the changes made to the loaded entity are saved to the database upon flush
the loaded entity is returned
This works fine with simple case but doesn't if you receive a partially valued object (with some fields or association set to null) to merge: the null fields will be set to null in the database, this might not be what you want.
How do you (or would you) handle such situations? Please don't advice me to use DTOs.
In that case, you should use a "manual merge": load the existing entity using find and update yourself the fields you want to update by copying the new state and let JPA detect the changes and flush them to the database.