Revision bed13665
Added by Cherian Mathew about 11 years ago
cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/conversation/ConversationHolder.java | ||
---|---|---|
11 | 11 |
|
12 | 12 |
import java.sql.Connection; |
13 | 13 |
import java.sql.SQLException; |
14 |
import java.util.ArrayList; |
|
15 |
import java.util.Iterator; |
|
16 |
import java.util.List; |
|
14 | 17 |
|
15 | 18 |
import javax.sql.DataSource; |
16 | 19 |
|
... | ... | |
23 | 26 |
import org.hibernate.jdbc.Work; |
24 | 27 |
import org.springframework.beans.factory.annotation.Autowired; |
25 | 28 |
import org.springframework.dao.DataAccessResourceFailureException; |
29 |
import org.springframework.jdbc.datasource.ConnectionHolder; |
|
26 | 30 |
import org.springframework.orm.hibernate4.SessionFactoryUtils; |
27 | 31 |
import org.springframework.orm.hibernate4.SessionHolder; |
28 | 32 |
import org.springframework.transaction.PlatformTransactionManager; |
... | ... | |
30 | 34 |
import org.springframework.transaction.TransactionStatus; |
31 | 35 |
import org.springframework.transaction.support.TransactionSynchronizationManager; |
32 | 36 |
|
37 |
import eu.etaxonomy.cdm.api.service.IService; |
|
38 |
import eu.etaxonomy.cdm.api.service.ServiceImpl; |
|
33 | 39 |
import eu.etaxonomy.cdm.persistence.hibernate.CdmPostDataChangeObservableListener; |
34 | 40 |
|
35 | 41 |
/** |
36 |
* This is an implementation of the session-per-conversation pattern for usage |
|
37 |
* in a Spring context. |
|
42 |
* This is an implementation of the session-per-conversation pattern for usage in a Spring context. |
|
43 |
* |
|
44 |
* The primary aim of this class is to create and maintain sessions across multiple transactions. |
|
45 |
* It is important to ensure that these (long running) sessions must always behave consistently |
|
46 |
* with regards to session management behaviour expected by Hibernate. |
|
47 |
* <p> |
|
48 |
* This behaviour essentially revolves around the resources map in the {@link org.springframework.transaction.support.TransactionSynchronizationManager TransactionSynchronizationManager}. |
|
49 |
* This resources map contains two entries of interest, |
|
50 |
* - (Autowired) {@link org.hibernate.SessionFactory} mapped to the {@link org.springframework.orm.hibernate4.SessionHolder} |
|
51 |
* - (Autowired) {@link javax.sql.DataSource} mapped to the {@link org.springframework.jdbc.datasource.ConnectionHolder} |
|
52 |
* <p> |
|
53 |
* The SessionHolder object itself contains the {@link org.hibernate.Session Session} as well as the {@link org.hibernate.Transaction object. |
|
54 |
* The ConnectionHolder contains the (JDBC) {@link java.sql.Connection Connection} to the database. For every action to do with the |
|
55 |
* transaction object it is required to have both entries present in the resources. Both the session as well as the connection |
|
56 |
* objects must not be null and the corresponding holders must have their 'synchronizedWithTransaction' flag set to true. |
|
57 |
* <p> |
|
58 |
* The default behaviour of the {@link org.springframework.transaction.PlatformTransactionManager PlatformTransactionManager} which in the CDM case is autowired |
|
59 |
* to {@link org.springframework.orm.hibernate4.HibernateTransactionManager HibernateTransactionManager}, is to check these entries |
|
60 |
* when starting a transaction. If this entries do not exist in the resource map then they are created, implying a new session, which |
|
61 |
* is in fact how hibernate implements the default 'session-per-request' pattern internally. |
|
62 |
* <p> |
|
63 |
* Given the above conditions, this class manages long running sessions by providing the following methods, |
|
64 |
* - {@link #bind()} : binds the session owned by this conversation to the resource map |
|
65 |
* - {@link #startTransaction()} : starts a transaction |
|
66 |
* - {@link #commit()} : commits the current transaction, with the option of restarting a new transaction. |
|
67 |
* - {@link #unbind()} : unbinds the session owned by this conversation from the resource map. |
|
68 |
* - {@link #close()} : closes the session owned by this conversation |
|
69 |
* <p> |
|
70 |
* With the exception of {@link #unbind()} (which should be called explicitly), the above sequence must be strictly followed to |
|
71 |
* maintain a consistent session state. Even though it is possible to interweave multiple conversations at the same time, for a |
|
72 |
* specific conversation the above sequence must be followed. |
|
38 | 73 |
* |
39 | 74 |
* @see http://www.hibernate.org/42.html |
40 | 75 |
* |
41 |
* @author n.hoffmann |
|
76 |
* @author n.hoffmann,c.mathew
|
|
42 | 77 |
* @created 12.03.2009 |
43 | 78 |
* @version 1.0 |
44 | 79 |
*/ |
45 |
public class ConversationHolder{ |
|
80 |
public class ConversationHolder {
|
|
46 | 81 |
|
47 | 82 |
private static final Logger logger = Logger.getLogger(ConversationHolder.class); |
48 | 83 |
|
... | ... | |
55 | 90 |
@Autowired |
56 | 91 |
private PlatformTransactionManager transactionManager; |
57 | 92 |
|
93 |
@Autowired |
|
94 |
private IService service; |
|
58 | 95 |
|
59 | 96 |
|
60 | 97 |
/** |
... | ... | |
76 | 113 |
* This conversations transaction |
77 | 114 |
*/ |
78 | 115 |
private TransactionStatus transactionStatus; |
116 |
|
|
79 | 117 |
|
80 | 118 |
private boolean closed = false; |
81 | 119 |
|
... | ... | |
91 | 129 |
this(); |
92 | 130 |
this.dataSource = dataSource; |
93 | 131 |
this.sessionFactory = sessionFactory; |
94 |
this.transactionManager = transactionManager; |
|
132 |
this.transactionManager = transactionManager;
|
|
95 | 133 |
|
96 | 134 |
bind(); |
97 | 135 |
|
98 | 136 |
if(TransactionSynchronizationManager.hasResource(getDataSource())){ |
99 | 137 |
TransactionSynchronizationManager.unbindResource(getDataSource()); |
100 | 138 |
} |
139 |
|
|
101 | 140 |
} |
102 | 141 |
|
103 | 142 |
/** |
... | ... | |
107 | 146 |
public void bind() { |
108 | 147 |
|
109 | 148 |
logger.info("Binding resources for ConversationHolder"); |
110 |
|
|
149 |
|
|
111 | 150 |
if(TransactionSynchronizationManager.isSynchronizationActive()){ |
112 | 151 |
TransactionSynchronizationManager.clearSynchronization(); |
113 | 152 |
} |
... | ... | |
117 | 156 |
logger.info("Starting new Synchronization in TransactionSynchronizationManager"); |
118 | 157 |
TransactionSynchronizationManager.initSynchronization(); |
119 | 158 |
|
159 |
|
|
120 | 160 |
if(TransactionSynchronizationManager.hasResource(getSessionFactory())){ |
121 | 161 |
TransactionSynchronizationManager.unbindResource(getSessionFactory()); |
122 | 162 |
} |
123 | 163 |
|
124 | 164 |
logger.info("Binding Session to TransactionSynchronizationManager: Session: " + getSessionHolder()); |
125 | 165 |
TransactionSynchronizationManager.bindResource(getSessionFactory(), getSessionHolder()); |
126 |
|
|
127 |
}catch(Exception e){ |
|
166 |
|
|
167 |
|
|
168 |
|
|
169 |
} catch(Exception e){ |
|
128 | 170 |
logger.error("Error binding resources for session", e); |
129 | 171 |
} |
130 | 172 |
|
131 | 173 |
} |
132 | 174 |
|
175 |
/** |
|
176 |
* This method has to be called when suspending the current unit of work. The conversation can be later bound again. |
|
177 |
*/ |
|
178 |
public void unbind() { |
|
179 |
|
|
180 |
logger.info("Unbinding resources for ConversationHolder"); |
|
181 |
|
|
182 |
if(TransactionSynchronizationManager.isSynchronizationActive()){ |
|
183 |
TransactionSynchronizationManager.clearSynchronization(); |
|
184 |
} |
|
185 |
|
|
186 |
|
|
187 |
if(isBound()) { |
|
188 |
// unbind the current session. |
|
189 |
// there is no need to bind a new session, since HibernateTransactionManager will create a new one |
|
190 |
// if the resource map does not contain one (ditto for the datasource-to-connection entry). |
|
191 |
TransactionSynchronizationManager.unbindResource(getSessionFactory()); |
|
192 |
if(TransactionSynchronizationManager.hasResource(getDataSource())){ |
|
193 |
TransactionSynchronizationManager.unbindResource(getDataSource()); |
|
194 |
} |
|
195 |
} |
|
196 |
} |
|
197 |
|
|
133 | 198 |
public SessionHolder getSessionHolder(){ |
134 | 199 |
if(this.sessionHolder == null){ |
135 | 200 |
logger.info("Creating new SessionHolder"); |
... | ... | |
150 | 215 |
*/ |
151 | 216 |
public boolean isBound(){ |
152 | 217 |
//return sessionHolder != null && longSession != null && longSession.isConnected(); |
153 |
return longSession != null && getSessionFactory().getCurrentSession() == longSession; |
|
218 |
SessionHolder currentSessionHolder = (SessionHolder)TransactionSynchronizationManager.getResource(getSessionFactory()); |
|
219 |
return longSession != null && currentSessionHolder != null && getSessionFactory().getCurrentSession() == longSession; |
|
154 | 220 |
} |
155 | 221 |
|
156 | 222 |
/** |
... | ... | |
164 | 230 |
logger.warn("We allow only one transaction at the moment but startTransaction " + |
165 | 231 |
"was called a second time.\nReturning the transaction already associated with this " + |
166 | 232 |
"ConversationManager"); |
167 |
}else{ |
|
233 |
}else{ |
|
234 |
//always safe to remove the datasource-to-connection entry since we |
|
235 |
// know that HibernateTransactionManager will create a new one |
|
236 |
if(TransactionSynchronizationManager.hasResource(getDataSource())){ |
|
237 |
TransactionSynchronizationManager.unbindResource(getDataSource()); |
|
238 |
} |
|
239 |
|
|
168 | 240 |
transactionStatus = transactionManager.getTransaction(definition); |
169 | 241 |
|
170 | 242 |
logger.info("Transaction started: " + transactionStatus); |
... | ... | |
219 | 291 |
if(getSessionHolder().isRollbackOnly()){ |
220 | 292 |
logger.error("Commiting this session will not work. It has been marked as rollback only."); |
221 | 293 |
} |
294 |
// if a datasource-to-connection entry already exists in the resource map |
|
295 |
// then its setSynchronizedWithTransaction should be true, since hibernate has added |
|
296 |
// this entry. |
|
297 |
// if the datasource-to-connection entry does not exist then we need to create one |
|
298 |
// and explicitly setSynchronizedWithTransaction to true. |
|
299 |
TransactionSynchronizationManager.getResource(getDataSource()); |
|
300 |
if(!TransactionSynchronizationManager.hasResource(getDataSource())){ |
|
301 |
try { |
|
302 |
ConnectionHolder ch = new ConnectionHolder(getDataSource().getConnection()); |
|
303 |
ch.setSynchronizedWithTransaction(true); |
|
304 |
TransactionSynchronizationManager.bindResource(getDataSource(),ch); |
|
305 |
|
|
306 |
} catch (IllegalStateException e) { |
|
307 |
// TODO Auto-generated catch block |
|
308 |
e.printStackTrace(); |
|
309 |
} catch (SQLException e) { |
|
310 |
// TODO Auto-generated catch block |
|
311 |
e.printStackTrace(); |
|
312 |
} |
|
313 |
} |
|
222 | 314 |
|
223 | 315 |
// commit the changes |
224 | 316 |
transactionManager.commit(transactionStatus); |
... | ... | |
244 | 336 |
/** |
245 | 337 |
* @return the session associated with this conversation manager |
246 | 338 |
*/ |
247 |
private Session getSession() {
|
|
339 |
public Session getSession() {
|
|
248 | 340 |
if(longSession == null){ |
249 |
logger.info("Creating Session: [" + longSession + "]"); |
|
250 |
try{ |
|
251 |
//TODO still need to check if connection and session() handling still works correctly |
|
252 |
//after upgrade to hibernate 4. |
|
253 |
//With hibernate 4 JDBC {@link Connection connection(s)} will be obtained from the |
|
254 |
// configured {@link org.hibernate.service.jdbc.connections.spi.ConnectionProvider} |
|
255 |
//as needed. |
|
256 |
//Also interesting: http://stackoverflow.com/questions/3526556/session-connection-deprecated-on-hibernate |
|
257 |
//http://blog-it.hypoport.de/2012/05/10/hibernate-4-migration/ |
|
258 |
longSession = sessionFactory.openSession(); |
|
259 |
longSession.setFlushMode(FlushMode.COMMIT); |
|
260 |
} |
|
261 |
catch (HibernateException ex) { |
|
262 |
throw new DataAccessResourceFailureException("Could not open Hibernate Session", ex); |
|
263 |
} |
|
264 |
|
|
265 |
|
|
341 |
longSession = getNewSession(); |
|
266 | 342 |
} |
267 |
|
|
268 | 343 |
return longSession; |
344 |
} |
|
345 |
|
|
346 |
/** |
|
347 |
* @return a new session to be managed by this conversation |
|
348 |
*/ |
|
349 |
private Session getNewSession() { |
|
350 |
|
|
351 |
// Interesting: http://stackoverflow.com/questions/3526556/session-connection-deprecated-on-hibernate |
|
352 |
// Also, http://blog-it.hypoport.de/2012/05/10/hibernate-4-migration/ |
|
353 |
|
|
354 |
// This will create a new session which must be explicitly managed by this conversation, which includes |
|
355 |
// binding / unbinding / closing session as well as starting / committing transactions. |
|
356 |
Session session = sessionFactory.openSession(); |
|
357 |
session.setFlushMode(FlushMode.COMMIT); |
|
358 |
logger.info("Creating Session: [" + longSession + "]"); |
|
359 |
return session; |
|
269 | 360 |
} |
270 | 361 |
|
362 |
|
|
363 |
|
|
364 |
|
|
271 | 365 |
/** |
272 | 366 |
* @return the session factory that is bound to this conversation manager |
273 | 367 |
*/ |
274 |
private SessionFactory getSessionFactory() {
|
|
368 |
public SessionFactory getSessionFactory() {
|
|
275 | 369 |
return sessionFactory; |
276 | 370 |
} |
277 | 371 |
|
... | ... | |
322 | 416 |
* Free resources bound to this conversationHolder |
323 | 417 |
*/ |
324 | 418 |
public void close(){ |
325 |
if(getSession().isOpen()) |
|
326 |
getSession().close(); |
|
419 |
if(getSession().isOpen()) { |
|
420 |
getSession().close(); |
|
421 |
unbind(); |
|
422 |
} |
|
423 |
longSession = null; |
|
424 |
sessionHolder = null; |
|
327 | 425 |
closed = true; |
328 | 426 |
} |
329 | 427 |
|
330 | 428 |
public boolean isClosed(){ |
331 | 429 |
return closed; |
332 | 430 |
} |
431 |
|
|
432 |
|
|
333 | 433 |
} |
Also available in: Unified diff
updated to be compatible with hibernate4