View Javadoc
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.jdbc.datasource;
18  
19  import java.sql.Connection;
20  import java.sql.DatabaseMetaData;
21  import java.sql.PreparedStatement;
22  import java.sql.SQLException;
23  import java.sql.Savepoint;
24  import javax.sql.DataSource;
25  
26  import org.junit.After;
27  import org.junit.Before;
28  import org.junit.Test;
29  import org.mockito.InOrder;
30  
31  import org.springframework.dao.DataAccessResourceFailureException;
32  import org.springframework.jdbc.UncategorizedSQLException;
33  import org.springframework.jdbc.support.nativejdbc.SimpleNativeJdbcExtractor;
34  import org.springframework.tests.Assume;
35  import org.springframework.tests.TestGroup;
36  import org.springframework.transaction.CannotCreateTransactionException;
37  import org.springframework.transaction.IllegalTransactionStateException;
38  import org.springframework.transaction.PlatformTransactionManager;
39  import org.springframework.transaction.TransactionDefinition;
40  import org.springframework.transaction.TransactionStatus;
41  import org.springframework.transaction.TransactionSystemException;
42  import org.springframework.transaction.TransactionTimedOutException;
43  import org.springframework.transaction.UnexpectedRollbackException;
44  import org.springframework.transaction.support.DefaultTransactionDefinition;
45  import org.springframework.transaction.support.TransactionCallbackWithoutResult;
46  import org.springframework.transaction.support.TransactionSynchronization;
47  import org.springframework.transaction.support.TransactionSynchronizationManager;
48  import org.springframework.transaction.support.TransactionTemplate;
49  
50  import static org.junit.Assert.*;
51  import static org.mockito.BDDMockito.*;
52  
53  /**
54   * @author Juergen Hoeller
55   * @since 04.07.2003
56   */
57  public class DataSourceTransactionManagerTests  {
58  
59  	private Connection con;
60  
61  	private DataSource ds;
62  
63  	private DataSourceTransactionManager tm;
64  
65  
66  	@Before
67  	public void setUp() throws Exception {
68  		con = mock(Connection.class);
69  		ds	= mock(DataSource.class);
70  		tm = new DataSourceTransactionManager(ds);
71  		given(ds.getConnection()).willReturn(con);
72  	}
73  
74  	@After
75  	public void verifyTransactionSynchronizationManagerState() {
76  		assertTrue(TransactionSynchronizationManager.getResourceMap().isEmpty());
77  		assertFalse(TransactionSynchronizationManager.isSynchronizationActive());
78  		assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
79  		assertFalse(TransactionSynchronizationManager.isActualTransactionActive());
80  	}
81  
82  
83  	@Test
84  	public void testTransactionCommitWithAutoCommitTrue() throws Exception {
85  		doTestTransactionCommitRestoringAutoCommit(true, false, false);
86  	}
87  
88  	@Test
89  	public void testTransactionCommitWithAutoCommitFalse() throws Exception {
90  		doTestTransactionCommitRestoringAutoCommit(false, false, false);
91  	}
92  
93  	@Test
94  	public void testTransactionCommitWithAutoCommitTrueAndLazyConnection() throws Exception {
95  		doTestTransactionCommitRestoringAutoCommit(true, true, false);
96  	}
97  
98  	@Test
99  	public void testTransactionCommitWithAutoCommitFalseAndLazyConnection() throws Exception {
100 		doTestTransactionCommitRestoringAutoCommit(false, true, false);
101 	}
102 
103 	@Test
104 	public void testTransactionCommitWithAutoCommitTrueAndLazyConnectionAndStatementCreated() throws Exception {
105 		doTestTransactionCommitRestoringAutoCommit(true, true, true);
106 	}
107 
108 	@Test
109 	public void testTransactionCommitWithAutoCommitFalseAndLazyConnectionAndStatementCreated() throws Exception {
110 		doTestTransactionCommitRestoringAutoCommit(false, true, true);
111 	}
112 
113 	private void doTestTransactionCommitRestoringAutoCommit(
114 			boolean autoCommit, boolean lazyConnection, final boolean createStatement)
115 			throws Exception {
116 		if (lazyConnection) {
117 			given(con.getAutoCommit()).willReturn(autoCommit);
118 			given(con.getTransactionIsolation()).willReturn(Connection.TRANSACTION_READ_COMMITTED);
119 		}
120 
121 		if (!lazyConnection || createStatement) {
122 			given(con.getAutoCommit()).willReturn(autoCommit);
123 		}
124 
125 		final DataSource dsToUse = (lazyConnection ? new LazyConnectionDataSourceProxy(ds) : ds);
126 		tm = new DataSourceTransactionManager(dsToUse);
127 		TransactionTemplate tt = new TransactionTemplate(tm);
128 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(dsToUse));
129 		assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
130 
131 		tt.execute(new TransactionCallbackWithoutResult() {
132 			@Override
133 			protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
134 				assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(dsToUse));
135 				assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
136 				assertTrue("Is new transaction", status.isNewTransaction());
137 				assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
138 				assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
139 				Connection tCon = DataSourceUtils.getConnection(dsToUse);
140 				try {
141 					if (createStatement) {
142 						tCon.createStatement();
143 						assertEquals(con, new SimpleNativeJdbcExtractor().getNativeConnection(tCon));
144 					}
145 				}
146 				catch (SQLException ex) {
147 					throw new UncategorizedSQLException("", "", ex);
148 				}
149 			}
150 		});
151 
152 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(dsToUse));
153 		assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
154 
155 		if(autoCommit && (!lazyConnection || createStatement)) {
156 			InOrder ordered = inOrder(con);
157 			ordered.verify(con).setAutoCommit(false);
158 			ordered.verify(con).commit();
159 			ordered.verify(con).setAutoCommit(true);
160 		}
161 		if (createStatement) {
162 			verify(con, times(2)).close();
163 		}
164 		else {
165 			verify(con).close();
166 		}
167 	}
168 
169 	@Test
170 	public void testTransactionRollbackWithAutoCommitTrue() throws Exception  {
171 		doTestTransactionRollbackRestoringAutoCommit(true, false, false);
172 	}
173 
174 	@Test
175 	public void testTransactionRollbackWithAutoCommitFalse() throws Exception  {
176 		doTestTransactionRollbackRestoringAutoCommit(false, false, false);
177 	}
178 
179 	@Test
180 	public void testTransactionRollbackWithAutoCommitTrueAndLazyConnection() throws Exception  {
181 		doTestTransactionRollbackRestoringAutoCommit(true, true, false);
182 	}
183 
184 	@Test
185 	public void testTransactionRollbackWithAutoCommitFalseAndLazyConnection() throws Exception  {
186 		doTestTransactionRollbackRestoringAutoCommit(false, true, false);
187 	}
188 
189 	@Test
190 	public void testTransactionRollbackWithAutoCommitTrueAndLazyConnectionAndCreateStatement() throws Exception  {
191 		doTestTransactionRollbackRestoringAutoCommit(true, true, true);
192 	}
193 
194 	@Test
195 	public void testTransactionRollbackWithAutoCommitFalseAndLazyConnectionAndCreateStatement() throws Exception  {
196 		doTestTransactionRollbackRestoringAutoCommit(false, true, true);
197 	}
198 
199 	private void doTestTransactionRollbackRestoringAutoCommit(
200 			boolean autoCommit, boolean lazyConnection, final boolean createStatement) throws Exception {
201 		if (lazyConnection) {
202 			given(con.getAutoCommit()).willReturn(autoCommit);
203 			given(con.getTransactionIsolation()).willReturn(
204 					Connection.TRANSACTION_READ_COMMITTED);
205 		}
206 
207 		if (!lazyConnection || createStatement) {
208 			given(con.getAutoCommit()).willReturn(autoCommit);
209 		}
210 
211 		final DataSource dsToUse = (lazyConnection ? new LazyConnectionDataSourceProxy(ds) : ds);
212 		 tm = new DataSourceTransactionManager(dsToUse);
213 		TransactionTemplate tt = new TransactionTemplate(tm);
214 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(dsToUse));
215 		assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
216 
217 		final RuntimeException ex = new RuntimeException("Application exception");
218 		try {
219 			tt.execute(new TransactionCallbackWithoutResult() {
220 				@Override
221 				protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
222 					assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(dsToUse));
223 					assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
224 					assertTrue("Is new transaction", status.isNewTransaction());
225 					Connection con = DataSourceUtils.getConnection(dsToUse);
226 					if (createStatement) {
227 						try {
228 							con.createStatement();
229 						}
230 						catch (SQLException ex) {
231 							throw new UncategorizedSQLException("", "", ex);
232 						}
233 					}
234 					throw ex;
235 				}
236 			});
237 			fail("Should have thrown RuntimeException");
238 		}
239 		catch (RuntimeException ex2) {
240 			// expected
241 			assertTrue("Correct exception thrown", ex2.equals(ex));
242 		}
243 
244 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
245 		assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
246 
247 		if(autoCommit && (!lazyConnection || createStatement)) {
248 			InOrder ordered = inOrder(con);
249 			ordered.verify(con).setAutoCommit(false);
250 			ordered.verify(con).rollback();
251 			ordered.verify(con).setAutoCommit(true);
252 		}
253 		if (createStatement) {
254 			verify(con, times(2)).close();
255 		}
256 		else {
257 			verify(con).close();
258 		}
259 	}
260 
261 	@Test
262 	public void testTransactionRollbackOnly() throws Exception {
263 		tm.setTransactionSynchronization(DataSourceTransactionManager.SYNCHRONIZATION_NEVER);
264 		TransactionTemplate tt = new TransactionTemplate(tm);
265 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
266 		assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
267 
268 		ConnectionHolder conHolder = new ConnectionHolder(con);
269 		conHolder.setTransactionActive(true);
270 		TransactionSynchronizationManager.bindResource(ds, conHolder);
271 		final RuntimeException ex = new RuntimeException("Application exception");
272 		try {
273 			tt.execute(new TransactionCallbackWithoutResult() {
274 				@Override
275 				protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
276 					assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
277 					assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
278 					assertTrue("Is existing transaction", !status.isNewTransaction());
279 					throw ex;
280 				}
281 			});
282 			fail("Should have thrown RuntimeException");
283 		}
284 		catch (RuntimeException ex2) {
285 			// expected
286 			assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
287 			assertEquals("Correct exception thrown", ex, ex2);
288 		}
289 		finally {
290 			TransactionSynchronizationManager.unbindResource(ds);
291 		}
292 
293 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
294 	}
295 
296 	@Test
297 	public void testParticipatingTransactionWithRollbackOnly() throws Exception {
298 		doTestParticipatingTransactionWithRollbackOnly(false);
299 	}
300 
301 	@Test
302 	public void testParticipatingTransactionWithRollbackOnlyAndFailEarly() throws Exception {
303 		doTestParticipatingTransactionWithRollbackOnly(true);
304 	}
305 
306 	private void doTestParticipatingTransactionWithRollbackOnly(boolean failEarly) throws Exception {
307 		given(con.isReadOnly()).willReturn(false);
308 		if (failEarly) {
309 			tm.setFailEarlyOnGlobalRollbackOnly(true);
310 		}
311 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
312 		assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
313 
314 		TransactionStatus ts = tm.getTransaction(new DefaultTransactionDefinition());
315 		TestTransactionSynchronization synch =
316 				new TestTransactionSynchronization(ds, TransactionSynchronization.STATUS_ROLLED_BACK);
317 		TransactionSynchronizationManager.registerSynchronization(synch);
318 
319 		boolean outerTransactionBoundaryReached = false;
320 		try {
321 			assertTrue("Is new transaction", ts.isNewTransaction());
322 
323 			final TransactionTemplate tt = new TransactionTemplate(tm);
324 			tt.execute(new TransactionCallbackWithoutResult() {
325 				@Override
326 				protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
327 					assertTrue("Is existing transaction", !status.isNewTransaction());
328 					assertFalse("Is not rollback-only", status.isRollbackOnly());
329 					tt.execute(new TransactionCallbackWithoutResult() {
330 						@Override
331 						protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
332 							assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
333 							assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
334 							assertTrue("Is existing transaction", !status.isNewTransaction());
335 							status.setRollbackOnly();
336 						}
337 					});
338 					assertTrue("Is existing transaction", !status.isNewTransaction());
339 					assertTrue("Is rollback-only", status.isRollbackOnly());
340 				}
341 			});
342 
343 			outerTransactionBoundaryReached = true;
344 			tm.commit(ts);
345 
346 			fail("Should have thrown UnexpectedRollbackException");
347 		}
348 		catch (UnexpectedRollbackException ex) {
349 			// expected
350 			if (!outerTransactionBoundaryReached) {
351 				tm.rollback(ts);
352 			}
353 			if (failEarly) {
354 				assertFalse(outerTransactionBoundaryReached);
355 			}
356 			else {
357 				assertTrue(outerTransactionBoundaryReached);
358 			}
359 		}
360 
361 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
362 		assertFalse(synch.beforeCommitCalled);
363 		assertTrue(synch.beforeCompletionCalled);
364 		assertFalse(synch.afterCommitCalled);
365 		assertTrue(synch.afterCompletionCalled);
366 		verify(con).rollback();
367 		verify(con).close();
368 	}
369 
370 	@Test
371 	public void testParticipatingTransactionWithIncompatibleIsolationLevel() throws Exception {
372 		tm.setValidateExistingTransaction(true);
373 
374 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
375 		assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
376 
377 		try {
378 			final TransactionTemplate tt = new TransactionTemplate(tm);
379 			final TransactionTemplate tt2 = new TransactionTemplate(tm);
380 			tt2.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
381 
382 			tt.execute(new TransactionCallbackWithoutResult() {
383 				@Override
384 				protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
385 					assertFalse("Is not rollback-only", status.isRollbackOnly());
386 					tt2.execute(new TransactionCallbackWithoutResult() {
387 						@Override
388 						protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
389 							status.setRollbackOnly();
390 						}
391 					});
392 					assertTrue("Is rollback-only", status.isRollbackOnly());
393 				}
394 			});
395 
396 			fail("Should have thrown IllegalTransactionStateException");
397 		}
398 		catch (IllegalTransactionStateException ex) {
399 			// expected
400 		}
401 
402 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
403 		verify(con).rollback();
404 		verify(con).close();
405 	}
406 
407 	@Test
408 	public void testParticipatingTransactionWithIncompatibleReadOnly() throws Exception {
409 		willThrow(new SQLException("read-only not supported")).given(con).setReadOnly(true);
410 		tm.setValidateExistingTransaction(true);
411 
412 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
413 		assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
414 
415 		try {
416 			final TransactionTemplate tt = new TransactionTemplate(tm);
417 			tt.setReadOnly(true);
418 			final TransactionTemplate tt2 = new TransactionTemplate(tm);
419 			tt2.setReadOnly(false);
420 
421 			tt.execute(new TransactionCallbackWithoutResult() {
422 				@Override
423 				protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
424 					assertFalse("Is not rollback-only", status.isRollbackOnly());
425 					tt2.execute(new TransactionCallbackWithoutResult() {
426 						@Override
427 						protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
428 							status.setRollbackOnly();
429 						}
430 					});
431 					assertTrue("Is rollback-only", status.isRollbackOnly());
432 				}
433 			});
434 
435 			fail("Should have thrown IllegalTransactionStateException");
436 		}
437 		catch (IllegalTransactionStateException ex) {
438 			// expected
439 		}
440 
441 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
442 		verify(con).rollback();
443 		verify(con).close();
444 	}
445 
446 	@Test
447 	public void testParticipatingTransactionWithTransactionStartedFromSynch() throws Exception {
448 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
449 		assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
450 
451 		final TransactionTemplate tt = new TransactionTemplate(tm);
452 		tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
453 
454 		final TestTransactionSynchronization synch =
455 				new TestTransactionSynchronization(ds, TransactionSynchronization.STATUS_COMMITTED) {
456 					@Override
457 					public void afterCompletion(int status) {
458 						super.afterCompletion(status);
459 						tt.execute(new TransactionCallbackWithoutResult() {
460 							@Override
461 							protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
462 							}
463 						});
464 					}
465 				};
466 
467 		tt.execute(new TransactionCallbackWithoutResult() {
468 			@Override
469 			protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
470 				TransactionSynchronizationManager.registerSynchronization(synch);
471 			}
472 		});
473 
474 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
475 		assertTrue(synch.beforeCommitCalled);
476 		assertTrue(synch.beforeCompletionCalled);
477 		assertTrue(synch.afterCommitCalled);
478 		assertTrue(synch.afterCompletionCalled);
479 		verify(con, times(2)).commit();
480 		verify(con, times(2)).close();
481 	}
482 
483 	@Test
484 	public void testParticipatingTransactionWithRollbackOnlyAndInnerSynch() throws Exception {
485 		tm.setTransactionSynchronization(DataSourceTransactionManager.SYNCHRONIZATION_NEVER);
486 		DataSourceTransactionManager tm2 = new DataSourceTransactionManager(ds);
487 		// tm has no synch enabled (used at outer level), tm2 has synch enabled (inner level)
488 
489 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
490 		assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
491 
492 		TransactionStatus ts = tm.getTransaction(new DefaultTransactionDefinition());
493 		final TestTransactionSynchronization synch =
494 				new TestTransactionSynchronization(ds, TransactionSynchronization.STATUS_UNKNOWN);
495 
496 		try {
497 			assertTrue("Is new transaction", ts.isNewTransaction());
498 
499 			final TransactionTemplate tt = new TransactionTemplate(tm2);
500 			tt.execute(new TransactionCallbackWithoutResult() {
501 				@Override
502 				protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
503 					assertTrue("Is existing transaction", !status.isNewTransaction());
504 					assertFalse("Is not rollback-only", status.isRollbackOnly());
505 					tt.execute(new TransactionCallbackWithoutResult() {
506 						@Override
507 						protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
508 							assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
509 							assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
510 							assertTrue("Is existing transaction", !status.isNewTransaction());
511 							status.setRollbackOnly();
512 						}
513 					});
514 					assertTrue("Is existing transaction", !status.isNewTransaction());
515 					assertTrue("Is rollback-only", status.isRollbackOnly());
516 					TransactionSynchronizationManager.registerSynchronization(synch);
517 				}
518 			});
519 
520 			tm.commit(ts);
521 
522 			fail("Should have thrown UnexpectedRollbackException");
523 		}
524 		catch (UnexpectedRollbackException ex) {
525 			// expected
526 		}
527 
528 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
529 		assertFalse(synch.beforeCommitCalled);
530 		assertTrue(synch.beforeCompletionCalled);
531 		assertFalse(synch.afterCommitCalled);
532 		assertTrue(synch.afterCompletionCalled);
533 		verify(con).rollback();
534 		verify(con).close();
535 	}
536 
537 	@Test
538 	public void testPropagationRequiresNewWithExistingTransaction() throws Exception {
539 		final TransactionTemplate tt = new TransactionTemplate(tm);
540 		tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
541 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
542 		assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
543 
544 		tt.execute(new TransactionCallbackWithoutResult() {
545 			@Override
546 			protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
547 				assertTrue("Is new transaction", status.isNewTransaction());
548 				assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
549 				assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
550 				assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
551 				tt.execute(new TransactionCallbackWithoutResult() {
552 					@Override
553 					protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
554 						assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
555 						assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
556 						assertTrue("Is new transaction", status.isNewTransaction());
557 						assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
558 						assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
559 						status.setRollbackOnly();
560 					}
561 				});
562 				assertTrue("Is new transaction", status.isNewTransaction());
563 				assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
564 				assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
565 			}
566 		});
567 
568 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
569 		verify(con).rollback();
570 		verify(con).commit();
571 		verify(con, times(2)).close();
572 	}
573 
574 	@Test
575 	public void testPropagationRequiresNewWithExistingTransactionAndUnrelatedDataSource() throws Exception {
576 		Connection con2 = mock(Connection.class);
577 		final DataSource ds2 = mock(DataSource.class);
578 		given(ds2.getConnection()).willReturn(con2);
579 
580 		final TransactionTemplate tt = new TransactionTemplate(tm);
581 		tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
582 
583 		PlatformTransactionManager tm2 = new DataSourceTransactionManager(ds2);
584 		final TransactionTemplate tt2 = new TransactionTemplate(tm2);
585 		tt2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
586 
587 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
588 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds2));
589 		assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
590 
591 		tt.execute(new TransactionCallbackWithoutResult() {
592 			@Override
593 			protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
594 				assertTrue("Is new transaction", status.isNewTransaction());
595 				assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
596 				assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
597 				assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
598 				tt2.execute(new TransactionCallbackWithoutResult() {
599 					@Override
600 					protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
601 						assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
602 						assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
603 						assertTrue("Is new transaction", status.isNewTransaction());
604 						assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
605 						assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
606 						status.setRollbackOnly();
607 					}
608 				});
609 				assertTrue("Is new transaction", status.isNewTransaction());
610 				assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
611 				assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
612 			}
613 		});
614 
615 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
616 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds2));
617 		verify(con).commit();
618 		verify(con).close();
619 		verify(con2).rollback();
620 		verify(con2).close();
621 	}
622 
623 	@Test
624 	public void testPropagationRequiresNewWithExistingTransactionAndUnrelatedFailingDataSource() throws Exception {
625 		final DataSource ds2 = mock(DataSource.class);
626 		SQLException failure = new SQLException();
627 		given(ds2.getConnection()).willThrow(failure);
628 
629 
630 		final TransactionTemplate tt = new TransactionTemplate(tm);
631 		tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
632 
633 		DataSourceTransactionManager tm2 = new DataSourceTransactionManager(ds2);
634 		tm2.setTransactionSynchronization(DataSourceTransactionManager.SYNCHRONIZATION_NEVER);
635 		final TransactionTemplate tt2 = new TransactionTemplate(tm2);
636 		tt2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
637 
638 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
639 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds2));
640 		assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
641 
642 		try {
643 			tt.execute(new TransactionCallbackWithoutResult() {
644 				@Override
645 				protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
646 					assertTrue("Is new transaction", status.isNewTransaction());
647 					assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
648 					assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
649 					assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
650 					tt2.execute(new TransactionCallbackWithoutResult() {
651 						@Override
652 						protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
653 							status.setRollbackOnly();
654 						}
655 					});
656 				}
657 			});
658 			fail("Should have thrown CannotCreateTransactionException");
659 		}
660 		catch (CannotCreateTransactionException ex) {
661 			assertSame(failure, ex.getCause());
662 		}
663 
664 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
665 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds2));
666 		verify(con).rollback();
667 		verify(con).close();
668 	}
669 
670 	@Test
671 	public void testPropagationNotSupportedWithExistingTransaction() throws Exception {
672 		final TransactionTemplate tt = new TransactionTemplate(tm);
673 		tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
674 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
675 		assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
676 
677 		tt.execute(new TransactionCallbackWithoutResult() {
678 			@Override
679 			protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
680 				assertTrue("Is new transaction", status.isNewTransaction());
681 				assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
682 				assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
683 				tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
684 				tt.execute(new TransactionCallbackWithoutResult() {
685 					@Override
686 					protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
687 						assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
688 						assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
689 						assertTrue("Isn't new transaction", !status.isNewTransaction());
690 						assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
691 						assertFalse(TransactionSynchronizationManager.isActualTransactionActive());
692 						status.setRollbackOnly();
693 					}
694 				});
695 				assertTrue("Is new transaction", status.isNewTransaction());
696 				assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
697 				assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
698 			}
699 		});
700 
701 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
702 		verify(con).commit();
703 		verify(con).close();
704 	}
705 
706 	@Test
707 	public void testPropagationNeverWithExistingTransaction() throws Exception {
708 		final TransactionTemplate tt = new TransactionTemplate(tm);
709 		tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
710 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
711 		assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
712 
713 		try {
714 			tt.execute(new TransactionCallbackWithoutResult() {
715 				@Override
716 				protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
717 					assertTrue("Is new transaction", status.isNewTransaction());
718 					tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NEVER);
719 					tt.execute(new TransactionCallbackWithoutResult() {
720 						@Override
721 						protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
722 							fail("Should have thrown IllegalTransactionStateException");
723 						}
724 					});
725 					fail("Should have thrown IllegalTransactionStateException");
726 				}
727 			});
728 		}
729 		catch (IllegalTransactionStateException ex) {
730 			// expected
731 		}
732 
733 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
734 		verify(con).rollback();
735 		verify(con).close();
736 	}
737 
738 	@Test
739 	public void testPropagationSupportsAndRequiresNew() throws Exception {
740 		TransactionTemplate tt = new TransactionTemplate(tm);
741 		tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
742 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
743 		assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
744 
745 		tt.execute(new TransactionCallbackWithoutResult() {
746 			@Override
747 			protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
748 				assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
749 				TransactionTemplate tt2 = new TransactionTemplate(tm);
750 				tt2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
751 				tt2.execute(new TransactionCallbackWithoutResult() {
752 					@Override
753 					protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
754 						assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
755 						assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
756 						assertTrue("Is new transaction", status.isNewTransaction());
757 						assertSame(con, DataSourceUtils.getConnection(ds));
758 						assertSame(con, DataSourceUtils.getConnection(ds));
759 					}
760 				});
761 			}
762 		});
763 
764 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
765 		verify(con).commit();
766 		verify(con).close();
767 	}
768 
769 	@Test
770 	public void testPropagationSupportsAndRequiresNewWithEarlyAccess() throws Exception {
771 		final Connection con1 = mock(Connection.class);
772 		final Connection con2 = mock(Connection.class);
773 		given(ds.getConnection()).willReturn(con1, con2);
774 
775 		final
776 		TransactionTemplate tt = new TransactionTemplate(tm);
777 		tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
778 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
779 		assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
780 
781 		tt.execute(new TransactionCallbackWithoutResult() {
782 			@Override
783 			protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
784 				assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
785 				assertSame(con1, DataSourceUtils.getConnection(ds));
786 				assertSame(con1, DataSourceUtils.getConnection(ds));
787 				TransactionTemplate tt2 = new TransactionTemplate(tm);
788 				tt2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
789 				tt2.execute(new TransactionCallbackWithoutResult() {
790 					@Override
791 					protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
792 						assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
793 						assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
794 						assertTrue("Is new transaction", status.isNewTransaction());
795 						assertSame(con2, DataSourceUtils.getConnection(ds));
796 						assertSame(con2, DataSourceUtils.getConnection(ds));
797 					}
798 				});
799 				assertSame(con1, DataSourceUtils.getConnection(ds));
800 			}
801 		});
802 
803 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
804 		verify(con1).close();
805 		verify(con2).commit();
806 		verify(con2).close();
807 	}
808 
809 	@Test
810 	public void testTransactionWithIsolationAndReadOnly() throws Exception {
811 		given(con.getTransactionIsolation()).willReturn(Connection.TRANSACTION_READ_COMMITTED);
812 		given(con.getAutoCommit()).willReturn(true);
813 
814 		TransactionTemplate tt = new TransactionTemplate(tm);
815 		tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
816 		tt.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
817 		tt.setReadOnly(true);
818 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
819 		tt.execute(new TransactionCallbackWithoutResult() {
820 			@Override
821 			protected void doInTransactionWithoutResult(TransactionStatus status) {
822 				assertTrue(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
823 				assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
824 				// something transactional
825 			}
826 		});
827 
828 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
829 		InOrder ordered = inOrder(con);
830 		ordered.verify(con).setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
831 		ordered.verify(con).setAutoCommit(false);
832 		ordered.verify(con).commit();
833 		ordered.verify(con).setAutoCommit(true);
834 		ordered.verify(con).setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
835 		verify(con).close();
836 	}
837 
838 	@Test
839 	public void testTransactionWithLongTimeout() throws Exception {
840 		doTestTransactionWithTimeout(10);
841 	}
842 
843 	@Test
844 	public void testTransactionWithShortTimeout() throws Exception {
845 		doTestTransactionWithTimeout(1);
846 	}
847 
848 	private void doTestTransactionWithTimeout(int timeout) throws Exception {
849 		Assume.group(TestGroup.PERFORMANCE);
850 
851 		PreparedStatement ps = mock(PreparedStatement.class);
852 		given(con.getAutoCommit()).willReturn(true);
853 		given(con.prepareStatement("some SQL statement")).willReturn(ps);
854 
855 		TransactionTemplate tt = new TransactionTemplate(tm);
856 		tt.setTimeout(timeout);
857 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
858 
859 		try {
860 			tt.execute(new TransactionCallbackWithoutResult() {
861 				@Override
862 				protected void doInTransactionWithoutResult(TransactionStatus status) {
863 					try {
864 						Thread.sleep(1500);
865 					}
866 					catch (InterruptedException ex) {
867 					}
868 					try {
869 						Connection con = DataSourceUtils.getConnection(ds);
870 						PreparedStatement ps = con.prepareStatement("some SQL statement");
871 						DataSourceUtils.applyTransactionTimeout(ps, ds);
872 					}
873 					catch (SQLException ex) {
874 						throw new DataAccessResourceFailureException("", ex);
875 					}
876 				}
877 			});
878 			if (timeout <= 1) {
879 				fail("Should have thrown TransactionTimedOutException");
880 			}
881 		}
882 		catch (TransactionTimedOutException ex) {
883 			if (timeout <= 1) {
884 				// expected
885 			}
886 			else {
887 				throw ex;
888 			}
889 		}
890 
891 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
892 		if (timeout > 1) {
893 			verify(ps).setQueryTimeout(timeout - 1);
894 			verify(con).commit();
895 		}
896 		else {
897 			verify(con).rollback();
898 		}
899 		InOrder ordered = inOrder(con);
900 		ordered.verify(con).setAutoCommit(false);
901 		ordered.verify(con).setAutoCommit(true);
902 		verify(con).close();
903 
904 	}
905 
906 	@Test
907 	public void testTransactionAwareDataSourceProxy() throws Exception {
908 		given(con.getAutoCommit()).willReturn(true);
909 
910 		TransactionTemplate tt = new TransactionTemplate(tm);
911 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
912 		tt.execute(new TransactionCallbackWithoutResult() {
913 			@Override
914 			protected void doInTransactionWithoutResult(TransactionStatus status) {
915 				// something transactional
916 				assertEquals(con, DataSourceUtils.getConnection(ds));
917 				TransactionAwareDataSourceProxy dsProxy = new TransactionAwareDataSourceProxy(ds);
918 				try {
919 					assertEquals(con, ((ConnectionProxy) dsProxy.getConnection()).getTargetConnection());
920 					assertEquals(con, new SimpleNativeJdbcExtractor().getNativeConnection(dsProxy.getConnection()));
921 					// should be ignored
922 					dsProxy.getConnection().close();
923 				}
924 				catch (SQLException ex) {
925 					throw new UncategorizedSQLException("", "", ex);
926 				}
927 			}
928 		});
929 
930 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
931 		InOrder ordered = inOrder(con);
932 		ordered.verify(con).setAutoCommit(false);
933 		ordered.verify(con).commit();
934 		ordered.verify(con).setAutoCommit(true);
935 		verify(con).close();
936 	}
937 
938 	@Test
939 	public void testTransactionAwareDataSourceProxyWithSuspension() throws Exception {
940 		given(con.getAutoCommit()).willReturn(true);
941 
942 		final TransactionTemplate tt = new TransactionTemplate(tm);
943 		tt.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW);
944 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
945 
946 		tt.execute(new TransactionCallbackWithoutResult() {
947 			@Override
948 			protected void doInTransactionWithoutResult(TransactionStatus status) {
949 				// something transactional
950 				assertEquals(con, DataSourceUtils.getConnection(ds));
951 				final TransactionAwareDataSourceProxy dsProxy = new TransactionAwareDataSourceProxy(ds);
952 				try {
953 					assertEquals(con, ((ConnectionProxy) dsProxy.getConnection()).getTargetConnection());
954 					assertEquals(con, new SimpleNativeJdbcExtractor().getNativeConnection(dsProxy.getConnection()));
955 					// should be ignored
956 					dsProxy.getConnection().close();
957 				}
958 				catch (SQLException ex) {
959 					throw new UncategorizedSQLException("", "", ex);
960 				}
961 
962 				tt.execute(new TransactionCallbackWithoutResult() {
963 					@Override
964 					protected void doInTransactionWithoutResult(TransactionStatus status) {
965 						// something transactional
966 						assertEquals(con, DataSourceUtils.getConnection(ds));
967 						try {
968 							assertEquals(con, ((ConnectionProxy) dsProxy.getConnection()).getTargetConnection());
969 							assertEquals(con, new SimpleNativeJdbcExtractor().getNativeConnection(dsProxy.getConnection()));
970 							// should be ignored
971 							dsProxy.getConnection().close();
972 						}
973 						catch (SQLException ex) {
974 							throw new UncategorizedSQLException("", "", ex);
975 						}
976 					}
977 				});
978 
979 				try {
980 					assertEquals(con, ((ConnectionProxy) dsProxy.getConnection()).getTargetConnection());
981 					// should be ignored
982 					dsProxy.getConnection().close();
983 				}
984 				catch (SQLException ex) {
985 					throw new UncategorizedSQLException("", "", ex);
986 				}
987 			}
988 		});
989 
990 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
991 		InOrder ordered = inOrder(con);
992 		ordered.verify(con).setAutoCommit(false);
993 		ordered.verify(con).commit();
994 		ordered.verify(con).setAutoCommit(true);
995 		verify(con, times(2)).close();
996 	}
997 
998 	@Test
999 	public void testTransactionAwareDataSourceProxyWithSuspensionAndReobtaining() throws Exception {
1000 		given(con.getAutoCommit()).willReturn(true);
1001 
1002 		final TransactionTemplate tt = new TransactionTemplate(tm);
1003 		tt.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW);
1004 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1005 
1006 		tt.execute(new TransactionCallbackWithoutResult() {
1007 			@Override
1008 			protected void doInTransactionWithoutResult(TransactionStatus status) {
1009 				// something transactional
1010 				assertEquals(con, DataSourceUtils.getConnection(ds));
1011 				final TransactionAwareDataSourceProxy dsProxy = new TransactionAwareDataSourceProxy(ds);
1012 				dsProxy.setReobtainTransactionalConnections(true);
1013 				try {
1014 					assertEquals(con, ((ConnectionProxy) dsProxy.getConnection()).getTargetConnection());
1015 					assertEquals(con, new SimpleNativeJdbcExtractor().getNativeConnection(dsProxy.getConnection()));
1016 					// should be ignored
1017 					dsProxy.getConnection().close();
1018 				}
1019 				catch (SQLException ex) {
1020 					throw new UncategorizedSQLException("", "", ex);
1021 				}
1022 
1023 				tt.execute(new TransactionCallbackWithoutResult() {
1024 					@Override
1025 					protected void doInTransactionWithoutResult(TransactionStatus status) {
1026 						// something transactional
1027 						assertEquals(con, DataSourceUtils.getConnection(ds));
1028 						try {
1029 							assertEquals(con, ((ConnectionProxy) dsProxy.getConnection()).getTargetConnection());
1030 							assertEquals(con, new SimpleNativeJdbcExtractor().getNativeConnection(dsProxy.getConnection()));
1031 							// should be ignored
1032 							dsProxy.getConnection().close();
1033 						}
1034 						catch (SQLException ex) {
1035 							throw new UncategorizedSQLException("", "", ex);
1036 						}
1037 					}
1038 				});
1039 
1040 				try {
1041 					assertEquals(con, ((ConnectionProxy) dsProxy.getConnection()).getTargetConnection());
1042 					// should be ignored
1043 					dsProxy.getConnection().close();
1044 				}
1045 				catch (SQLException ex) {
1046 					throw new UncategorizedSQLException("", "", ex);
1047 				}
1048 			}
1049 		});
1050 
1051 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1052 		InOrder ordered = inOrder(con);
1053 		ordered.verify(con).setAutoCommit(false);
1054 		ordered.verify(con).commit();
1055 		ordered.verify(con).setAutoCommit(true);
1056 		verify(con, times(2)).close();
1057 	}
1058 
1059 	/**
1060 	 * Test behavior if the first operation on a connection (getAutoCommit) throws SQLException.
1061 	 */
1062 	@Test
1063 	public void testTransactionWithExceptionOnBegin() throws Exception {
1064 		willThrow(new SQLException("Cannot begin")).given(con).getAutoCommit();
1065 
1066 		TransactionTemplate tt = new TransactionTemplate(tm);
1067 		try {
1068 			tt.execute(new TransactionCallbackWithoutResult() {
1069 				@Override
1070 				protected void doInTransactionWithoutResult(TransactionStatus status) {
1071 					// something transactional
1072 				}
1073 			});
1074 			fail("Should have thrown CannotCreateTransactionException");
1075 		}
1076 		catch (CannotCreateTransactionException ex) {
1077 			// expected
1078 		}
1079 
1080 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1081 		verify(con).close();
1082 	}
1083 
1084 	@Test
1085 	public void testTransactionWithExceptionOnCommit() throws Exception {
1086 		willThrow(new SQLException("Cannot commit")).given(con).commit();
1087 
1088 		TransactionTemplate tt = new TransactionTemplate(tm);
1089 		try {
1090 			tt.execute(new TransactionCallbackWithoutResult() {
1091 				@Override
1092 				protected void doInTransactionWithoutResult(TransactionStatus status) {
1093 					// something transactional
1094 				}
1095 			});
1096 			fail("Should have thrown TransactionSystemException");
1097 		}
1098 		catch (TransactionSystemException ex) {
1099 			// expected
1100 		}
1101 
1102 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1103 		verify(con).close();
1104 	}
1105 
1106 	@Test
1107 	public void testTransactionWithExceptionOnCommitAndRollbackOnCommitFailure() throws Exception {
1108 		willThrow(new SQLException("Cannot commit")).given(con).commit();
1109 
1110 		tm.setRollbackOnCommitFailure(true);
1111 		TransactionTemplate tt = new TransactionTemplate(tm);
1112 		try {
1113 			tt.execute(new TransactionCallbackWithoutResult() {
1114 				@Override
1115 				protected void doInTransactionWithoutResult(TransactionStatus status) {
1116 					// something transactional
1117 				}
1118 			});
1119 			fail("Should have thrown TransactionSystemException");
1120 		}
1121 		catch (TransactionSystemException ex) {
1122 			// expected
1123 		}
1124 
1125 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1126 		verify(con).rollback();
1127 		verify(con).close();
1128 	}
1129 
1130 	@Test
1131 	public void testTransactionWithExceptionOnRollback() throws Exception {
1132 		given(con.getAutoCommit()).willReturn(true);
1133 		willThrow(new SQLException("Cannot rollback")).given(con).rollback();
1134 
1135 		TransactionTemplate tt = new TransactionTemplate(tm);
1136 		try {
1137 			tt.execute(new TransactionCallbackWithoutResult() {
1138 				@Override
1139 				protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
1140 					status.setRollbackOnly();
1141 				}
1142 			});
1143 			fail("Should have thrown TransactionSystemException");
1144 		}
1145 		catch (TransactionSystemException ex) {
1146 			// expected
1147 		}
1148 
1149 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1150 		InOrder ordered = inOrder(con);
1151 		ordered.verify(con).setAutoCommit(false);
1152 		ordered.verify(con).rollback();
1153 		ordered.verify(con).setAutoCommit(true);
1154 		verify(con).close();
1155 	}
1156 
1157 	@Test
1158 	public void testTransactionWithPropagationSupports() throws Exception {
1159 		TransactionTemplate tt = new TransactionTemplate(tm);
1160 		tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
1161 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1162 
1163 		tt.execute(new TransactionCallbackWithoutResult() {
1164 			@Override
1165 			protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
1166 				assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1167 				assertTrue("Is not new transaction", !status.isNewTransaction());
1168 				assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
1169 				assertFalse(TransactionSynchronizationManager.isActualTransactionActive());
1170 			}
1171 		});
1172 
1173 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1174 	}
1175 
1176 	@Test public void testTransactionWithPropagationNotSupported() throws Exception {
1177 		TransactionTemplate tt = new TransactionTemplate(tm);
1178 		tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
1179 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1180 
1181 		tt.execute(new TransactionCallbackWithoutResult() {
1182 			@Override
1183 			protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
1184 				assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1185 				assertTrue("Is not new transaction", !status.isNewTransaction());
1186 			}
1187 		});
1188 
1189 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1190 	}
1191 
1192 	@Test
1193 	public void testTransactionWithPropagationNever() throws Exception {
1194 		TransactionTemplate tt = new TransactionTemplate(tm);
1195 		tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NEVER);
1196 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1197 
1198 		tt.execute(new TransactionCallbackWithoutResult() {
1199 			@Override
1200 			protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
1201 				assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1202 				assertTrue("Is not new transaction", !status.isNewTransaction());
1203 			}
1204 		});
1205 
1206 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1207 	}
1208 
1209 	@Test
1210 	public void testExistingTransactionWithPropagationNested() throws Exception {
1211 		doTestExistingTransactionWithPropagationNested(1);
1212 	}
1213 
1214 	@Test
1215 	public void testExistingTransactionWithPropagationNestedTwice() throws Exception {
1216 		doTestExistingTransactionWithPropagationNested(2);
1217 	}
1218 
1219 	private void doTestExistingTransactionWithPropagationNested(final int count) throws Exception {
1220 		DatabaseMetaData md = mock(DatabaseMetaData.class);
1221 		Savepoint sp = mock(Savepoint.class);
1222 
1223 		given(md.supportsSavepoints()).willReturn(true);
1224 		given(con.getMetaData()).willReturn(md);
1225 		for (int i = 1; i <= count; i++) {
1226 			given(con.setSavepoint(ConnectionHolder.SAVEPOINT_NAME_PREFIX + i)).willReturn(sp);
1227 		}
1228 
1229 		final TransactionTemplate tt = new TransactionTemplate(tm);
1230 		tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED);
1231 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1232 		assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
1233 
1234 		tt.execute(new TransactionCallbackWithoutResult() {
1235 			@Override
1236 			protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
1237 				assertTrue("Is new transaction", status.isNewTransaction());
1238 				assertTrue("Isn't nested transaction", !status.hasSavepoint());
1239 				for (int i = 0; i < count; i++) {
1240 					tt.execute(new TransactionCallbackWithoutResult() {
1241 						@Override
1242 						protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
1243 							assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
1244 							assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
1245 							assertTrue("Isn't new transaction", !status.isNewTransaction());
1246 							assertTrue("Is nested transaction", status.hasSavepoint());
1247 						}
1248 					});
1249 				}
1250 				assertTrue("Is new transaction", status.isNewTransaction());
1251 				assertTrue("Isn't nested transaction", !status.hasSavepoint());
1252 			}
1253 		});
1254 
1255 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1256 		verify(con, times(count)).releaseSavepoint(sp);
1257 		verify(con).commit();
1258 		verify(con).close();
1259 	}
1260 
1261 	@Test
1262 	public void testExistingTransactionWithPropagationNestedAndRollback() throws Exception {
1263 		DatabaseMetaData md = mock(DatabaseMetaData.class);
1264 		Savepoint sp = mock(Savepoint.class);
1265 
1266 		given(md.supportsSavepoints()).willReturn(true);
1267 		given(con.getMetaData()).willReturn(md);
1268 		given(con.setSavepoint("SAVEPOINT_1")).willReturn(sp);
1269 
1270 		final TransactionTemplate tt = new TransactionTemplate(tm);
1271 		tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED);
1272 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1273 		assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
1274 
1275 		tt.execute(new TransactionCallbackWithoutResult() {
1276 			@Override
1277 			protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
1278 				assertTrue("Is new transaction", status.isNewTransaction());
1279 				assertTrue("Isn't nested transaction", !status.hasSavepoint());
1280 				tt.execute(new TransactionCallbackWithoutResult() {
1281 					@Override
1282 					protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
1283 						assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
1284 						assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
1285 						assertTrue("Isn't new transaction", !status.isNewTransaction());
1286 						assertTrue("Is nested transaction", status.hasSavepoint());
1287 						status.setRollbackOnly();
1288 					}
1289 				});
1290 				assertTrue("Is new transaction", status.isNewTransaction());
1291 				assertTrue("Isn't nested transaction", !status.hasSavepoint());
1292 			}
1293 		});
1294 
1295 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1296 		verify(con).rollback(sp);
1297 		verify(con).releaseSavepoint(sp);
1298 		verify(con).commit();
1299 		verify(con).isReadOnly();
1300 		verify(con).close();
1301 	}
1302 
1303 	@Test
1304 	public void testExistingTransactionWithManualSavepoint() throws Exception {
1305 		DatabaseMetaData md = mock(DatabaseMetaData.class);
1306 		Savepoint sp = mock(Savepoint.class);
1307 
1308 		given(md.supportsSavepoints()).willReturn(true);
1309 		given(con.getMetaData()).willReturn(md);
1310 		given(con.setSavepoint("SAVEPOINT_1")).willReturn(sp);
1311 
1312 		final TransactionTemplate tt = new TransactionTemplate(tm);
1313 		tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED);
1314 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1315 		assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
1316 
1317 		tt.execute(new TransactionCallbackWithoutResult() {
1318 			@Override
1319 			protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
1320 				assertTrue("Is new transaction", status.isNewTransaction());
1321 				Object savepoint = status.createSavepoint();
1322 				status.releaseSavepoint(savepoint);
1323 				assertTrue("Is new transaction", status.isNewTransaction());
1324 			}
1325 		});
1326 
1327 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1328 		verify(con).releaseSavepoint(sp);
1329 		verify(con).commit();
1330 		verify(con).close();
1331 		verify(ds).getConnection();
1332 	}
1333 
1334 	@Test
1335 	public void testExistingTransactionWithManualSavepointAndRollback() throws Exception {
1336 		DatabaseMetaData md = mock(DatabaseMetaData.class);
1337 		Savepoint sp = mock(Savepoint.class);
1338 
1339 		given(md.supportsSavepoints()).willReturn(true);
1340 		given(con.getMetaData()).willReturn(md);
1341 		given(con.setSavepoint("SAVEPOINT_1")).willReturn(sp);
1342 
1343 		final TransactionTemplate tt = new TransactionTemplate(tm);
1344 		tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED);
1345 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1346 		assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
1347 
1348 		tt.execute(new TransactionCallbackWithoutResult() {
1349 			@Override
1350 			protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
1351 				assertTrue("Is new transaction", status.isNewTransaction());
1352 				Object savepoint = status.createSavepoint();
1353 				status.rollbackToSavepoint(savepoint);
1354 				assertTrue("Is new transaction", status.isNewTransaction());
1355 			}
1356 		});
1357 
1358 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1359 		verify(con).rollback(sp);
1360 		verify(con).commit();
1361 		verify(con).close();
1362 	}
1363 
1364 	@Test
1365 	public void testTransactionWithPropagationNested() throws Exception {
1366 		final TransactionTemplate tt = new TransactionTemplate(tm);
1367 		tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED);
1368 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1369 		assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
1370 
1371 		tt.execute(new TransactionCallbackWithoutResult() {
1372 			@Override
1373 			protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
1374 				assertTrue("Is new transaction", status.isNewTransaction());
1375 			}
1376 		});
1377 
1378 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1379 		verify(con).commit();
1380 		verify(con).close();
1381 	}
1382 
1383 	@Test
1384 	public void testTransactionWithPropagationNestedAndRollback() throws Exception {
1385 		final TransactionTemplate tt = new TransactionTemplate(tm);
1386 		tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED);
1387 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1388 		assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
1389 
1390 		tt.execute(new TransactionCallbackWithoutResult() {
1391 			@Override
1392 			protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
1393 				assertTrue("Is new transaction", status.isNewTransaction());
1394 				status.setRollbackOnly();
1395 			}
1396 		});
1397 
1398 		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
1399 		verify(con).rollback();
1400 		verify(con).close();
1401 	}
1402 
1403 
1404 	private static class TestTransactionSynchronization implements TransactionSynchronization {
1405 
1406 		private DataSource dataSource;
1407 
1408 		private int status;
1409 
1410 		public boolean beforeCommitCalled;
1411 
1412 		public boolean beforeCompletionCalled;
1413 
1414 		public boolean afterCommitCalled;
1415 
1416 		public boolean afterCompletionCalled;
1417 
1418 		public TestTransactionSynchronization(DataSource dataSource, int status) {
1419 			this.dataSource = dataSource;
1420 			this.status = status;
1421 		}
1422 
1423 		@Override
1424 		public void suspend() {
1425 		}
1426 
1427 		@Override
1428 		public void resume() {
1429 		}
1430 
1431 		@Override
1432 		public void flush() {
1433 		}
1434 
1435 		@Override
1436 		public void beforeCommit(boolean readOnly) {
1437 			if (this.status != TransactionSynchronization.STATUS_COMMITTED) {
1438 				fail("Should never be called");
1439 			}
1440 			assertFalse(this.beforeCommitCalled);
1441 			this.beforeCommitCalled = true;
1442 		}
1443 
1444 		@Override
1445 		public void beforeCompletion() {
1446 			assertFalse(this.beforeCompletionCalled);
1447 			this.beforeCompletionCalled = true;
1448 		}
1449 
1450 		@Override
1451 		public void afterCommit() {
1452 			if (this.status != TransactionSynchronization.STATUS_COMMITTED) {
1453 				fail("Should never be called");
1454 			}
1455 			assertFalse(this.afterCommitCalled);
1456 			this.afterCommitCalled = true;
1457 		}
1458 
1459 		@Override
1460 		public void afterCompletion(int status) {
1461 			assertFalse(this.afterCompletionCalled);
1462 			this.afterCompletionCalled = true;
1463 			assertTrue(status == this.status);
1464 			assertTrue(TransactionSynchronizationManager.hasResource(this.dataSource));
1465 		}
1466 	}
1467 
1468 }