1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.transaction.interceptor;
18
19 import java.lang.reflect.Method;
20
21 import org.junit.Test;
22
23 import org.springframework.dao.OptimisticLockingFailureException;
24 import org.springframework.tests.sample.beans.ITestBean;
25 import org.springframework.tests.sample.beans.TestBean;
26 import org.springframework.transaction.CannotCreateTransactionException;
27 import org.springframework.transaction.MockCallbackPreferringTransactionManager;
28 import org.springframework.transaction.NoTransactionException;
29 import org.springframework.transaction.PlatformTransactionManager;
30 import org.springframework.transaction.TransactionDefinition;
31 import org.springframework.transaction.TransactionStatus;
32 import org.springframework.transaction.TransactionSystemException;
33 import org.springframework.transaction.UnexpectedRollbackException;
34 import org.springframework.transaction.interceptor.TransactionAspectSupport.TransactionInfo;
35
36 import static org.junit.Assert.*;
37 import static org.mockito.BDDMockito.*;
38
39
40
41
42
43
44
45
46
47
48
49
50
51 public abstract class AbstractTransactionAspectTests {
52
53 protected Method exceptionalMethod;
54
55 protected Method getNameMethod;
56
57 protected Method setNameMethod;
58
59
60 public AbstractTransactionAspectTests() {
61 try {
62
63 exceptionalMethod = ITestBean.class.getMethod("exceptional", new Class[] { Throwable.class });
64 getNameMethod = ITestBean.class.getMethod("getName", (Class[]) null);
65 setNameMethod = ITestBean.class.getMethod("setName", new Class[] { String.class} );
66 }
67 catch (NoSuchMethodException ex) {
68 throw new RuntimeException("Shouldn't happen", ex);
69 }
70 }
71
72
73 @Test
74 public void noTransaction() throws Exception {
75 PlatformTransactionManager ptm = mock(PlatformTransactionManager.class);
76
77 TestBean tb = new TestBean();
78 TransactionAttributeSource tas = new MapTransactionAttributeSource();
79
80
81
82
83 ITestBean itb = (ITestBean) advised(tb, ptm, tas);
84
85 checkTransactionStatus(false);
86 itb.getName();
87 checkTransactionStatus(false);
88
89
90 verifyZeroInteractions(ptm);
91 }
92
93
94
95
96 @Test
97 public void transactionShouldSucceed() throws Exception {
98 TransactionAttribute txatt = new DefaultTransactionAttribute();
99
100 MapTransactionAttributeSource tas = new MapTransactionAttributeSource();
101 tas.register(getNameMethod, txatt);
102
103 TransactionStatus status = mock(TransactionStatus.class);
104 PlatformTransactionManager ptm = mock(PlatformTransactionManager.class);
105
106 given(ptm.getTransaction(txatt)).willReturn(status);
107
108 TestBean tb = new TestBean();
109 ITestBean itb = (ITestBean) advised(tb, ptm, tas);
110
111 checkTransactionStatus(false);
112 itb.getName();
113 checkTransactionStatus(false);
114
115 verify(ptm).commit(status);
116 }
117
118
119
120
121
122 @Test
123 public void transactionShouldSucceedWithCallbackPreference() throws Exception {
124 TransactionAttribute txatt = new DefaultTransactionAttribute();
125
126 MapTransactionAttributeSource tas = new MapTransactionAttributeSource();
127 tas.register(getNameMethod, txatt);
128
129 MockCallbackPreferringTransactionManager ptm = new MockCallbackPreferringTransactionManager();
130
131 TestBean tb = new TestBean();
132 ITestBean itb = (ITestBean) advised(tb, ptm, tas);
133
134 checkTransactionStatus(false);
135 itb.getName();
136 checkTransactionStatus(false);
137
138 assertSame(txatt, ptm.getDefinition());
139 assertFalse(ptm.getStatus().isRollbackOnly());
140 }
141
142 @Test
143 public void transactionExceptionPropagatedWithCallbackPreference() throws Throwable {
144 TransactionAttribute txatt = new DefaultTransactionAttribute();
145
146 MapTransactionAttributeSource tas = new MapTransactionAttributeSource();
147 tas.register(exceptionalMethod, txatt);
148
149 MockCallbackPreferringTransactionManager ptm = new MockCallbackPreferringTransactionManager();
150
151 TestBean tb = new TestBean();
152 ITestBean itb = (ITestBean) advised(tb, ptm, tas);
153
154 checkTransactionStatus(false);
155 try {
156 itb.exceptional(new OptimisticLockingFailureException(""));
157 fail("Should have thrown OptimisticLockingFailureException");
158 }
159 catch (OptimisticLockingFailureException ex) {
160
161 }
162 checkTransactionStatus(false);
163
164 assertSame(txatt, ptm.getDefinition());
165 assertFalse(ptm.getStatus().isRollbackOnly());
166 }
167
168
169
170
171 @Test
172 public void twoTransactionsShouldSucceed() throws Exception {
173 TransactionAttribute txatt = new DefaultTransactionAttribute();
174
175 MapTransactionAttributeSource tas1 = new MapTransactionAttributeSource();
176 tas1.register(getNameMethod, txatt);
177 MapTransactionAttributeSource tas2 = new MapTransactionAttributeSource();
178 tas2.register(setNameMethod, txatt);
179
180 TransactionStatus status = mock(TransactionStatus.class);
181 PlatformTransactionManager ptm = mock(PlatformTransactionManager.class);
182
183 given(ptm.getTransaction(txatt)).willReturn(status);
184
185 TestBean tb = new TestBean();
186 ITestBean itb = (ITestBean) advised(tb, ptm, new TransactionAttributeSource[] {tas1, tas2});
187
188 checkTransactionStatus(false);
189 itb.getName();
190 checkTransactionStatus(false);
191 itb.setName("myName");
192 checkTransactionStatus(false);
193
194 verify(ptm, times(2)).commit(status);
195 }
196
197
198
199
200 @Test
201 public void transactionShouldSucceedWithNotNew() throws Exception {
202 TransactionAttribute txatt = new DefaultTransactionAttribute();
203
204 MapTransactionAttributeSource tas = new MapTransactionAttributeSource();
205 tas.register(getNameMethod, txatt);
206
207 TransactionStatus status = mock(TransactionStatus.class);
208 PlatformTransactionManager ptm = mock(PlatformTransactionManager.class);
209
210 given(ptm.getTransaction(txatt)).willReturn(status);
211
212 TestBean tb = new TestBean();
213 ITestBean itb = (ITestBean) advised(tb, ptm, tas);
214
215 checkTransactionStatus(false);
216
217 itb.getName();
218 checkTransactionStatus(false);
219
220 verify(ptm).commit(status);
221 }
222
223 @Test
224 public void enclosingTransactionWithNonTransactionMethodOnAdvisedInside() throws Throwable {
225 TransactionAttribute txatt = new DefaultTransactionAttribute();
226
227 MapTransactionAttributeSource tas = new MapTransactionAttributeSource();
228 tas.register(exceptionalMethod, txatt);
229
230 TransactionStatus status = mock(TransactionStatus.class);
231 PlatformTransactionManager ptm = mock(PlatformTransactionManager.class);
232
233 given(ptm.getTransaction(txatt)).willReturn(status);
234
235 final String spouseName = "innerName";
236
237 TestBean outer = new TestBean() {
238 @Override
239 public void exceptional(Throwable t) throws Throwable {
240 TransactionInfo ti = TransactionAspectSupport.currentTransactionInfo();
241 assertTrue(ti.hasTransaction());
242 assertEquals(spouseName, getSpouse().getName());
243 }
244 };
245 TestBean inner = new TestBean() {
246 @Override
247 public String getName() {
248
249 TransactionInfo ti = TransactionAspectSupport.currentTransactionInfo();
250 assertFalse(ti.hasTransaction());
251 return spouseName;
252 }
253 };
254
255 ITestBean outerProxy = (ITestBean) advised(outer, ptm, tas);
256 ITestBean innerProxy = (ITestBean) advised(inner, ptm, tas);
257 outer.setSpouse(innerProxy);
258
259 checkTransactionStatus(false);
260
261
262 outerProxy.exceptional(null);
263
264 checkTransactionStatus(false);
265
266 verify(ptm).commit(status);
267 }
268
269 @Test
270 public void enclosingTransactionWithNestedTransactionOnAdvisedInside() throws Throwable {
271 final TransactionAttribute outerTxatt = new DefaultTransactionAttribute();
272 final TransactionAttribute innerTxatt = new DefaultTransactionAttribute(TransactionDefinition.PROPAGATION_NESTED);
273
274 Method outerMethod = exceptionalMethod;
275 Method innerMethod = getNameMethod;
276 MapTransactionAttributeSource tas = new MapTransactionAttributeSource();
277 tas.register(outerMethod, outerTxatt);
278 tas.register(innerMethod, innerTxatt);
279
280 TransactionStatus outerStatus = mock(TransactionStatus.class);
281 TransactionStatus innerStatus = mock(TransactionStatus.class);
282
283 PlatformTransactionManager ptm = mock(PlatformTransactionManager.class);
284
285 given(ptm.getTransaction(outerTxatt)).willReturn(outerStatus);
286 given(ptm.getTransaction(innerTxatt)).willReturn(innerStatus);
287
288 final String spouseName = "innerName";
289
290 TestBean outer = new TestBean() {
291 @Override
292 public void exceptional(Throwable t) throws Throwable {
293 TransactionInfo ti = TransactionAspectSupport.currentTransactionInfo();
294 assertTrue(ti.hasTransaction());
295 assertEquals(outerTxatt, ti.getTransactionAttribute());
296 assertEquals(spouseName, getSpouse().getName());
297 }
298 };
299 TestBean inner = new TestBean() {
300 @Override
301 public String getName() {
302
303 TransactionInfo ti = TransactionAspectSupport.currentTransactionInfo();
304
305 assertTrue(ti.hasTransaction());
306 assertEquals(innerTxatt, ti.getTransactionAttribute());
307 return spouseName;
308 }
309 };
310
311 ITestBean outerProxy = (ITestBean) advised(outer, ptm, tas);
312 ITestBean innerProxy = (ITestBean) advised(inner, ptm, tas);
313 outer.setSpouse(innerProxy);
314
315 checkTransactionStatus(false);
316
317
318 outerProxy.exceptional(null);
319
320 checkTransactionStatus(false);
321
322 verify(ptm).commit(innerStatus);
323 verify(ptm).commit(outerStatus);
324 }
325
326 @Test
327 public void rollbackOnCheckedException() throws Throwable {
328 doTestRollbackOnException(new Exception(), true, false);
329 }
330
331 @Test
332 public void noRollbackOnCheckedException() throws Throwable {
333 doTestRollbackOnException(new Exception(), false, false);
334 }
335
336 @Test
337 public void rollbackOnUncheckedException() throws Throwable {
338 doTestRollbackOnException(new RuntimeException(), true, false);
339 }
340
341 @Test
342 public void noRollbackOnUncheckedException() throws Throwable {
343 doTestRollbackOnException(new RuntimeException(), false, false);
344 }
345
346 @Test
347 public void rollbackOnCheckedExceptionWithRollbackException() throws Throwable {
348 doTestRollbackOnException(new Exception(), true, true);
349 }
350
351 @Test
352 public void noRollbackOnCheckedExceptionWithRollbackException() throws Throwable {
353 doTestRollbackOnException(new Exception(), false, true);
354 }
355
356 @Test
357 public void rollbackOnUncheckedExceptionWithRollbackException() throws Throwable {
358 doTestRollbackOnException(new RuntimeException(), true, true);
359 }
360
361 @Test
362 public void noRollbackOnUncheckedExceptionWithRollbackException() throws Throwable {
363 doTestRollbackOnException(new RuntimeException(), false, true);
364 }
365
366
367
368
369
370
371
372 @SuppressWarnings("serial")
373 protected void doTestRollbackOnException(
374 final Exception ex, final boolean shouldRollback, boolean rollbackException) throws Exception {
375
376 TransactionAttribute txatt = new DefaultTransactionAttribute() {
377 @Override
378 public boolean rollbackOn(Throwable t) {
379 assertTrue(t == ex);
380 return shouldRollback;
381 }
382 };
383
384 Method m = exceptionalMethod;
385 MapTransactionAttributeSource tas = new MapTransactionAttributeSource();
386 tas.register(m, txatt);
387
388 TransactionStatus status = mock(TransactionStatus.class);
389 PlatformTransactionManager ptm = mock(PlatformTransactionManager.class);
390
391
392 given(ptm.getTransaction(txatt)).willReturn(status);
393
394 TransactionSystemException tex = new TransactionSystemException("system exception");
395 if (rollbackException) {
396 if (shouldRollback) {
397 willThrow(tex).given(ptm).rollback(status);
398 }
399 else {
400 willThrow(tex).given(ptm).commit(status);
401 }
402 }
403
404 TestBean tb = new TestBean();
405 ITestBean itb = (ITestBean) advised(tb, ptm, tas);
406
407 try {
408 itb.exceptional(ex);
409 fail("Should have thrown exception");
410 }
411 catch (Throwable t) {
412 if (rollbackException) {
413 assertEquals("Caught wrong exception", tex, t );
414 }
415 else {
416 assertEquals("Caught wrong exception", ex, t);
417 }
418 }
419
420 if (!rollbackException) {
421 if (shouldRollback) {
422 verify(ptm).rollback(status);
423 }
424 else {
425 verify(ptm).commit(status);
426 }
427 }
428 }
429
430
431
432
433 @Test
434 public void programmaticRollback() throws Exception {
435 TransactionAttribute txatt = new DefaultTransactionAttribute();
436
437 Method m = getNameMethod;
438 MapTransactionAttributeSource tas = new MapTransactionAttributeSource();
439 tas.register(m, txatt);
440
441 TransactionStatus status = mock(TransactionStatus.class);
442 PlatformTransactionManager ptm = mock(PlatformTransactionManager.class);
443
444 given(ptm.getTransaction(txatt)).willReturn(status);
445
446 final String name = "jenny";
447 TestBean tb = new TestBean() {
448 @Override
449 public String getName() {
450 TransactionStatus txStatus = TransactionInterceptor.currentTransactionStatus();
451 txStatus.setRollbackOnly();
452 return name;
453 }
454 };
455
456 ITestBean itb = (ITestBean) advised(tb, ptm, tas);
457
458
459 assertTrue(name.equals(itb.getName()));
460
461 verify(ptm).commit(status);
462 }
463
464
465
466
467
468 @Test
469 public void cannotCreateTransaction() throws Exception {
470 TransactionAttribute txatt = new DefaultTransactionAttribute();
471
472 Method m = getNameMethod;
473 MapTransactionAttributeSource tas = new MapTransactionAttributeSource();
474 tas.register(m, txatt);
475
476 PlatformTransactionManager ptm = mock(PlatformTransactionManager.class);
477
478 CannotCreateTransactionException ex = new CannotCreateTransactionException("foobar", null);
479 given(ptm.getTransaction(txatt)).willThrow(ex);
480
481 TestBean tb = new TestBean() {
482 @Override
483 public String getName() {
484 throw new UnsupportedOperationException(
485 "Shouldn't have invoked target method when couldn't create transaction for transactional method");
486 }
487 };
488 ITestBean itb = (ITestBean) advised(tb, ptm, tas);
489
490 try {
491 itb.getName();
492 fail("Shouldn't have invoked method");
493 }
494 catch (CannotCreateTransactionException thrown) {
495 assertTrue(thrown == ex);
496 }
497 }
498
499
500
501
502
503
504 @Test
505 public void cannotCommitTransaction() throws Exception {
506 TransactionAttribute txatt = new DefaultTransactionAttribute();
507
508 Method m = setNameMethod;
509 MapTransactionAttributeSource tas = new MapTransactionAttributeSource();
510 tas.register(m, txatt);
511
512
513
514 PlatformTransactionManager ptm = mock(PlatformTransactionManager.class);
515
516 TransactionStatus status = mock(TransactionStatus.class);
517 given(ptm.getTransaction(txatt)).willReturn(status);
518 UnexpectedRollbackException ex = new UnexpectedRollbackException("foobar", null);
519 willThrow(ex).given(ptm).commit(status);
520
521 TestBean tb = new TestBean();
522 ITestBean itb = (ITestBean) advised(tb, ptm, tas);
523
524 String name = "new name";
525 try {
526 itb.setName(name);
527 fail("Shouldn't have succeeded");
528 }
529 catch (UnexpectedRollbackException thrown) {
530 assertTrue(thrown == ex);
531 }
532
533
534 assertTrue(itb.getName() == name);
535 }
536
537 protected void checkTransactionStatus(boolean expected) {
538 try {
539 TransactionInterceptor.currentTransactionStatus();
540 if (!expected) {
541 fail("Should have thrown NoTransactionException");
542 }
543 }
544 catch (NoTransactionException ex) {
545 if (expected) {
546 fail("Should have current TransactionStatus");
547 }
548 }
549 }
550
551
552 protected Object advised(
553 Object target, PlatformTransactionManager ptm, TransactionAttributeSource[] tas) throws Exception {
554
555 return advised(target, ptm, new CompositeTransactionAttributeSource(tas));
556 }
557
558
559
560
561
562
563
564
565
566
567
568 protected abstract Object advised(
569 Object target, PlatformTransactionManager ptm, TransactionAttributeSource tas) throws Exception;
570
571 }