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 import static java.lang.Thread.currentThread;
20
21 import jakarta.inject.Inject;
22
23 import java.lang.reflect.Constructor;
24 import java.lang.reflect.Method;
25 import java.util.Arrays;
26
27 import org.aopalliance.intercept.MethodInterceptor;
28 import org.aopalliance.intercept.MethodInvocation;
29 import org.apache.ibatis.logging.Log;
30 import org.apache.ibatis.logging.LogFactory;
31 import org.apache.ibatis.session.SqlSessionManager;
32
33
34
35
36 public final class TransactionalMethodInterceptor implements MethodInterceptor {
37
38 private static final Class<?>[] CAUSE_TYPES = new Class[] { Throwable.class };
39
40 private static final Class<?>[] MESSAGE_CAUSE_TYPES = new Class[] { String.class, Throwable.class };
41
42
43
44
45 private final Log log = LogFactory.getLog(getClass());
46
47
48
49
50 @Inject
51 private SqlSessionManager sqlSessionManager;
52
53
54
55
56
57
58
59 public void setSqlSessionManager(SqlSessionManager sqlSessionManager) {
60 this.sqlSessionManager = sqlSessionManager;
61 }
62
63
64
65
66 @Override
67 public Object invoke(MethodInvocation invocation) throws Throwable {
68 Method interceptedMethod = invocation.getMethod();
69 Transactional transactional = interceptedMethod.getAnnotation(Transactional.class);
70
71
72 if (transactional == null) {
73 transactional = interceptedMethod.getDeclaringClass().getAnnotation(Transactional.class);
74 }
75
76 String debugPrefix = null;
77 if (this.log.isDebugEnabled()) {
78 debugPrefix = format("[Intercepted method: %s]", interceptedMethod.toGenericString());
79 }
80
81 boolean isSessionInherited = this.sqlSessionManager.isManagedSessionStarted();
82
83 if (isSessionInherited) {
84 if (log.isDebugEnabled()) {
85 log.debug(format("%s - SqlSession already set for thread: %s", debugPrefix, currentThread().getId()));
86 }
87 } else {
88 if (log.isDebugEnabled()) {
89 log.debug(
90 format("%s - SqlSession not set for thread: %s, creating a new one", debugPrefix, currentThread().getId()));
91 }
92
93 sqlSessionManager.startManagedSession(transactional.executorType(),
94 transactional.isolation().getTransactionIsolationLevel());
95 }
96
97 Object object = null;
98 boolean needsRollback = transactional.rollbackOnly();
99 try {
100 object = invocation.proceed();
101 } catch (Throwable t) {
102 needsRollback = true;
103 throw convertThrowableIfNeeded(invocation, transactional, t);
104 } finally {
105 if (!isSessionInherited) {
106 try {
107 if (needsRollback) {
108 if (log.isDebugEnabled()) {
109 log.debug(debugPrefix + " - SqlSession of thread: " + currentThread().getId() + " rolling back");
110 }
111
112 sqlSessionManager.rollback(true);
113 } else {
114 if (log.isDebugEnabled()) {
115 log.debug(debugPrefix + " - SqlSession of thread: " + currentThread().getId() + " committing");
116 }
117
118 sqlSessionManager.commit(transactional.force());
119 }
120 } finally {
121 if (log.isDebugEnabled()) {
122 log.debug(format("%s - SqlSession of thread: %s terminated its life-cycle, closing it", debugPrefix,
123 currentThread().getId()));
124 }
125
126 sqlSessionManager.close();
127 }
128 } else if (log.isDebugEnabled()) {
129 log.debug(format("%s - SqlSession of thread: %s is inherited, skipped close operation", debugPrefix,
130 currentThread().getId()));
131 }
132 }
133
134 return object;
135 }
136
137 private Throwable convertThrowableIfNeeded(MethodInvocation invocation, Transactional transactional, Throwable t) {
138 Method interceptedMethod = invocation.getMethod();
139
140
141 for (Class<?> exceptionClass : interceptedMethod.getExceptionTypes()) {
142 if (exceptionClass.isAssignableFrom(t.getClass())) {
143 return t;
144 }
145 }
146
147
148 if (transactional.rethrowExceptionsAs().isAssignableFrom(t.getClass())) {
149 return t;
150 }
151
152
153 String errorMessage;
154 Object[] initargs;
155 Class<?>[] initargsType;
156
157 if (transactional.exceptionMessage().length() != 0) {
158 errorMessage = format(transactional.exceptionMessage(), invocation.getArguments());
159 initargs = new Object[] { errorMessage, t };
160 initargsType = MESSAGE_CAUSE_TYPES;
161 } else {
162 initargs = new Object[] { t };
163 initargsType = CAUSE_TYPES;
164 }
165
166 Constructor<? extends Throwable> exceptionConstructor = getMatchingConstructor(transactional.rethrowExceptionsAs(),
167 initargsType);
168 Throwable rethrowEx = null;
169 if (exceptionConstructor != null) {
170 try {
171 rethrowEx = exceptionConstructor.newInstance(initargs);
172 } catch (Exception e) {
173 errorMessage = format("Impossible to re-throw '%s', it needs the constructor with %s argument(s).",
174 transactional.rethrowExceptionsAs().getName(), Arrays.toString(initargsType));
175 log.error(errorMessage, e);
176 rethrowEx = new RuntimeException(errorMessage, e);
177 }
178 } else {
179 errorMessage = format("Impossible to re-throw '%s', it needs the constructor with %s or %s argument(s).",
180 transactional.rethrowExceptionsAs().getName(), Arrays.toString(CAUSE_TYPES),
181 Arrays.toString(MESSAGE_CAUSE_TYPES));
182 log.error(errorMessage);
183 rethrowEx = new RuntimeException(errorMessage);
184 }
185
186 return rethrowEx;
187 }
188
189 @SuppressWarnings("unchecked")
190 private static <E extends Throwable> Constructor<E> getMatchingConstructor(Class<E> type, Class<?>[] argumentsType) {
191 Class<? super E> currentType = type;
192 while (Object.class != currentType) {
193 for (Constructor<?> constructor : currentType.getConstructors()) {
194 if (Arrays.equals(argumentsType, constructor.getParameterTypes())) {
195 return (Constructor<E>) constructor;
196 }
197 }
198 currentType = currentType.getSuperclass();
199 }
200 return null;
201 }
202
203 }