I have a problem with Hibernate Enverse (Version 5.2.0-Final).
Context:
I'm auditing some entities with some lazy relations. I have a jsf-page that loads one version of one entity with all relations of that version. That works fine. So now I have a page that shows a revision of the entity with all relations of that revision. On this page I can open a fieldset, that triggers an AJAX. In this request we reattach all relations by calling entityManager.merge(entity) to be able to fetch the lazy relations in this fieldset. (The EntityManager is RequestScoped)
The Problem:
The AJAX is a new request. The server calls entityManager.merge(entity), what enforces creation of a new EntityManager (So a new org.hibernate.internal.SessionImpl is created). On this object hibernate calls SessionImpl.merge(...). But in the method org.hibernate.internal.AbstractSharedSessionContract.createQuery(String) a other SessionImpl object is used, which is already closed in the request before. That enforces an java.lang.IllegalStateException: Session/EntityManager is closed.
In one sentence: Although a new entityManager was created and a merge was called on that new entityManger, Hibernate uses an old Session/EntityManager of the request before.
I debugged the problem and found following:
Debug1: Shows the Stacktrace of the SessionImpl.merge(...) with the session's object id
Debug2: Shows the last method with the correct SessionImpl object (see it's id). This object is not used in next methods.
Debug3: The step after Debug2 does not know the given SessionImpl object. It has it's own SessionImpl object in collection.initializor.versionsReader. This session was created and closed in the request before (on loading the page).
Debug4: Now Hibernate wants to create the query wit the closed SessionImpl
Debug5: This enforces the exception, as the session is closed.
My questions:
Is this a bug of Hibernate?
Why is the given SessionImpl in method org.hibernate.type.CollectionType.getElementIterater(...) not used?
Anyone knows a solution or workaround for this problem?
Tank you very much for any idea. I spent days on this bug.
Why is the Session arg in o.h.type.CollectionType.getElementIterator not used?
The short answer is it isn't required, its simply a backward compatibility concern from 8 years ago.
The long answer is the type-system used to actually deviate some behavior based on whether or not the user had specified the session to operate in EntityMode.MAP or EntityMode.POJO and therefore the types needed to know what mode the session was in; hence why it was passed.
But even back in 2011 when this was changed, the session argument only ever influenced behavior if and only if the session was operating in EntityMode.MAP. In other words, all other modes always routed directly to the underlying collections Collection#iterator() method.
All this aside however, this doesn't have any impact on what you experience in your Debug3 screen-shot.
Is this a bug in Hibernate?
No, based on what I have read, I believe you're mixing concerns.
In Hibernate (no Envers), you can basically do this
// Request 1
request1EntityManager = getEntityManager();
sessionScopeEntity = request1EntityManager.find( MyEntity.class, myEntityId );
// Request 2
request2EntityManager = getEntityManager();
sessionScopeEntity = request2EntityManager.merge( sessionScopeEntity );
for ( SomeCollectionItem Item : sessionScopeEntity.getSomeCollection() ) {
// do things here
}
The above works because you reassociate the entity with the new session which in-turn injects the session into all the uninitialized proxies the entity maintains. But you can also rewrite the above as
// Request 1
request1EntityManager = getEntityManager();
sessionScopeEntity = request1EntityManager.find( MyEntity.class, myEntityId );
sessionScopeEntity.getSomeCollection().size() // initialize collection w/request1Session
// Request 2
request2EntityManager = getEntityManager();
for ( SomeCollectionItem Item : sessionScopeEntity.getSomeCollection() ) {
// do things here
}
The difference is the collection gets initialized with the first session and therefore when you attempt to access it with the second session, the entity doesn't necessarily need a merge because the collection is no longer a proxy but actually populated like a normal fetched collection would be.
The major difference between an entity instance returned by Hibernate and an audited entity instance returned by Envers is that the audited entity instance is NOT a managed persistent entity.
Depending on your scenario, you may decide to only audit a subset of fields on an entity mapping. This is why you cannot nor should not use things like merge with that instance as it could easily lead to unintended side effects with your real data.
If you intend to pass the audited entity instance across sessions, i would highly suggest that you instead consider initializing the collections you need up-front with the first session where you fetched the instance because presently there is no way to re-associate an audited entity instance with a new session.
Related
In 'plain vanilla' MongoDB, methods that do a write operation into the DB, such as .insertOne() or .deleteOne(), will return an InsertOneResult or DeleteResult respectively. These objects will return a boolean value for acknowledged and an id where applicable. This allows us to ensure that the write operation was successful.
However, in Panache, the MongoOperations.class is returning void:
MongoOperations.class
private static void persist(MongoCollection collection, Object entity) {
collection.insertOne(entity);
}
The question then is, how can we retrieve the resulting InsertResult and DeleteObject object?
pom dependency being used
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-mongodb-panache</artifactId>
<version>1.5.0.Final</version>
This is a design choice.
We choose to make MongoDB with Panache as close as possible to Hibernate with Panache so persist() and delete() returns void.
We didn't want to expose MongoDB related API (so not returing InsertOneResult or DeleteResult) neither.
So, your question is about accessing the acknowledged boolean to be sure that the operation has been acknowledged on MongoDB side, and the id in case you create new documents and let MongoDB manage the identifier for you.
Here are some answers:
The id is computed before insertion to the database, you can retrieve it from the entity after having call the persist() operation, it would have been populated by the MongoDB driver.
The default write concern for MongoDB is w:1 meaning that the operation will succeed if the master (or the standalone) node has acknowledged the operation. This means that by default, if the call to persist() didn't throw an exception the operation has been acknowledged. Unless you configure MongoDB with a write concern of w:0, as soon as the persist() operation succeed this means the document has been inserted to the database.
For the delete() operation, the same write concern rules applies. But we didn't check that the document has been deleted (we didn't check deletedCount to be 1), maybe it's an oversigth, and we may have done it. If you think we should, you can open an issue in Quarkus Github repository (this may break backward compatibility so we will need to be careful about this).
I am trying to control concurrent access to same object in spring+jpa configuration.
For Example, I have an entity named A. Now multiple processes updating the same object of A.
I am using versioning field but controlling it but here is the issue:
For example 2 processes reads the same entity (A) having version=1.
Now one process update the entity and version gets incremented.
when 2nd process tries to persist the object, Optimistic lock exception would be thrown.
I am using spring services and repository to access the objects.
Could you please help me here?
What's the problem then? That's how it's supposed to work.
You can catch the JpaOptimisticLockingFailureException and then decide what to do from there.
This, for example, would give a validation error message on a Spring MVC form:
...
if(!bindingResult.hasErrors()) {
try {
fooRepository.save(foo);
} catch (JpaOptimisticLockingFailureException exp){
bindingResult.reject("", "This record was modified by another user. Try refreshing the page.");
}
}
...
What would be the main difference between doing a session.Clear() vs. session.GetSessionImplementation().PersistenceContext.Clear()?
In my case, ideally, I would only clear the cache for a context switch in my code because I want to prevent lazyloading objects from previous context. And also, I wouldn't get the error that I am getting:
NHibernate.LazyInitializationException : Initializing[...#34]-failed to lazily initialize a collection of role: [...], no session or session was closed
session.Clear() clears PersistenceContext and also cancels all pending actions (saves, updates and deletions).
My problem is straightforward. I want to access some data from the database when the application loads on Tomcat. To do something at that point in time I use #PostConstruct (which does its job properly).
However, in that method I make 2 separate connections to the DB: one for bringing a list of entities and another for adding them into a common library. The second step implies some behind-the-scenes queries for resolving some lazy-loading associations. Here is the code snippet:
#Override
#PostConstruct
public void populateLibrary() {
// query for the Book Descriptors - 1st query works!!!
List<BookDescriptor> bookDescriptors= bookDescriptorService.list();
Session session = sessionFactory.openSession();
Transaction transaction = null;
try {
transaction = session.beginTransaction();
// resolving some lazy-loading associations - 2nd query fails!!!
for (BookDescriptor book: bookDescriptors) {
library.addEntry(book);
}
transaction.commit();
} catch (HibernateException e) {
transaction.rollback();
e.printStackTrace();
} finally {
session.close();
}
}
1st query works while the 2nd fails, as I wrote in the comments. The failure gives:
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:86)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:140)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:190)
at com.freightgate.domain.SecurityFiling_$$_javassist_7.getSfSubmissionType(SecurityFiling_$$_javassist_7.java)
at com.freightgate.dao.SecurityFilingTest.test(SecurityFilingTest.java:73)
Which is very odd since I explicitly opened and closed a transaction. However, if I inspect some details of how the 1st query works it seems like behind the scenes the session is bound to AbstractLazyInitializer class.
I resolved my problem by abstracting away the functionality from the for loop into a separate service class that is annotated with #Transactional(readOnly = true). Still I'm puzzled as to why the approch that I posted here fails.
If anyone has some hints, I'd be very happy to hear them.
You load entities in a first session, then close this session, then open a new session, and try to lazy-load collections of the entities. That can't work.
For lazy-loading to work, the entity must be attached to an open session. Just opening another session doesn't make any entity you have loaded before attached to this new session. In the meantime, some other transaction could have radically changed the database, the entity could not exist anymore...
The best solution is what you have done. Encapsulate evrything into a single transactional service. You could also have open the transaction before calling the first service, but why handle transactions programmatically, since Spring does it for you declaratively?
I've got a question about working with entities which were received from db.
Currently I've a lot of operations, where I need to get entities from db, and pass them to another service. Simplified version of such code are is like this:
List<Entity> list;
using(var session = SessionFactory.OpenSession())
{
list = Session.QueryOver<Entity>.Future().ToList();
}
So now I don't know, if list of objects isn't disposed for a long time, will it cause memory lear accordint to stored sessions. Does nhibernate sessions exist while exist objects which were received during the session?
Update:
Found some session setting Session.ActiveEntityMode - POCO, does it solves my problem?
the session is disposed as soon as the using ends. All entities loaded are still valid except not initialized lazyloaded collections/references/properties.
Also the Future in Session.QueryOver<Entity>.Future().ToList(); is a noop when there are no other operations befor which have Future/futurevalue on them.