1 /* 2 * Copyright 2002-2014 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.springframework.test; 18 19 import org.springframework.transaction.PlatformTransactionManager; 20 import org.springframework.transaction.TransactionDefinition; 21 import org.springframework.transaction.TransactionException; 22 import org.springframework.transaction.TransactionStatus; 23 import org.springframework.transaction.support.DefaultTransactionDefinition; 24 25 /** 26 * This class is only used within tests in the spring-orm module. 27 * 28 * <p>Convenient base class for JUnit 3.8 based tests that should occur in a 29 * transaction, but normally will roll the transaction back on the completion of 30 * each test. 31 * 32 * <p>This is useful in a range of circumstances, allowing the following benefits: 33 * <ul> 34 * <li>Ability to delete or insert any data in the database, without affecting 35 * other tests 36 * <li>Providing a transactional context for any code requiring a transaction 37 * <li>Ability to write anything to the database without any need to clean up. 38 * </ul> 39 * 40 * <p>This class is typically very fast, compared to traditional setup/teardown 41 * scripts. 42 * 43 * <p>If data should be left in the database, call the {@link #setComplete()} 44 * method in each test. The {@link #setDefaultRollback "defaultRollback"} 45 * property, which defaults to "true", determines whether transactions will 46 * complete by default. 47 * 48 * <p>It is even possible to end the transaction early; for example, to verify lazy 49 * loading behavior of an O/R mapping tool. (This is a valuable away to avoid 50 * unexpected errors when testing a web UI, for example.) Simply call the 51 * {@link #endTransaction()} method. Execution will then occur without a 52 * transactional context. 53 * 54 * <p>The {@link #startNewTransaction()} method may be called after a call to 55 * {@link #endTransaction()} if you wish to create a new transaction, quite 56 * independent of the old transaction. The new transaction's default fate will 57 * be to roll back, unless {@link #setComplete()} is called again during the 58 * scope of the new transaction. Any number of transactions may be created and 59 * ended in this way. The final transaction will automatically be rolled back 60 * when the test case is torn down. 61 * 62 * <p>Transactional behavior requires a single bean in the context implementing the 63 * {@link PlatformTransactionManager} interface. This will be set by the 64 * superclass's Dependency Injection mechanism. If using the superclass's Field 65 * Injection mechanism, the implementation should be named "transactionManager". 66 * This mechanism allows the use of the 67 * {@link AbstractDependencyInjectionSpringContextTests} superclass even when 68 * there is more than one transaction manager in the context. 69 * 70 * <p><b>This base class can also be used without transaction management, if no 71 * PlatformTransactionManager bean is found in the context provided.</b> Be 72 * careful about using this mode, as it allows the potential to permanently 73 * modify data. This mode is available only if dependency checking is turned off 74 * in the {@link AbstractDependencyInjectionSpringContextTests} superclass. The 75 * non-transactional capability is provided to enable use of the same subclass 76 * in different environments. 77 * 78 * @author Rod Johnson 79 * @author Juergen Hoeller 80 * @author Sam Brannen 81 * @since 1.1.1 82 * @deprecated as of Spring 3.0, in favor of using the listener-based test context framework 83 * ({@link org.springframework.test.context.junit38.AbstractJUnit38SpringContextTests}) 84 */ 85 @Deprecated 86 public abstract class AbstractTransactionalSpringContextTests extends AbstractDependencyInjectionSpringContextTests { 87 88 /** The transaction manager to use */ 89 protected PlatformTransactionManager transactionManager; 90 91 /** Should we roll back by default? */ 92 private boolean defaultRollback = true; 93 94 /** Should we commit the current transaction? */ 95 private boolean complete = false; 96 97 /** Number of transactions started */ 98 private int transactionsStarted = 0; 99 100 /** 101 * Transaction definition used by this test class: by default, a plain 102 * DefaultTransactionDefinition. Subclasses can change this to cause 103 * different behavior. 104 */ 105 protected TransactionDefinition transactionDefinition= new DefaultTransactionDefinition(); 106 107 /** 108 * TransactionStatus for this test. Typical subclasses won't need to use it. 109 */ 110 protected TransactionStatus transactionStatus; 111 112 113 /** 114 * Default constructor for AbstractTransactionalSpringContextTests. 115 */ 116 public AbstractTransactionalSpringContextTests() { 117 } 118 119 /** 120 * Constructor for AbstractTransactionalSpringContextTests with a JUnit name. 121 */ 122 public AbstractTransactionalSpringContextTests(String name) { 123 super(name); 124 } 125 126 127 /** 128 * Specify the transaction manager to use. No transaction management will be 129 * available if this is not set. Populated through dependency injection by 130 * the superclass. 131 * <p> 132 * This mode works only if dependency checking is turned off in the 133 * {@link AbstractDependencyInjectionSpringContextTests} superclass. 134 */ 135 public void setTransactionManager(PlatformTransactionManager transactionManager) { 136 this.transactionManager = transactionManager; 137 } 138 139 /** 140 * Subclasses can set this value in their constructor to change the default, 141 * which is always to roll the transaction back. 142 */ 143 public void setDefaultRollback(final boolean defaultRollback) { 144 this.defaultRollback = defaultRollback; 145 } 146 /** 147 * Get the <em>default rollback</em> flag for this test. 148 * @see #setDefaultRollback(boolean) 149 * @return The <em>default rollback</em> flag. 150 */ 151 protected boolean isDefaultRollback() { 152 return this.defaultRollback; 153 } 154 155 /** 156 * Determines whether or not to rollback transactions for the current test. 157 * <p>The default implementation delegates to {@link #isDefaultRollback()}. 158 * Subclasses can override as necessary. 159 */ 160 protected boolean isRollback() { 161 return isDefaultRollback(); 162 } 163 164 /** 165 * Call this method in an overridden {@link #runBare()} method to prevent 166 * transactional execution. 167 */ 168 protected void preventTransaction() { 169 this.transactionDefinition = null; 170 } 171 172 /** 173 * Call this method in an overridden {@link #runBare()} method to override 174 * the transaction attributes that will be used, so that {@link #setUp()} 175 * and {@link #tearDown()} behavior is modified. 176 * @param customDefinition the custom transaction definition 177 */ 178 protected void setTransactionDefinition(TransactionDefinition customDefinition) { 179 this.transactionDefinition = customDefinition; 180 } 181 182 /** 183 * This implementation creates a transaction before test execution. 184 * <p>Override {@link #onSetUpBeforeTransaction()} and/or 185 * {@link #onSetUpInTransaction()} to add custom set-up behavior for 186 * transactional execution. Alternatively, override this method for general 187 * set-up behavior, calling {@code super.onSetUp()} as part of your 188 * method implementation. 189 * @throws Exception simply let any exception propagate 190 * @see #onTearDown() 191 */ 192 @Override 193 protected void onSetUp() throws Exception { 194 this.complete = !this.isRollback(); 195 196 if (this.transactionManager == null) { 197 this.logger.info("No transaction manager set: test will NOT run within a transaction"); 198 } 199 else if (this.transactionDefinition == null) { 200 this.logger.info("No transaction definition set: test will NOT run within a transaction"); 201 } 202 else { 203 onSetUpBeforeTransaction(); 204 startNewTransaction(); 205 try { 206 onSetUpInTransaction(); 207 } 208 catch (final Exception ex) { 209 endTransaction(); 210 throw ex; 211 } 212 } 213 } 214 215 /** 216 * Subclasses can override this method to perform any setup operations, such 217 * as populating a database table, <i>before</i> the transaction created by 218 * this class. Only invoked if there <i>is</i> a transaction: that is, if 219 * {@link #preventTransaction()} has not been invoked in an overridden 220 * {@link #runTest()} method. 221 * @throws Exception simply let any exception propagate 222 */ 223 protected void onSetUpBeforeTransaction() throws Exception { 224 } 225 226 /** 227 * Subclasses can override this method to perform any setup operations, such 228 * as populating a database table, <i>within</i> the transaction created by 229 * this class. 230 * <p><b>NB:</b> Not called if there is no transaction management, due to no 231 * transaction manager being provided in the context. 232 * <p>If any {@link Throwable} is thrown, the transaction that has been started 233 * prior to the execution of this method will be 234 * {@link #endTransaction() ended} (or rather an attempt will be made to 235 * {@link #endTransaction() end it gracefully}); The offending 236 * {@link Throwable} will then be rethrown. 237 * @throws Exception simply let any exception propagate 238 */ 239 protected void onSetUpInTransaction() throws Exception { 240 } 241 242 /** 243 * This implementation ends the transaction after test execution. 244 * <p>Override {@link #onTearDownInTransaction()} and/or 245 * {@link #onTearDownAfterTransaction()} to add custom tear-down behavior 246 * for transactional execution. Alternatively, override this method for 247 * general tear-down behavior, calling {@code super.onTearDown()} as 248 * part of your method implementation. 249 * <p>Note that {@link #onTearDownInTransaction()} will only be called if a 250 * transaction is still active at the time of the test shutdown. In 251 * particular, it will <i>not</i> be called if the transaction has been 252 * completed with an explicit {@link #endTransaction()} call before. 253 * @throws Exception simply let any exception propagate 254 * @see #onSetUp() 255 */ 256 @Override 257 protected void onTearDown() throws Exception { 258 // Call onTearDownInTransaction and end transaction if the transaction 259 // is still active. 260 if (this.transactionStatus != null && !this.transactionStatus.isCompleted()) { 261 try { 262 onTearDownInTransaction(); 263 } 264 finally { 265 endTransaction(); 266 } 267 } 268 269 // Call onTearDownAfterTransaction if there was at least one 270 // transaction, even if it has been completed early through an 271 // endTransaction() call. 272 if (this.transactionsStarted > 0) { 273 onTearDownAfterTransaction(); 274 } 275 } 276 277 /** 278 * Subclasses can override this method to run invariant tests here. The 279 * transaction is <i>still active</i> at this point, so any changes made in 280 * the transaction will still be visible. However, there is no need to clean 281 * up the database, as a rollback will follow automatically. 282 * <p><b>NB:</b> Not called if there is no actual transaction, for example due 283 * to no transaction manager being provided in the application context. 284 * @throws Exception simply let any exception propagate 285 */ 286 protected void onTearDownInTransaction() throws Exception { 287 } 288 289 /** 290 * Subclasses can override this method to perform cleanup after a 291 * transaction here. At this point, the transaction is <i>not active anymore</i>. 292 * @throws Exception simply let any exception propagate 293 */ 294 protected void onTearDownAfterTransaction() throws Exception { 295 } 296 297 /** 298 * Cause the transaction to commit for this test method, even if the test 299 * method is configured to {@link #isRollback() rollback}. 300 * @throws IllegalStateException if the operation cannot be set to complete 301 * as no transaction manager was provided 302 */ 303 protected void setComplete() { 304 if (this.transactionManager == null) { 305 throw new IllegalStateException("No transaction manager set"); 306 } 307 this.complete = true; 308 } 309 310 /** 311 * Immediately force a commit or rollback of the transaction, according to 312 * the {@code complete} and {@link #isRollback() rollback} flags. 313 * <p>Can be used to explicitly let the transaction end early, for example to 314 * check whether lazy associations of persistent objects work outside of a 315 * transaction (that is, have been initialized properly). 316 * @see #setComplete() 317 */ 318 protected void endTransaction() { 319 final boolean commit = this.complete || !isRollback(); 320 if (this.transactionStatus != null) { 321 try { 322 if (commit) { 323 this.transactionManager.commit(this.transactionStatus); 324 this.logger.debug("Committed transaction after execution of test [" + getName() + "]."); 325 } 326 else { 327 this.transactionManager.rollback(this.transactionStatus); 328 this.logger.debug("Rolled back transaction after execution of test [" + getName() + "]."); 329 } 330 } 331 finally { 332 this.transactionStatus = null; 333 } 334 } 335 } 336 337 /** 338 * Start a new transaction. Only call this method if 339 * {@link #endTransaction()} has been called. {@link #setComplete()} can be 340 * used again in the new transaction. The fate of the new transaction, by 341 * default, will be the usual rollback. 342 * @throws TransactionException if starting the transaction failed 343 */ 344 protected void startNewTransaction() throws TransactionException { 345 if (this.transactionStatus != null) { 346 throw new IllegalStateException("Cannot start new transaction without ending existing transaction: " 347 + "Invoke endTransaction() before startNewTransaction()"); 348 } 349 if (this.transactionManager == null) { 350 throw new IllegalStateException("No transaction manager set"); 351 } 352 353 this.transactionStatus = this.transactionManager.getTransaction(this.transactionDefinition); 354 ++this.transactionsStarted; 355 this.complete = !this.isRollback(); 356 357 if (this.logger.isDebugEnabled()) { 358 this.logger.debug("Began transaction (" + this.transactionsStarted + "): transaction manager [" 359 + this.transactionManager + "]; rollback [" + this.isRollback() + "]."); 360 } 361 } 362 363 }