1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.mybatis.guice.transactional;
17
18 import static java.lang.String.format;
19
20 import jakarta.ejb.ApplicationException;
21 import jakarta.inject.Inject;
22 import jakarta.inject.Provider;
23 import jakarta.transaction.TransactionManager;
24
25 import java.lang.annotation.Annotation;
26 import java.lang.reflect.Method;
27 import java.util.HashMap;
28 import java.util.Map;
29
30 import javax.transaction.xa.XAResource;
31
32 import org.aopalliance.intercept.MethodInterceptor;
33 import org.aopalliance.intercept.MethodInvocation;
34 import org.apache.ibatis.logging.Log;
35 import org.apache.ibatis.logging.LogFactory;
36 import org.mybatis.guice.transactional.Transactional.TxType;
37
38
39
40
41 public class TxTransactionalMethodInterceptor implements MethodInterceptor {
42
43
44
45 private final Log log = LogFactory.getLog(getClass());
46
47 @Inject
48 private TransactionManager manager;
49
50 @Inject
51 private Provider<XAResource> xaResourceProvider;
52
53 private Map<TxType, TransactionAttributeStrategy> strategies = new HashMap<>();
54
55 public TxTransactionalMethodInterceptor() {
56 strategies.put(TxType.REQUIRED, new RequiredTransactionAttributeStrategy());
57 strategies.put(TxType.REQUIRES_NEW, new RequiresNewTransactionAttributeStrategy());
58 strategies.put(TxType.MANDATORY, new MandatoryTransactionAttributeStrategy());
59 strategies.put(TxType.SUPPORTS, new SupportsTransactionAttributeStrategy());
60 strategies.put(TxType.NEVER, new NeverTransactionAttributeStrategy());
61 }
62
63 private boolean isApplicationExceptionAvailable() {
64 try {
65 Class.forName("jakarta.ejb.ApplicationException");
66 return true;
67 } catch (ClassNotFoundException e) {
68 return false;
69 }
70 }
71
72 private <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationClass) {
73 Class<?> current = clazz;
74 A annotation = null;
75 while (annotation == null && current != null) {
76 annotation = current.getAnnotation(annotationClass);
77 current = current.getSuperclass();
78 }
79 return annotation;
80 }
81
82
83
84
85 @Override
86 public Object invoke(MethodInvocation invocation) throws Throwable {
87 Method interceptedMethod = invocation.getMethod();
88 Transactional transactional = interceptedMethod.getAnnotation(Transactional.class);
89
90
91 if (transactional == null) {
92 transactional = interceptedMethod.getDeclaringClass().getAnnotation(Transactional.class);
93 }
94
95 String debugPrefix = null;
96 if (this.log.isDebugEnabled()) {
97 debugPrefix = String.format("[Intercepted method: %s]", interceptedMethod.toGenericString());
98 }
99
100 boolean needsRollback = transactional.rollbackOnly();
101 Object object = null;
102 TransactionAttribute attribute = null;
103
104 if (manager != null) {
105 TxType txType = transactional.value();
106 TransactionAttributeStrategy strategy = strategies.get(txType);
107 if (strategy != null) {
108 attribute = strategy.getTransactionAttribute();
109 }
110 }
111
112 if (attribute == null) {
113 if (log.isDebugEnabled()) {
114 log.debug(format("%s - skip Tx Transaction", debugPrefix));
115 }
116
117
118 try {
119 object = invocation.proceed();
120 } catch (Throwable t) {
121 throw t;
122 }
123 } else {
124 if (log.isDebugEnabled()) {
125 log.debug(format("%s - Tx Transaction %s begin", debugPrefix, attribute.name()));
126 }
127
128
129 TransactionToken tranToken = attribute.begin(manager);
130
131 log.debug("enlistResource XASqlSessionManager");
132 XAResource xaRes = xaResourceProvider.get();
133 tranToken.getActiveTransaction().enlistResource(xaRes);
134
135 try {
136 if (log.isDebugEnabled()) {
137 log.debug(format("%s - Tx Transaction %s (CompletionAllowed %s) call method", debugPrefix, attribute.name(),
138 tranToken.isCompletionAllowed()));
139 }
140 object = invocation.proceed();
141
142 if (needsRollback) {
143 manager.setRollbackOnly();
144 }
145
146 } catch (Throwable t) {
147 if (log.isDebugEnabled()) {
148 log.debug(format("%s - Tx Transaction %s (CompletionAllowed %s) rolling back", debugPrefix, attribute.name(),
149 tranToken.isCompletionAllowed()));
150 }
151 if (isApplicationExceptionAvailable()) {
152 ApplicationException ae = t.getClass().getAnnotation(ApplicationException.class);
153 ApplicationException parentAe = findAnnotation(t.getClass().getSuperclass(), ApplicationException.class);
154 boolean bothAEsNull = (ae == null && parentAe == null);
155 boolean aeNotNullAndRollback = (ae != null && ae.rollback());
156 boolean parentAeRollbackConditions = false;
157 if (parentAe != null) {
158 parentAeRollbackConditions = (!parentAe.inherited() || parentAe.rollback());
159 }
160 boolean shouldTriggerRollback = (bothAEsNull || aeNotNullAndRollback || parentAeRollbackConditions);
161
162 if (shouldTriggerRollback) {
163 manager.setRollbackOnly();
164 }
165 } else {
166 manager.setRollbackOnly();
167 }
168 throw t;
169 } finally {
170 if (log.isDebugEnabled()) {
171 log.debug(format("%s - Tx Transaction %s (CompletionAllowed %s) finish", debugPrefix, attribute.name(),
172 tranToken.isCompletionAllowed()));
173 }
174 attribute.finish(manager, tranToken);
175 }
176 }
177 return object;
178 }
179
180 }