jpa conversational persistence
This commit is contained in:
@@ -33,5 +33,7 @@
|
||||
<classpathentry kind="lib" path="lib/buildtime/jta.jar"/>
|
||||
<classpathentry kind="lib" path="lib/test/spring-jdbc.jar"/>
|
||||
<classpathentry kind="lib" path="lib/test/spring-mock.jar" sourcepath="/spring/mock"/>
|
||||
<classpathentry kind="lib" path="lib/buildtime/persistence-api.jar"/>
|
||||
<classpathentry kind="lib" path="lib/buildtime/spring-jpa.jar"/>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
|
||||
@@ -55,10 +55,15 @@
|
||||
<dependency org="concurrent" name="concurrent" rev="1.3.4" conf="buildtime->default" />
|
||||
<dependency org="javax.servlet" name="servlet-api" rev="2.4" conf="buildtime->default" />
|
||||
<dependency org="javax.portlet" name="portlet-api" rev="1.0" conf="buildtime->default" />
|
||||
<dependency org="org.hibernate" name="hibernate" rev="3.2.3.ga" conf="buildtime->default" />
|
||||
<dependency org="org.hibernate" name="hibernate" rev="3.2.4.ga" conf="buildtime->default" />
|
||||
|
||||
<dependency org="org.springframework" name="spring-dao" rev="2.0.6" conf="buildtime->default" />
|
||||
<dependency org="org.springframework" name="spring-hibernate3" rev="2.0.6" conf="buildtime->default" />
|
||||
|
||||
|
||||
<dependency org="javax.persistence" name="persistence-api" rev="1.0b" conf="buildtime->default"/>
|
||||
<dependency org="toplink.essentials" name="toplink-essentials" rev="2.0-56" conf="buildtime->default" />
|
||||
<dependency org="org.springframework" name="spring-jpa" rev="2.0.6" conf="buildtime->default" />
|
||||
|
||||
<!-- test time only dependencies -->
|
||||
<dependency org="hsqldb" name="hsqldb" rev="1.8.0.7" conf="test->default" />
|
||||
<dependency org="log4j" name="log4j" rev="1.2.14" conf="test->default" />
|
||||
|
||||
@@ -23,7 +23,6 @@ import org.springframework.orm.hibernate3.SessionHolder;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.webflow.core.collection.AttributeMap;
|
||||
import org.springframework.webflow.execution.FlowExecution;
|
||||
import org.springframework.webflow.execution.FlowExecutionException;
|
||||
import org.springframework.webflow.execution.FlowExecutionListener;
|
||||
import org.springframework.webflow.execution.FlowExecutionListenerAdapter;
|
||||
@@ -32,134 +31,93 @@ import org.springframework.webflow.execution.RequestContext;
|
||||
import org.springframework.webflow.execution.ViewSelection;
|
||||
|
||||
/**
|
||||
* A {@link FlowExecutionListener} that implements the Hibernate
|
||||
* Session-per-Conversation pattern as described in Java Persistence with
|
||||
* Hibernate (chapter 11).
|
||||
* A {@link FlowExecutionListener} that implements the Hibernate Session-per-Conversation pattern as described in Java
|
||||
* Persistence with Hibernate (chapter 11).
|
||||
* <p>
|
||||
* This implementation uses raw Hibernate APIs and binds the current session to
|
||||
* the thread-local location identified by Spring's
|
||||
* <code>HibernateTransactionManager</code>.
|
||||
* This implementation uses raw Hibernate APIs and binds the current session to the thread-local location identified by
|
||||
* Spring's <code>HibernateTransactionManager</code>.
|
||||
* <p>
|
||||
* This listener assumes that you are accessing Hibernate via Spring support
|
||||
* such as HibernateTemplate or the LocalSessionFactoryBean. If not, Hibernate
|
||||
* data access code will not participate in the proper transaction.
|
||||
* This listener assumes that you are accessing Hibernate via Spring support such as HibernateTemplate or the
|
||||
* LocalSessionFactoryBean. If not, Hibernate data access code will not participate in the proper transaction.
|
||||
* <p>
|
||||
* Note that when accessing service layer methods with Spring managed
|
||||
* transactions, those transaction should have {@link Propagation#REQUIRED}
|
||||
* semantics. Anything else defeats the purpose of holding the transaction open
|
||||
* until the end of the session.
|
||||
* Note that when accessing service layer methods with Spring managed transactions, those transaction should have
|
||||
* {@link Propagation#REQUIRED} semantics. Anything else defeats the purpose of holding the transaction open until the
|
||||
* end of the session.
|
||||
*
|
||||
* @author Ben Hale
|
||||
* @since 1.1
|
||||
*/
|
||||
public class HibernateSessionPerConversationListener extends FlowExecutionListenerAdapter {
|
||||
|
||||
private static final String HIBERNATE_SESSION = "hibernate.session";
|
||||
private static final String HIBERNATE_SESSION_ATTRIBUTE = "hibernate.session";
|
||||
|
||||
private SessionFactory sessionFactory;
|
||||
private SessionFactory sessionFactory;
|
||||
|
||||
/**
|
||||
* Create a new Session-per-Conversation listener using giving Hibernate session
|
||||
* factory.
|
||||
* @param sessionFactory the session factory to use
|
||||
*/
|
||||
public HibernateSessionPerConversationListener(SessionFactory sessionFactory) {
|
||||
this.sessionFactory = sessionFactory;
|
||||
}
|
||||
/**
|
||||
* Create a new Session-per-Conversation listener using giving Hibernate session factory.
|
||||
* @param sessionFactory the session factory to use
|
||||
*/
|
||||
public HibernateSessionPerConversationListener(SessionFactory sessionFactory) {
|
||||
this.sessionFactory = sessionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* When a {@link FlowSession} is created a Hibernate <code>Session</code>
|
||||
* is opened and put into MANUAL flush mode. From there it is bound to the
|
||||
* conversation scope of this {@link FlowSession}. Since
|
||||
* {@link #resumed(RequestContext)} will not be called on start of the
|
||||
* session, this method also binds the session to the thread-local location.
|
||||
* This behavior will only be exhibited on the root flow in a {@link FlowExecution}.
|
||||
*/
|
||||
public void sessionCreated(RequestContext context, FlowSession session) {
|
||||
if (session.isRoot()) {
|
||||
Session hibSession = createSession(context);
|
||||
bindSession(hibSession);
|
||||
}
|
||||
public void sessionCreated(RequestContext context, FlowSession session) {
|
||||
if (session.isRoot()) {
|
||||
Session hibernateSession = createSession(context);
|
||||
context.getConversationScope().put(HIBERNATE_SESSION_ATTRIBUTE, hibernateSession);
|
||||
bind(hibernateSession, context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When a {@link FlowExecution} is resumed, the conversationally scoped
|
||||
* <code>Session</code> is bound to the thread-local location and a
|
||||
* transaction is opened.
|
||||
*/
|
||||
public void resumed(RequestContext context) {
|
||||
Session hibSession = getHibernateSession(context);
|
||||
bindSession(hibSession);
|
||||
}
|
||||
public void resumed(RequestContext context) {
|
||||
Session hibSession = getHibernateSession(context);
|
||||
bind(hibSession, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* When a {@link FlowExecution} is paused the conversationally scoped
|
||||
* <code>Session</code> is unbound from the thread-local location and the
|
||||
* transaction is committed. The transaction that is closed is a Hibernate
|
||||
* transaction and with a FlushMode of MANUAL will not flush anything to the
|
||||
* database.
|
||||
*/
|
||||
public void paused(RequestContext context, ViewSelection selectedView) {
|
||||
Session hibSession = getHibernateSession(context);
|
||||
unBindSession(hibSession);
|
||||
}
|
||||
public void paused(RequestContext context, ViewSelection selectedView) {
|
||||
Session session = getHibernateSession(context);
|
||||
unbind(session, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* When a {@link FlowSession} is destroyed all changes are flushed to the
|
||||
* database, the current transaction committed, and the Hibernate
|
||||
* <code>Session</code> is closed. Since
|
||||
* {@link #paused(RequestContext, ViewSelection)} will not be called on the
|
||||
* ending of this session, this method also unbinds the session from the
|
||||
* thread-local location. This behavior will only be exhibited on the root
|
||||
* flow in a {@link FlowExecution}.
|
||||
*/
|
||||
public void sessionEnded(RequestContext context, FlowSession session, AttributeMap output) {
|
||||
if (session.isRoot()) {
|
||||
Session hibSession = (Session) context.getConversationScope().remove(HIBERNATE_SESSION);
|
||||
hibSession.flush();
|
||||
unBindSession(hibSession);
|
||||
destroySession(hibSession);
|
||||
}
|
||||
public void sessionEnded(RequestContext context, FlowSession session, AttributeMap output) {
|
||||
if (session.isRoot()) {
|
||||
Session hibernateSession = (Session) context.getConversationScope().remove(HIBERNATE_SESSION_ATTRIBUTE);
|
||||
hibernateSession.flush();
|
||||
unbind(hibernateSession, context);
|
||||
hibernateSession.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* When an exception is thrown from a {@link FlowExecution}, the
|
||||
* conversationally scoped <code>Session</code> is unbound from the
|
||||
* thread-local location and the transaction is committed. The
|
||||
* transaction that is closed is a Hibernate transaction and with a
|
||||
* FlushMode of MANUAL will not flush anything to the database.
|
||||
*/
|
||||
public void exceptionThrown(RequestContext context, FlowExecutionException exception) {
|
||||
Session hibSession = getHibernateSession(context);
|
||||
unBindSession(hibSession);
|
||||
}
|
||||
|
||||
// internal helpers
|
||||
}
|
||||
|
||||
private Session createSession(RequestContext context) {
|
||||
Session hibSession = sessionFactory.openSession();
|
||||
hibSession.setFlushMode(FlushMode.MANUAL);
|
||||
context.getConversationScope().put(HIBERNATE_SESSION, hibSession);
|
||||
return hibSession;
|
||||
}
|
||||
public void exceptionThrown(RequestContext context, FlowExecutionException exception) {
|
||||
Session session = getHibernateSession(context);
|
||||
unbind(session, context);
|
||||
}
|
||||
|
||||
private void destroySession(Session hibSession) {
|
||||
hibSession.close();
|
||||
}
|
||||
// internal helpers
|
||||
|
||||
private Session getHibernateSession(RequestContext context) {
|
||||
return (Session) context.getConversationScope().get(HIBERNATE_SESSION);
|
||||
}
|
||||
private Session createSession(RequestContext context) {
|
||||
Session session = sessionFactory.openSession();
|
||||
session.setFlushMode(FlushMode.MANUAL);
|
||||
return session;
|
||||
}
|
||||
|
||||
private void bindSession(Session hibSession) {
|
||||
Transaction hibTx = hibSession.beginTransaction();
|
||||
SessionHolder sessionHolder = new SessionHolder(hibSession);
|
||||
sessionHolder.setTransaction(hibTx);
|
||||
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
|
||||
}
|
||||
private Session getHibernateSession(RequestContext context) {
|
||||
return (Session) context.getConversationScope().get(HIBERNATE_SESSION_ATTRIBUTE);
|
||||
}
|
||||
|
||||
private void unBindSession(Session hibSession) {
|
||||
hibSession.getTransaction().commit();
|
||||
TransactionSynchronizationManager.unbindResource(sessionFactory);
|
||||
private void bind(Session hibSession, RequestContext context) {
|
||||
SessionHolder sessionHolder = new SessionHolder(hibSession);
|
||||
if (context.getActiveFlow().getAttributes().getBoolean("transactional").booleanValue() == true) {
|
||||
Transaction tx = hibSession.beginTransaction();
|
||||
sessionHolder.setTransaction(tx);
|
||||
}
|
||||
}
|
||||
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
|
||||
}
|
||||
|
||||
private void unbind(Session hibSession, RequestContext context) {
|
||||
if (context.getActiveFlow().getAttributes().getBoolean("transactional").booleanValue() == true) {
|
||||
hibSession.getTransaction().commit();
|
||||
}
|
||||
TransactionSynchronizationManager.unbindResource(sessionFactory);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2004-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.webflow.support.persistence;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
|
||||
import org.springframework.orm.jpa.EntityManagerHolder;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.webflow.core.collection.AttributeMap;
|
||||
import org.springframework.webflow.execution.FlowExecutionException;
|
||||
import org.springframework.webflow.execution.FlowExecutionListener;
|
||||
import org.springframework.webflow.execution.FlowExecutionListenerAdapter;
|
||||
import org.springframework.webflow.execution.FlowSession;
|
||||
import org.springframework.webflow.execution.RequestContext;
|
||||
import org.springframework.webflow.execution.ViewSelection;
|
||||
|
||||
/**
|
||||
* A {@link FlowExecutionListener} that implements the Session-per-Conversation using the standard Java Persistence API
|
||||
* (JPA).
|
||||
* <p>
|
||||
* This implementation uses standard JPA APIs. The general pattern is as follows:
|
||||
* <ul>
|
||||
* <li>When a flow execution starts, create a new JPA persistence context and bind it to conversation scope.
|
||||
* <li>Before processing a flow execution request, expose the conversationally-bound persistence context as the
|
||||
* "current" persistence context for the current thread.
|
||||
* <li>When an existing flow pauses, unbind the persistence context from the current thread.
|
||||
* <li>When an existing flow ends, unbind persistence context and and close it.
|
||||
* </ul>
|
||||
*
|
||||
* The general data access pattern implemented here is:
|
||||
* <ul>
|
||||
* <li> Create a new persistence context when a new conversation (e.g. edit session) starts
|
||||
* <li> Load some objects non-transactionally using this persistence context
|
||||
* <li> Perform edits to those objects over a series of conversational requests
|
||||
* <li> On successful conversation completion, commit and flush those edits to the database
|
||||
* </ul>
|
||||
*
|
||||
* Note: care should be taken to ensure at the service-layer that all data access in a conversation occurs
|
||||
* non-transactionally until the final "commit" request to ensure isolation of intermediate object changes made during
|
||||
* the course of the conversation. This care should be taken because, by default, JPA will always flush upon transaction
|
||||
* commit, resulting in changes in the object model being synchronized with the database at that time. You would
|
||||
* generally not want such intermediate flushing to happen, as the nature of a conversation implies a transient resource
|
||||
* that can be canceled.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @since 1.1
|
||||
*/
|
||||
public class JpaSessionPerConversationListener extends FlowExecutionListenerAdapter {
|
||||
|
||||
private static final String ENTITY_MANAGER_ATTRIBUTE = "jpa.entityManager";
|
||||
|
||||
private EntityManagerFactory entityManagerFactory;
|
||||
|
||||
/**
|
||||
* Create a new Session-per-Conversation listener using given JPA Entity Manager factory.
|
||||
* @param entityManagerFactory the entity manager factory to use
|
||||
*/
|
||||
public JpaSessionPerConversationListener(EntityManagerFactory entityManagerFactory) {
|
||||
this.entityManagerFactory = entityManagerFactory;
|
||||
}
|
||||
|
||||
public void sessionCreated(RequestContext context, FlowSession session) {
|
||||
if (session.isRoot()) {
|
||||
EntityManager em = entityManagerFactory.createEntityManager();
|
||||
context.getConversationScope().put(ENTITY_MANAGER_ATTRIBUTE, em);
|
||||
bind(em);
|
||||
}
|
||||
}
|
||||
|
||||
public void resumed(RequestContext context) {
|
||||
bind(getEntityManager(context));
|
||||
}
|
||||
|
||||
public void paused(RequestContext context, ViewSelection selectedView) {
|
||||
unbind(getEntityManager(context));
|
||||
}
|
||||
|
||||
public void sessionEnded(RequestContext context, FlowSession session, AttributeMap output) {
|
||||
if (session.isRoot()) {
|
||||
EntityManager em = (EntityManager) context.getConversationScope().remove(ENTITY_MANAGER_ATTRIBUTE);
|
||||
unbind(em);
|
||||
em.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void exceptionThrown(RequestContext context, FlowExecutionException exception) {
|
||||
unbind(getEntityManager(context));
|
||||
}
|
||||
|
||||
// internal helpers
|
||||
|
||||
private EntityManager getEntityManager(RequestContext context) {
|
||||
return (EntityManager) context.getConversationScope().get(ENTITY_MANAGER_ATTRIBUTE);
|
||||
}
|
||||
|
||||
private void bind(EntityManager em) {
|
||||
TransactionSynchronizationManager.bindResource(em, new EntityManagerHolder(em));
|
||||
}
|
||||
|
||||
private void unbind(EntityManager em) {
|
||||
TransactionSynchronizationManager.unbindResource(em);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user