Javers ENTITY_INSTANCE_WITH_NULL_ID when using 2 databases - mongodb

I have this exception "ENTITY_INSTANCE_WITH_NULL_ID" when I store data in Postgres (using JPA Lazy Load) and I store javers in MongoDB
Spring Boot: 1.4.0.RELEASE
Sprig Data JPA: 1.4.0.RELEASE
Javers: 2.3.0
I debugged and saw that ID is null if object is a lazy object:
org.javers.core.metamodel.type.EntityType:88 "Object cdoId = getIdProperty().get(instance);"

When you commit an object to JaVers, its previous version is loaded from JaversRepository and compared with the current version (that version you have just passed to commit() method).
In this case JaVers finds the previous version using GlobalId query so TypeName + entity ID.
That's why ID can't be null for Entities.
There are two possibilities:
If null ID is normal in this class (according to your domain model) you should map it as ValueObject in JaVers.
If you are using Hibernate, there is common problem with lazy loading proxies. For certain queries, Hibernate doesn't return your real domain objects but dynamic proxy objects which are essentially empty (hence null ID). This technique maybe looks smart but makes your objects garbage until they are initialized by Hibernate.
JaVers provides HibernateUnproxyObjectAccessHook which does the cleaning: initializing and un-proxying of your domain objects.
JaversBuilder.javers().withObjectAccessHook(
new HibernateUnproxyObjectAccessHook()).build()
This hook is enabled by default in javers-spring-boot-starter-sql but not in javers-spring-boot-starter-mongo. If you are using Mongo starter, create a JaVers bean on your own, with the hook enabled, see JaversMongoAutoConfiguration.

I solved the problem by making my own access hook and added to the to the Javers with
.withObjectAccessHook(new EntityAccessHook()).build()
public class EntityAccessHook<T> extends HibernateUnproxyObjectAccessHook<T> {
#Override
public Optional<ObjectAccessProxy<T>> createAccessor(T entity) {
Optional<ObjectAccessProxy<T>> accessProxy = super.createAccessor(entity);
if (accessProxy.isEmpty() && entity instanceof AbstractUuidEntity) {
return fromEntityInitializer((AbstractUuidEntity) entity);
}
return accessProxy;
}
private Optional<ObjectAccessProxy<T>> fromEntityInitializer(
AbstractUuidEntity abstractUuidEntity) {
return Optional.of(
new ObjectAccessProxy(
() -> abstractUuidEntity,
abstractUuidEntity.getClass(),
abstractUuidEntity.getId() == null ? UUID.randomUUID() : abstractUuidEntity.getId()));
}
}

Related

How to inject spring aop advice for MongoDb call?

I am new to Spring Aop, but I have case to implement AOP advice for a mongo db call(monog db update). I am trying in different way but getting 'Point cut not well formed' error or 'warning no match for this type name: arg string [Xlint:invalidAbsoluteTypeName]'(even if I give absolute name of the argument). Anyone can help on this as how to inject advice for mongo db update call?
#Aspect
#Component
public class DBStatsLoggerAspect {
private static final Logger log = LoggerFactory
.getLogger(DBStatsLoggerAspect.class);
private static final Document reqStatsCmdBson = new Document(
"getLastRequestStatistics", 1);
private DbCallback<Document> requestStatsDbCallback = new DbCallback<Document>() {
#Override
public Document doInDB(MongoDatabase db) throws MongoException,
DataAccessException {
return db.runCommand(reqStatsCmdBson);
}
};
#After("execution( public * com.mongodb.client.MongoCollection.*(..)) && args(org.bson.conversions.Bson.filter,..)")
public void requestStatsLoggerAdvice(JoinPoint joinPoint) {
MongoTemplate mongoTemplate = (MongoTemplate) joinPoint.getTarget();
log.info(mongoTemplate.execute(requestStatsDbCallback).toJson());
}
}
Actual db call method where I need to inject advice:(filter, updatePart all are org.bson.conversions.Bson data type) and here 'collection' is com.mongodb.client.MongoCollection.collection
Document result = collection.findOneAndUpdate(filter, updatePart, new FindOneAndUpdateOptions().upsert(false));
I am not a Spring or MongoDB user, just an AOP expert. But from what I see I am wondering:
You are intercepting execution(public * com.mongodb.client.MongoCollection.*(..)), so joinPoint.getTarget() is a MongoCollection type. Why do you think you can cast it to MongoTemplate? That would only work if your MongoCollection happened to be a MongoTemplate subclass. To me this looks like a bug.
Class MongoCollection is not a Spring component but a third-party class. Spring AOP can only intercept Spring component calls by means of creating dynamic proxies for those components and adding aspect interceptors to said proxies. so no matter how correct or incorrect your pointcut, it should never trigger.
What you can do instead is switch from Spring AOP to full-blown AspectJ. The standard way to do this is to activate AspectJ load-time weaving (LTW).

When does a object become detached in Spring Data JPA?

I have a service that is using a Spring Data Repository to retrieve an object. The service is NOT marked as transactional, thus I assumed that any object returned from the repository would be detached since the transaction would be scoped to the repository. However, it seems as though the object is NOT detached which surprises me. Here is a code sample:
public class MyService {
#Autowired
private MyRepository repo;
#Autowired
private EntityManager entityManager;
/**
* Updates a persisted entity based on the given DTO representation.
*/
public MyObjectDto update(MyObjectDto dto) {
MyObjectJpa existing = repo.findOne(dto.getId());
entityManager.isJoinedToTransaction(); // returns false so no transaction should be active in this scope I would assume
entityManager.contains(existing); // this returns true, but I don't know why
if (existing != null) {
MyObjectJpa updated = toJpa(dto);
// calling repo.save(..) modifies the state of 'existing' object which surpised me
MyObjectDto updatedDto = toDto(repo.save(updated));
return updatedDto;
}
return null;
}
Why is the 'existing' object in my code sample still managed by the entityManager even though my service method is not marked as transactional (i.e. not using the #Transactional annotation from Spring)? Thanks.
In Spring Boot parameter spring.jpa.open-in-view is set to true by default.
I think you should turn it to false.
From java-doc:
Register OpenEntityManagerInViewInterceptor. Binds a JPA EntityManager to the thread for the entire processing of the request.

Delete loaded and unloaded objects by ID in EntityFrameworkCore

I have a method that receives an IEnumerable<Guid> of IDs to objects I want to delete. One suggested method is as follows
foreach(Guid id in ids)
{
var tempInstance = new MyEntity { Id = id };
DataContext.Attach(tempInstance); // Exception here
DataContext.Remove(tempInstance);
}
This works fine if the objects aren't already loaded into memory. But my problem is that when they are already loaded then the Attach method throws an InvalidOperationException - The instance of entity type 'MyEntity' cannot be tracked because another instance with the key value 'Id:...' is already being tracked. The same happens if I use DataContext.Remove without calling Attach.
foreach(Guid id in ids)
{
var tempInstance = new MyEntity { Id = id };
DataContext.Remove(tempInstance); // Exception here
}
I don't want to use DataContext.Find to grab the instance of an already loaded object because that will load the object into memory if it isn't already loaded.
I cannot use DataContext.ChangeTracker to find already loaded objects because only objects with modified state appear in there and my objects might be loaded and unmodified.
The following approach throws the same InvalidOperationException when setting EntityEntry.State, even when I override GetHashCode and Equals on MyEntity to ensure dictionary lookups see them as the same object.
foreach(Guid id in ids)
{
var tempInstance = new MyEntity { Id = id };
EntityEntry entry = DataContext.Entry(tempInstance);
entry.State == EntityState.Deleted; // Exception here
}
The only way so far I have found that I can achieve deleting objects by ID without knowing if the object is the following:
foreach(Guid id in ids)
{
var tempInstance = new MyEntity { Id = id };
try
{
DataContext.Attach(tempInstance); // Exception here
}
catch (InvalidOperationException)
{
}
DataContext.Remove(tempInstance);
}
It's odd that I am able to call DataContext.Remove(tempInstance) without error after experiencing an exception trying to Attach it, but at this point it does work without an exception and also deletes the correct rows from the database when DataContext.SaveChanges is executed.
I don't like catching the exception. Is there a "good" way of achieving what I want?
Note: If the class has a self-reference then you need to load the objects into memory so EntityFrameworkCore can determine in which order to delete the objects.
Strangely, although this is a quite common exception in EF6 and EF Core, neither of them expose publicly a method for programmatically detecting the already tracked entity instance with the same key. Note that overriding GetHashCode and Equals doesn't help since EF is using reference equality for tracking entity instances.
Of course it can be obtained from the DbSet<T>.Local property, but it would not be as efficient as the internal EF mechanism used by Find and the methods throwing the aforementioned exception. All we need is the first part of the Find method and returning null when not found instead of loading from the database.
Luckily, for EF Core the method that we need can be implemented relatively easily by using some of the EF Core internals (under the standard This API supports the Entity Framework Core infrastructure and is not intended to be used directly from your code. This API may change or be removed in future releases. policy). Here is the sample implementation, tested on EF Core 2.0.1:
using Microsoft.EntityFrameworkCore.Internal;
namespace Microsoft.EntityFrameworkCore
{
public static partial class CustomExtensions
{
public static TEntity FindTracked<TEntity>(this DbContext context, params object[] keyValues)
where TEntity : class
{
var entityType = context.Model.FindEntityType(typeof(TEntity));
var key = entityType.FindPrimaryKey();
var stateManager = context.GetDependencies().StateManager;
var entry = stateManager.TryGetEntry(key, keyValues);
return entry?.Entity as TEntity;
}
}
}
Now you can use simply:
foreach (var id in ids)
DataContext.Remove(DataContext.FindTracked<MyEntity>(id) ?? new MyEntity { Id = id }));
or
DataContext.RemoveRange(ids.Select(id =>
DataContext.FindTracked<MyEntity>(id) ?? new MyEntity { Id = id }));

How to find all managed attached objects in EntityManager (JPA)

Is there a way to get all objects which are currently attached in the entity manager?
I want to write some monitoring code which will report the number of attached objects and their classes.
Meaning finding all objects which were loaded by previous queries and find operations into the entity manager.
I'm using EclipseLink, so a specific solution is good too.
EclipseLink's JPA interface pretty much wraps its native code such that an EntityManager uses a UnitOfWork session underneath (and the EMF wraps a ServerSession). You need to get at the UnitOfWork if you want to see what entities it is managing.
If using JPA 2.0, you can use the EntityManager unwrap method:
UnitOfWork uow = em.unwrap(UnitOfWork.class);
otherwise, use some casting
UnitOfWork uow = ((EntityManagerImpl)em).getUnitOfWork();
From there, the UnitOfWork has a list of all registered (aka managed) entities. You can use the UOW to directly log what it has using the printRegisteredObjects() method, or obtain it yourself using getCloneMapping().keySet().
You can also see deleted objects by using hasDeletedObjects() and then getDeletedObjects().keySet() if there are any, as and the same for new objects using hasNewObjectsInParentOriginalToClone() and getNewObjectsCloneToOriginal().keySet()
you can use JPA in a lot of ways i am still unaware of, and there is a lot going on under the hood in eclipselink that i still do not fully understand, but it looks like it is possible to see into the persistence context. USE THIS CODE AT YOUR OWN RISK. it is only meant to give you a hint that it is possible to inspect the context. (whether the code is right or wrong i'm posting it because it would have helped me when i was trying to decide whether to use eclipselink. there doesn't seem to be much in the way of documentation about how to do this properly.)
public void saveChanges() {
Date now = new Date();
JpaEntityManager jem = em.unwrap(JpaEntityManager.class);
UnitOfWorkImpl uow = jem.unwrap(UnitOfWorkImpl.class);
// inserts
for (Object entity : uow.getNewObjectsCloneToOriginal().keySet()) {
if (entity instanceof IAuditedEntity) {
IAuditedEntity auditedEntity = (IAuditedEntity) entity;
auditedEntity.setAuditedUserId(this.userId);
auditedEntity.setAuditedAt(now);
auditedEntity.setCreatedAt(now);
}
}
// updates
UnitOfWorkChangeSet uowChangeSet = (UnitOfWorkChangeSet) uow.getUnitOfWorkChangeSet();
if (uowChangeSet != null) {
List<IAuditedEntity> toUpdate = new ArrayList<>();
for(Entry<Object, ObjectChangeSet> entry : uowChangeSet.getCloneToObjectChangeSet().entrySet()) {
if (entry.getValue().hasChanges()) {
if (entry.getKey() instanceof IAuditedEntity) {
toUpdate.add((IAuditedEntity) entry.getKey());
}
}
}
for (IAuditedEntity auditedEntity : toUpdate) {
auditedEntity.setAuditedUserId(this.userId);
auditedEntity.setAuditedAt(now);
}
}
// deletions
Project jpaProject = uow.getProject();
boolean anyAuditedDeletions = false;
for (Object entity : uow.getDeletedObjects().keySet()) {
if (entity instanceof IAuditedEntity) {
anyAuditedDeletions = true;
DeletedEntity deletion = new DeletedEntity();
deletion.setTableName(jpaProject.getClassDescriptor(entity.getClass()).getTableName());
deletion.setEntityId(((IAuditedEntity) entity).getId());
deletion.setAuditedUserId(this.userId);
em.persist(deletion);
}
}
}
You can achieve this by inspecting the entities on MetaModel which can be obtained from any EntityManager.
Example usage:
EntityManager em = // get your EM however...
for(EntityType<?> entityType : em.getMetaModel().getEntities())
{
Class<?> managedClass = entityType.getBindableJavaType();
System.out.println("Managing type: " + managedClass.getCanonicalName());
}
This example will print out all of the class types being managed by the EntityManager. To get all of the actual objects being managed, simply query all objects of that type on the EntityManager.
Update:
As of JPA 2.0 you can cache results that will be managed by javax.persistence.Cache. However, with plain JPA there is no way to actually retrieve the objects stored in the cache, the best you can do is check if a certain object is in the Cache via Cache.contains(Class cls, Object pk):
em.getEntityManagerFactory().getCache().contains(MyData.class, somePK);
However, EclipseLink extends Cache with JpaCache. You can use this to actually get the object from the cache via JpaCache.getObject(Class cls, Object id). This doesn't return a collection or anything, but it's the next best thing.
Unfortunately, if you want to actually access objects in the cache, you will need to manage this yourself.
I dont see such an option in the EntityManager interface. There is only a contains(Object entity) method but you need to pass the conrete objects and they are the checked for existentnce in the PersistenceContext. Also looking at the PersistenceContext interface i dont see such an option.

PersistenceUnitInfo implementation in EclipseLink

EntityManagerFactory can be created without a persistence unit xml using
org.eclipse.persistence.jpa.PersistenceProvider {
public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info,
java.util.Map properties)
}
but what is the implementation class of javax.persistence.spi.PersistenceUnitInfo in eclipselink
I am struggling on this problem too. I think that a PersistenceUnitInfo must be provided by the container(i.e. Application Server). It means that Eclipselink do not create one itself. If you are using Spring ORM, it uses a DefaultPersistenceUnitManager and call its obtainPersistenceUnitInfo(String unitName) method to get a instance of PersistenceUnitInfo. The unitName must be defined in persistence.xml. This means that you still needs an xml file.
By digging into the source code of Spring ORM, I found that Spring provides several implementations of PersistenceUnitInfo. In fact they are generally a Java Bean. You may be interested in SmartPersistenceInfo, MutablePersistenceInfo and SpringPersistenceUnitInfo. View them on Github.
EDIT:
I found the implementation of Eclipselink: It's SEPersistenceUnitInfo in org.eclipse.persistence.internal.jpa.deployment. Also found the method that reads every persistence unit in the configuration xml file.
public static Set<SEPersistenceUnitInfo> getPersistenceUnits(ClassLoader loader, Map m, List<URL> jarFileUrls) {
String descriptorPath = (String) m.get(PersistenceUnitProperties.ECLIPSELINK_PERSISTENCE_XML);
if(descriptorPath == null) {
descriptorPath = System.getProperty(PersistenceUnitProperties.ECLIPSELINK_PERSISTENCE_XML, PersistenceUnitProperties.ECLIPSELINK_PERSISTENCE_XML_DEFAULT);
}
Set<Archive> archives = findPersistenceArchives(loader, descriptorPath, jarFileUrls);
Set<SEPersistenceUnitInfo> puInfos = new HashSet();
try {
for(Archive archive : archives) {
List<SEPersistenceUnitInfo> puInfosFromArchive = getPersistenceUnits(archive, loader);
puInfos.addAll(puInfosFromArchive);
}
} finally {
for(Archive archive : archives) {
archive.close();
}
}
return puInfos;
}
Java EE platform spec 6 says : the container is responsible for finding persistence.xml condensing the information into PersistenceUnitInfo and supplying that with a call to createContainerEntityManagerFactory.
PersistenceUnitInfo is defined by the Spec, refer to the JPA spec code or JavaDoc for its implementation.
http://www.eclipse.org/eclipselink/api/2.5/javax/persistence/spi/PersistenceUnitInfo.html