Project

General

Profile

« Previous | Next » 

Revision bed13665

Added by Cherian Mathew about 11 years ago

updated to be compatible with hibernate4

View differences:

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