Is it possible to find changes for entities that reference another entity? - javers

I have a Project entity, which is referenced by many other types of entities, for example House and Street. In Javers is it possible to construct a query that would find changes for all entities that belong to a certain project, i.e. reference that project entity?
class Project {
#Id Long id;
}
class House {
#Id Long id;
Project project;
String name;
}
class Street {
#Id Long id;
Project project;
String name;
}

No, Javers isn't a relational database, there are no joins between Entities. All you can do is to change the mapping, and map House and Street as Value Objects. If so, they would be part of the Project Entity and they would be querable using Project Id.

It possible with commit-property-filter with the constraint that each time you commit the change of the entity belong to Project, provide the project id and the corresponding name in the commit property (e.g: projectId, 1).
In case you are using Javers with SpringBoot and track changes through JaversSpringDataJpaAuditableRepositoryAspect then here's the hint for you Is there possibility to provide object dependent map for CommitPropertiesProvider?

My solution is to always have a change on the outer entity (which would be House and Street in your case). This way, JaVers dutifully records a new snapshot, even if just Project changes.
To minimize the storage impact of the "always, but fake change", I created an int f field on the outer entity that I toggle between 0 and 1. With 0, it's even not part of the JSON snapshot data. To figure out which one it should be, I read in the last snapshot and get the old value there. The alternative is to always create a random number here. That uses a bit more space in the JSON, but it saves you from the database queries to retrieve the last snapshot.

Related

DatastoreException: The given key doesn't have a String name value but a conversion to String was attempted

Changed #Id type from Long to String in GCP datastore using spring java Repository.
DatastoreDataException
org.springframework.cloud.gcp.data.datastore.core.mapping.DatastoreDataException: The given key doesn't have a String name value but a conversion to String was attempted
So Keys in datastore can either have the property id which is a number or the property name which is a string.
I included 2 screenshots of an example of each
Numeric id:
String name:
So when you say this:
Changed #Id type from Long to String in GCP datastore using spring java Repository.
What did you actually do?
It sounds like you just changed a model definition in your ORM. This doesn't actually change anything already stored in the datastore, it only impacts new entities going forward. So it sounds like, you're fetching entities with ids but your model definition is expecting them to have names.
You would have to have some kind of data migration job convert them all over. Convert isnt even the right word since changing the key to use name instead would just create a new entity. You would have to delete the old entities that use id in this process.
You would also have to update all other entities that have key properties to this kind too.
So we changed the Id from Long to String. And datastore table was already created with Long Id. so when we changed it we saw the above exception. By creating new table with String Id we resolved the issue.

Spring data. How can I update an entity, having the id modified?

Spring data jpa + postgres.
Have an entity
class Entity {
#Id
#GeneratedValue// generator from here https://stackoverflow.com/questions/60989691/how-to-manually-set-a-value-for-generatedvalue/61007375#61007375
private int id;
private String value;
}
And what I wish to do is to UPDATE an existing entity, setting a different id (be it a bad practice or not) value.
By default it of course is treated as a new entity and is attempted to be INSERTed.
Going by the flow of #Modifying seems to do the job right, but currently struggling to find if I can pass the whole entity instead of pinpointing every field:
update Entity e set e.id=?1, e.value=?2 where...
to
update Entity e set e=?1
So the questions here would be:
1. Is there a way to gracefully do an "UPDATE" with modified id in terms or regular spring-data-jpa flow?
2. If not, is there a way to provide the full entity to be consumed by the #Query?
Is there a way to gracefully do an "UPDATE" with modified id in terms or regular spring-data-jpa flow?
If you are using GenerationType.AUTO strategy for #GeneratedValue (AUTO is the default strategy), then you can set id of Entity to null before calling save. It will insert a new record with rest of the fields being the same as original. id of new record will be generated automatically by database engine.
If you are using GenerationType.AUTO strategy for #GeneratedValue
If not, is there a way to provide the full entity to be consumed by the #Query?
You can chose not use #Query. A far simpler approach can be using default save method provided by JPA repositories to directly pass an Entity object.

Method in Entity need to load all data from aggregate, how to optimalize this?

I've problem with aggregate which one will increase over time.
One day there will be thousands of records and optimalization gonna be bad.
#Entity
public class Serviceman ... {
#ManyToMany(mappedBy = "servicemanList")
private List<ServiceJob> services = new ArrayList<>();
...
public Optional<ServiceJob> firstServiceJobAfterDate(LocalDateTime dateTime) {
return services.stream().filter(i -> i.getStartDate().isAfter(dateTime))
.min(Comparator.comparing(ServiceJob::getStartDate));
}
}
Method just loading all ServiceJob to get just one of them.
Maybe I should delegate this method into service with native sql.
You have to design small aggregates instead of large ones.
This essay explains in detail how to do it: http://dddcommunity.org/library/vernon_2011/. It explains how to decompose your aggregates to smaller ones so you can manage the complexity.
In your case instead of having an Aggregate consisting of two entities: Serviceman and Servicejob with Serviceman being the aggregate root you can decompose it in two smaller aggregates with single entity. ServiceJob will reference Serviceman by ID and you can use ServicejobRpository to make queries.
In your example you will have ServicejobRpository.firstServiceJobAfterDate(guid servicemanID, DateTime date).
This way if you have a lot of entities and you need to scale, you can store Servicejob entities to another DB Server.
If for some reason Serviceman or Servicejob need references to each other to do their work you can use a Service that will use ServicemanRepository and ServicejobRepository to get both aggregates and pass them to one another so they can do their work.

is it possible to have conditional #Transient field?

Let's say if I have an Entity named person with lots of information including SSN. When other user query this person, I want to show a 'lite' version of person Entity. I could've done so by annotating SSN with #Transient, but that means the person himself would not get this field too. Is it possible to reuse the same Entity but return two different json to client? I'm using spring boot.
First of all #Transient just means that the value, the SSN in your case, won't be persisted to the database.
As for your problem annotations are static and cannot be applied dynamically.
You have 2 Options:
Define a new View class for your user.
Look at JacksonJsonViews

Treat Entity with Id NULL as NEW

To the question "Save Differences with Entity ID" I found the following answer:
"For Entities, Id property cannot be null, so you need to map this class as ValueObject. If so,
Id property is treated as regular property and it not goes to GlobalId of this object."
My question is:
Why can't an entity be treated as NEW if the Id is NULL?
I have an object graph that is fetched from the database, and between two javers commits an entity is added to a list in the graph.
Two commits and in the second commit there is a new entity (Id NULL)
Get the change => exeption because Javers can't create a GlobalId.
I can get arround this by doing EntityManager - persist (creates Id:s), but I would like to avoid doing that. The present code may do a persist later or it just lets the transaction finish.
Because the Id is NULL, the entity is NEW. Would it be possible to generate a unigue temp Id (allow Id = NULL) to be able to create the GlobalId?
In the change list, the entity would be reported as NEW. No need to compare with earlier commits.
You should compare/commit your objects when they are fully initialized so when they have Ids.
An entity without Id can't be handled by JaVers for several reasons:
it can't be compared to other entity/version (diff algorithm is based on GlobalIds)
it can't be queried from JaVersRepository (queries use GlobalIds)
If you are using Hibernate, compare/commit your new objects after Hibernate assigns them Ids from sequences.
Another options:
don't use sequence-generated values as JaVers Id but some business identifiers
if an Entity doesn't have a business identifier you can generate UUID in a constructor and use it as JaVers id (and also database PK if you like)

Resources