You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DBSessionsImplTest.java 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.db;
  21. import com.google.common.base.Throwables;
  22. import java.sql.Connection;
  23. import java.sql.SQLException;
  24. import java.util.ArrayList;
  25. import java.util.Arrays;
  26. import java.util.List;
  27. import java.util.Random;
  28. import java.util.Set;
  29. import java.util.function.BiConsumer;
  30. import java.util.stream.Collectors;
  31. import java.util.stream.IntStream;
  32. import org.apache.ibatis.session.Configuration;
  33. import org.apache.ibatis.session.ResultHandler;
  34. import org.apache.ibatis.session.RowBounds;
  35. import org.junit.After;
  36. import org.junit.Rule;
  37. import org.junit.Test;
  38. import org.mockito.Mockito;
  39. import org.slf4j.event.Level;
  40. import org.sonar.api.testfixtures.log.LogTester;
  41. import static java.lang.Math.abs;
  42. import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
  43. import static org.assertj.core.api.Assertions.assertThat;
  44. import static org.mockito.ArgumentMatchers.anyBoolean;
  45. import static org.mockito.Mockito.mock;
  46. import static org.mockito.Mockito.reset;
  47. import static org.mockito.Mockito.times;
  48. import static org.mockito.Mockito.verify;
  49. import static org.mockito.Mockito.verifyNoMoreInteractions;
  50. import static org.mockito.Mockito.when;
  51. public class DBSessionsImplTest {
  52. @Rule
  53. public LogTester logTester = new LogTester();
  54. private final MyBatis myBatis = mock(MyBatis.class);
  55. private final DbSession myBatisDbSession = mock(DbSession.class);
  56. private final Random random = new Random();
  57. private final DBSessionsImpl underTest = new DBSessionsImpl(myBatis);
  58. @After
  59. public void tearDown() {
  60. underTest.disableCaching();
  61. }
  62. @Test
  63. public void openSession_without_caching_always_returns_a_new_regular_session_when_parameter_is_false() {
  64. DbSession[] expected = {mock(DbSession.class), mock(DbSession.class), mock(DbSession.class), mock(DbSession.class)};
  65. when(myBatis.openSession(false))
  66. .thenReturn(expected[0])
  67. .thenReturn(expected[1])
  68. .thenReturn(expected[2])
  69. .thenReturn(expected[3])
  70. .thenThrow(oneCallTooMuch());
  71. assertThat(Arrays.stream(expected).map(ignored -> underTest.openSession(false)).toList())
  72. .containsExactly(expected);
  73. }
  74. @Test
  75. public void openSession_without_caching_always_returns_a_new_batch_session_when_parameter_is_true() {
  76. DbSession[] expected = {mock(DbSession.class), mock(DbSession.class), mock(DbSession.class), mock(DbSession.class)};
  77. when(myBatis.openSession(true))
  78. .thenReturn(expected[0])
  79. .thenReturn(expected[1])
  80. .thenReturn(expected[2])
  81. .thenReturn(expected[3])
  82. .thenThrow(oneCallTooMuch());
  83. assertThat(Arrays.stream(expected).map(ignored -> underTest.openSession(true)).toList())
  84. .containsExactly(expected);
  85. }
  86. @Test
  87. public void openSession_with_caching_always_returns_the_same_regular_session_when_parameter_is_false() {
  88. DbSession expected = mock(DbSession.class);
  89. when(myBatis.openSession(false))
  90. .thenReturn(expected)
  91. .thenThrow(oneCallTooMuch());
  92. underTest.enableCaching();
  93. int size = 1 + abs(random.nextInt(10));
  94. Set<DbSession> dbSessions = IntStream.range(0, size).mapToObj(ignored -> underTest.openSession(false)).collect(Collectors.toSet());
  95. assertThat(dbSessions).hasSize(size);
  96. assertThat(getWrappedDbSessions(dbSessions))
  97. .hasSize(1)
  98. .containsOnly(expected);
  99. }
  100. @Test
  101. public void openSession_with_caching_always_returns_the_same_batch_session_when_parameter_is_true() {
  102. DbSession expected = mock(DbSession.class);
  103. when(myBatis.openSession(true))
  104. .thenReturn(expected)
  105. .thenThrow(oneCallTooMuch());
  106. underTest.enableCaching();
  107. int size = 1 + abs(random.nextInt(10));
  108. Set<DbSession> dbSessions = IntStream.range(0, size).mapToObj(ignored -> underTest.openSession(true)).collect(Collectors.toSet());
  109. assertThat(dbSessions).hasSize(size);
  110. assertThat(getWrappedDbSessions(dbSessions))
  111. .hasSize(1)
  112. .containsOnly(expected);
  113. }
  114. @Test
  115. public void openSession_with_caching_returns_a_session_per_thread() {
  116. boolean batchOrRegular = random.nextBoolean();
  117. DbSession[] expected = {mock(DbSession.class), mock(DbSession.class), mock(DbSession.class), mock(DbSession.class)};
  118. when(myBatis.openSession(batchOrRegular))
  119. .thenReturn(expected[0])
  120. .thenReturn(expected[1])
  121. .thenReturn(expected[2])
  122. .thenReturn(expected[3])
  123. .thenThrow(oneCallTooMuch());
  124. List<DbSession> collector = new ArrayList<>();
  125. Runnable runnable = () -> {
  126. underTest.enableCaching();
  127. collector.add(underTest.openSession(batchOrRegular));
  128. underTest.disableCaching();
  129. };
  130. Thread[] threads = {new Thread(runnable, "T1"), new Thread(runnable, "T2"), new Thread(runnable, "T3")};
  131. executeThreadsAndCurrent(runnable, threads);
  132. // verify each DbSession was closed and then reset mock for next verification
  133. Arrays.stream(expected).forEach(s -> {
  134. verify(s).close();
  135. reset(s);
  136. });
  137. // verify whether each thread got the expected DbSession from MyBatis
  138. // by verifying to which each returned DbSession delegates to
  139. DbSession[] dbSessions = collector.toArray(new DbSession[0]);
  140. for (int i = 0; i < expected.length; i++) {
  141. dbSessions[i].rollback();
  142. verify(expected[i]).rollback();
  143. List<DbSession> sub = new ArrayList<>(Arrays.asList(expected));
  144. sub.remove(expected[i]);
  145. sub.forEach(Mockito::verifyNoMoreInteractions);
  146. reset(expected);
  147. }
  148. }
  149. private static void executeThreadsAndCurrent(Runnable runnable, Thread[] threads) {
  150. Arrays.stream(threads).forEach(thread -> {
  151. thread.start();
  152. try {
  153. thread.join();
  154. } catch (InterruptedException e) {
  155. Throwables.propagate(e);
  156. }
  157. });
  158. runnable.run();
  159. }
  160. @Test
  161. public void openSession_with_caching_returns_wrapper_of_MyBatis_DbSession_which_delegates_all_methods_but_close() {
  162. boolean batchOrRegular = random.nextBoolean();
  163. underTest.enableCaching();
  164. verifyFirstDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  165. dbSession.rollback();
  166. verify(myBatisDbSession).rollback();
  167. });
  168. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  169. boolean flag = random.nextBoolean();
  170. dbSession.rollback(flag);
  171. verify(myBatisDbSession).rollback(flag);
  172. });
  173. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  174. dbSession.commit();
  175. verify(myBatisDbSession).commit();
  176. });
  177. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  178. boolean flag = random.nextBoolean();
  179. dbSession.commit(flag);
  180. verify(myBatisDbSession).commit(flag);
  181. });
  182. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  183. String str = randomAlphabetic(10);
  184. dbSession.selectOne(str);
  185. verify(myBatisDbSession).selectOne(str);
  186. });
  187. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  188. String str = randomAlphabetic(10);
  189. Object object = new Object();
  190. dbSession.selectOne(str, object);
  191. verify(myBatisDbSession).selectOne(str, object);
  192. });
  193. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  194. String str = randomAlphabetic(10);
  195. dbSession.selectList(str);
  196. verify(myBatisDbSession).selectList(str);
  197. });
  198. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  199. String str = randomAlphabetic(10);
  200. Object object = new Object();
  201. dbSession.selectList(str, object);
  202. verify(myBatisDbSession).selectList(str, object);
  203. });
  204. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  205. String str = randomAlphabetic(10);
  206. Object parameter = new Object();
  207. RowBounds rowBounds = new RowBounds();
  208. dbSession.selectList(str, parameter, rowBounds);
  209. verify(myBatisDbSession).selectList(str, parameter, rowBounds);
  210. });
  211. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  212. String str = randomAlphabetic(10);
  213. String mapKey = randomAlphabetic(10);
  214. dbSession.selectMap(str, mapKey);
  215. verify(myBatisDbSession).selectMap(str, mapKey);
  216. });
  217. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  218. String str = randomAlphabetic(10);
  219. Object parameter = new Object();
  220. String mapKey = randomAlphabetic(10);
  221. dbSession.selectMap(str, parameter, mapKey);
  222. verify(myBatisDbSession).selectMap(str, parameter, mapKey);
  223. });
  224. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  225. String str = randomAlphabetic(10);
  226. Object parameter = new Object();
  227. String mapKey = randomAlphabetic(10);
  228. RowBounds rowBounds = new RowBounds();
  229. dbSession.selectMap(str, parameter, mapKey, rowBounds);
  230. verify(myBatisDbSession).selectMap(str, parameter, mapKey, rowBounds);
  231. });
  232. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  233. String str = randomAlphabetic(10);
  234. ResultHandler handler = mock(ResultHandler.class);
  235. dbSession.select(str, handler);
  236. verify(myBatisDbSession).select(str, handler);
  237. });
  238. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  239. String str = randomAlphabetic(10);
  240. Object parameter = new Object();
  241. ResultHandler handler = mock(ResultHandler.class);
  242. dbSession.select(str, parameter, handler);
  243. verify(myBatisDbSession).select(str, parameter, handler);
  244. });
  245. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  246. String str = randomAlphabetic(10);
  247. Object parameter = new Object();
  248. ResultHandler handler = mock(ResultHandler.class);
  249. RowBounds rowBounds = new RowBounds();
  250. dbSession.select(str, parameter, rowBounds, handler);
  251. verify(myBatisDbSession).select(str, parameter, rowBounds, handler);
  252. });
  253. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  254. String str = randomAlphabetic(10);
  255. dbSession.insert(str);
  256. verify(myBatisDbSession).insert(str);
  257. });
  258. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  259. String str = randomAlphabetic(10);
  260. Object object = new Object();
  261. dbSession.insert(str, object);
  262. verify(myBatisDbSession).insert(str, object);
  263. });
  264. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  265. String str = randomAlphabetic(10);
  266. dbSession.update(str);
  267. verify(myBatisDbSession).update(str);
  268. });
  269. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  270. String str = randomAlphabetic(10);
  271. Object object = new Object();
  272. dbSession.update(str, object);
  273. verify(myBatisDbSession).update(str, object);
  274. });
  275. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  276. String str = randomAlphabetic(10);
  277. dbSession.delete(str);
  278. verify(myBatisDbSession).delete(str);
  279. });
  280. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  281. String str = randomAlphabetic(10);
  282. Object object = new Object();
  283. dbSession.delete(str, object);
  284. verify(myBatisDbSession).delete(str, object);
  285. });
  286. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  287. dbSession.flushStatements();
  288. verify(myBatisDbSession).flushStatements();
  289. });
  290. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  291. dbSession.clearCache();
  292. verify(myBatisDbSession).clearCache();
  293. });
  294. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  295. Configuration expected = mock(Configuration.class);
  296. when(myBatisDbSession.getConfiguration()).thenReturn(expected);
  297. assertThat(dbSession.getConfiguration()).isSameAs(expected);
  298. verify(myBatisDbSession).getConfiguration();
  299. });
  300. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  301. Class<Object> clazz = Object.class;
  302. Object expected = new Object();
  303. when(myBatisDbSession.getMapper(clazz)).thenReturn(expected);
  304. assertThat(dbSession.getMapper(clazz)).isSameAs(expected);
  305. verify(myBatisDbSession).getMapper(clazz);
  306. });
  307. verifyDelegation(batchOrRegular, (myBatisDbSession, dbSession) -> {
  308. Connection connection = mock(Connection.class);
  309. when(myBatisDbSession.getConnection()).thenReturn(connection);
  310. assertThat(dbSession.getConnection()).isSameAs(connection);
  311. verify(myBatisDbSession).getConnection();
  312. });
  313. }
  314. @Test
  315. public void openSession_with_caching_returns_DbSession_that_rolls_back_on_close_if_any_mutation_call_was_not_followed_by_commit_nor_rollback() throws SQLException {
  316. DbSession dbSession = openSessionAndDoSeveralMutatingAndNeutralCalls();
  317. dbSession.close();
  318. verify(myBatisDbSession).rollback();
  319. }
  320. @Test
  321. public void openSession_with_caching_returns_DbSession_that_does_not_roll_back_on_close_if_any_mutation_call_was_followed_by_commit() throws SQLException {
  322. DbSession dbSession = openSessionAndDoSeveralMutatingAndNeutralCalls();
  323. COMMIT_CALLS[random.nextBoolean() ? 0 : 1].consume(dbSession);
  324. dbSession.close();
  325. verify(myBatisDbSession, times(0)).rollback();
  326. }
  327. @Test
  328. public void openSession_with_caching_returns_DbSession_that_does_not_roll_back_on_close_if_any_mutation_call_was_followed_by_rollback_without_parameters() throws SQLException {
  329. DbSession dbSession = openSessionAndDoSeveralMutatingAndNeutralCalls();
  330. dbSession.rollback();
  331. dbSession.close();
  332. verify(myBatisDbSession, times(1)).rollback();
  333. }
  334. @Test
  335. public void openSession_with_caching_returns_DbSession_that_does_not_roll_back_on_close_if_any_mutation_call_was_followed_by_rollback_with_parameters() throws SQLException {
  336. boolean force = random.nextBoolean();
  337. DbSession dbSession = openSessionAndDoSeveralMutatingAndNeutralCalls();
  338. dbSession.rollback(force);
  339. dbSession.close();
  340. verify(myBatisDbSession, times(1)).rollback(force);
  341. verify(myBatisDbSession, times(0)).rollback();
  342. }
  343. private DbSession openSessionAndDoSeveralMutatingAndNeutralCalls() throws SQLException {
  344. boolean batchOrRegular = random.nextBoolean();
  345. when(myBatis.openSession(batchOrRegular))
  346. .thenReturn(myBatisDbSession)
  347. .thenThrow(oneCallTooMuch());
  348. underTest.enableCaching();
  349. DbSession dbSession = underTest.openSession(batchOrRegular);
  350. int dirtyingCallsCount = 1 + abs(random.nextInt(5));
  351. int neutralCallsCount = abs(random.nextInt(5));
  352. int[] dirtyCallIndices = IntStream.range(0, dirtyingCallsCount).map(ignored -> abs(random.nextInt(DIRTYING_CALLS.length))).toArray();
  353. int[] neutralCallsIndices = IntStream.range(0, neutralCallsCount).map(ignored -> abs(random.nextInt(NEUTRAL_CALLS.length))).toArray();
  354. for (int index : dirtyCallIndices) {
  355. DIRTYING_CALLS[index].consume(dbSession);
  356. }
  357. for (int index : neutralCallsIndices) {
  358. NEUTRAL_CALLS[index].consume(dbSession);
  359. }
  360. return dbSession;
  361. }
  362. private static DbSessionCaller[] DIRTYING_CALLS = {
  363. session -> session.insert(randomAlphabetic(3)),
  364. session -> session.insert(randomAlphabetic(2), new Object()),
  365. session -> session.update(randomAlphabetic(3)),
  366. session -> session.update(randomAlphabetic(3), new Object()),
  367. session -> session.delete(randomAlphabetic(3)),
  368. session -> session.delete(randomAlphabetic(3), new Object()),
  369. };
  370. private static DbSessionCaller[] COMMIT_CALLS = {
  371. session -> session.commit(),
  372. session -> session.commit(new Random().nextBoolean()),
  373. };
  374. private static DbSessionCaller[] ROLLBACK_CALLS = {
  375. session -> session.rollback(),
  376. session -> session.rollback(new Random().nextBoolean()),
  377. };
  378. private static DbSessionCaller[] NEUTRAL_CALLS = {
  379. session -> session.selectOne(randomAlphabetic(3)),
  380. session -> session.selectOne(randomAlphabetic(3), new Object()),
  381. session -> session.select(randomAlphabetic(3), mock(ResultHandler.class)),
  382. session -> session.select(randomAlphabetic(3), new Object(), mock(ResultHandler.class)),
  383. session -> session.select(randomAlphabetic(3), new Object(), new RowBounds(), mock(ResultHandler.class)),
  384. session -> session.selectList(randomAlphabetic(3)),
  385. session -> session.selectList(randomAlphabetic(3), new Object()),
  386. session -> session.selectList(randomAlphabetic(3), new Object(), new RowBounds()),
  387. session -> session.selectMap(randomAlphabetic(3), randomAlphabetic(3)),
  388. session -> session.selectMap(randomAlphabetic(3), new Object(), randomAlphabetic(3)),
  389. session -> session.selectMap(randomAlphabetic(3), new Object(), randomAlphabetic(3), new RowBounds()),
  390. session -> session.getMapper(Object.class),
  391. session -> session.getConfiguration(),
  392. session -> session.getConnection(),
  393. session -> session.clearCache(),
  394. session -> session.flushStatements()
  395. };
  396. private interface DbSessionCaller {
  397. void consume(DbSession t) throws SQLException;
  398. }
  399. @Test
  400. public void disableCaching_does_not_open_DB_connection_if_openSession_was_never_called() {
  401. when(myBatis.openSession(anyBoolean()))
  402. .thenThrow(oneCallTooMuch());
  403. underTest.enableCaching();
  404. underTest.disableCaching();
  405. verifyNoMoreInteractions(myBatis);
  406. }
  407. @Test
  408. public void disableCaching_has_no_effect_if_enabledCaching_has_not_been_called() {
  409. underTest.disableCaching();
  410. verifyNoMoreInteractions(myBatis);
  411. }
  412. @Test
  413. public void disableCaching_does_not_fail_but_logs_if_closing_MyBatis_session_close_throws_an_exception() {
  414. boolean batchOrRegular = random.nextBoolean();
  415. IllegalStateException toBeThrown = new IllegalStateException("Faking MyBatisSession#close failing");
  416. when(myBatis.openSession(batchOrRegular))
  417. .thenReturn(myBatisDbSession)
  418. .thenThrow(oneCallTooMuch());
  419. Mockito.doThrow(toBeThrown)
  420. .when(myBatisDbSession).close();
  421. underTest.enableCaching();
  422. underTest.openSession(batchOrRegular);
  423. underTest.disableCaching();
  424. List<String> errorLogs = logTester.logs(Level.ERROR);
  425. assertThat(errorLogs)
  426. .hasSize(1)
  427. .containsOnly("Failed to close " + (batchOrRegular ? "batch" : "regular") + " connection in " + Thread.currentThread());
  428. }
  429. private void verifyFirstDelegation(boolean batchOrRegular, BiConsumer<DbSession, DbSession> r) {
  430. verifyDelegation(batchOrRegular, true, r);
  431. }
  432. private void verifyDelegation(boolean batchOrRegular, BiConsumer<DbSession, DbSession> r) {
  433. verifyDelegation(batchOrRegular, false, r);
  434. }
  435. private void verifyDelegation(boolean batchOrRegular, boolean firstCall, BiConsumer<DbSession, DbSession> r) {
  436. when(myBatis.openSession(batchOrRegular))
  437. .thenReturn(myBatisDbSession)
  438. .thenThrow(oneCallTooMuch());
  439. r.accept(myBatisDbSession, underTest.openSession(batchOrRegular));
  440. verify(myBatisDbSession, times(firstCall ? 1 : 0)).getSqlSession();
  441. verifyNoMoreInteractions(myBatisDbSession);
  442. reset(myBatis, myBatisDbSession);
  443. }
  444. private static IllegalStateException oneCallTooMuch() {
  445. return new IllegalStateException("one call too much");
  446. }
  447. private Set<DbSession> getWrappedDbSessions(Set<DbSession> dbSessions) {
  448. return dbSessions.stream()
  449. .filter(NonClosingDbSession.class::isInstance)
  450. .map(NonClosingDbSession.class::cast)
  451. .map(NonClosingDbSession::getDelegate)
  452. .collect(Collectors.toSet());
  453. }
  454. }