Reputation: 725
I do have a problem, and I can't figure out what it happens... GWT beginner, working on a personal project.
Environment:
maven project with two modules
one module is the 'model', and has Hibernate, HSQLDB and Spring dependencies. HSQLDB runs embedded, in memory, configured from spring applicationContext.xml
the other module is the 'web' and has all GWT dependencies
The application is built using some Spring Roo generated code as basis, later modified and extended.
The issue is that, when editing some entity fields and pressing save, nothing happens. No problem when creating a new entity instance, only on edit changing some field and pressing 'save' basically overrides the new values. So I started to thoroughly debug the client code, enabled hibernate and Spring detailed logging, but still ... nothing.
Then I made a surprising (for me) discovery. Inspecting the GWT response payload, I have seen this:
{"S":[false],"O": [{"T":"663_uruC_g7F5h5IXBGvTP3BBKM=","V":"MS4w","S":"IjMi","O":"UPDATE"}],"I":[{"F":true,"M":"Server Error: org.hibernate.PersistentObjectException: detached entity passed to persist: com.myvdm.server.domain.Document; nested exception is javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.myvdm.server.domain.Document"}]}
Aha, detached entity passed to persist !!! Please note that the gwt client code uses this snippet to call the service:
requestContext.persist().using(proxy);
Arguably this could trigger the exception, and calling merge() could solve the problem, however, read on, to question 3...
Three question arise now:
Thanks a lot,
Avaiting for some opinions/suggestions.
EDITED AFTER T. BROYER's RESPONSE::
Hi Thomas, thanks for the response.
I have a custom class that implements RequestTransport and implements send(). This is how I collected the response payload. Implementation follows::
public void send(String payload, final TransportReceiver receiver) {
TransportReceiver myReceiver = new TransportReceiver() {
@Override
public void onTransportSuccess(String payload) {
try {
receiver.onTransportSuccess(payload);
} finally {
eventBus.fireEvent(new RequestEvent(RequestEvent.State.RECEIVED));
}
}
@Override
public void onTransportFailure(ServerFailure failure) {
try {
receiver.onTransportFailure(failure);
} finally {
eventBus.fireEvent(new RequestEvent(RequestEvent.State.RECEIVED));
}
}
};
try {
wrapped.send(payload, myReceiver);
} finally {
eventBus.fireEvent(new RequestEvent(RequestEvent.State.SENT));
}
}
Here's the code that is executed when 'save' button is clicked in edit mode:
RequestContext requestContext = editorDriver.flush();
if (editorDriver.hasErrors()) {
return;
}
requestContext.fire(new Receiver<Void>() {
@Override
public void onFailure(ServerFailure error) {
if (editorDriver != null) {
setWaiting(false);
super.onFailure(error);
}
}
@Override
public void onSuccess(Void ignore) {
if (editorDriver != null) {
editorDriver = null;
exit(true);
}
}
@Override
public void onConstraintViolation(Set<ConstraintViolation<?>> errors) {
if (editorDriver != null) {
setWaiting(false);
editorDriver.setConstraintViolations(errors);
}
}
});
Based on what you said, onSuccess() should be called, and it's called
So how do I isolate exactly the code that creates the problem? I have this method that creates a fresh request context in order to persist the object
@Override
protected RequestContext createSaveRequestContextFor(DocumentProxy proxy) {
DocumentRequestContext request = requests.documentRequestContext();
request.persist().using(proxy);
return request;
}
and this is how it is called::
editorDriver.edit(getProxy(), createSaveRequestContextFor(getProxy()));
As for the Spring problem, you are saying that, between two subsequent requests, the find() and persist(), the JPA entityManager should not be closed. I am still investigating this, but after I press the edit button, I see the message 'org.springframework.orm.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager' and that is not right, maybe the @Transactional annotation is not applied...
Upvotes: 0
Views: 481
Reputation: 17489
Regarding the open session in view pattern Thomas mentioned, just add this filter definitions to your web.xml
to turn on the pattern in your Spring application:
<filter>
<filter-name>
Spring OpenEntityManagerInViewFilter
</filter-name>
<filter-class>
org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Upvotes: 1
Reputation: 64541
Why isn't this somehow sent to the client as an error/exception?
It is. The "S": [false]
indicates the first (and only) method invocation (remember, a RequestContext
is a batch!) has failed. The onFailure
method of the invocation's Receiver
will be called.
The "F": true
of the ServerFailure
then says it's a fatal error, so the default implementation of Receiver#onFailure
would throw a RuntimeException
. However, as you do not use a Receiver
at all, nothing happens and the error is silently ignored.
Note that the batch request in itself has succeeded, so the global Receiver
(the one you'd pass to RequestContext#fire
) would have its onSuccess
method called.
Also note that Request#fire(Receiver)
is a shorthand for Request#to(Receiver)
followed by RequestContext#fire()
(with no argument).
Why isn't this logged by Hibernate?
This I don't know, sorry.
How come the Spring Roo generated code (as I said, used as basis) works without manifesting this problem?
OK, let's explore the underlying reason of the exception: the entity is loaded by your Locator
(or the entity class's findXxx
static method) and then the persist
method is called on the instance. If you do not use the same JPA EntityManager
/ Hibernate session in the find
and persist
methods, then you'll have the issue.
Request Factory expects you to use the open session in view pattern to overcome this. I unfortunately do not know what kind of code Spring Roo generates.
Upvotes: 2