{"id":22864950,"url":"https://github.com/dtonhofer/testing_h2_and_spring_jdbc","last_synced_at":"2026-05-03T02:43:06.323Z","repository":{"id":189337024,"uuid":"680468423","full_name":"dtonhofer/testing_h2_and_spring_jdbc","owner":"dtonhofer","description":"Code written to exercise myself with H2 + Spring Data JDBC + Transactions","archived":false,"fork":false,"pushed_at":"2023-09-08T17:23:12.000Z","size":5694,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-03-28T04:35:36.346Z","etag":null,"topics":["educational","h2-database","spring-boot","spring-jdbc","sql","transactions"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dtonhofer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-08-19T10:42:07.000Z","updated_at":"2024-07-11T10:01:50.000Z","dependencies_parsed_at":"2023-08-19T14:05:58.793Z","dependency_job_id":null,"html_url":"https://github.com/dtonhofer/testing_h2_and_spring_jdbc","commit_stats":null,"previous_names":["dtonhofer/testing_h2_and_spring_jdbc"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dtonhofer/testing_h2_and_spring_jdbc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dtonhofer%2Ftesting_h2_and_spring_jdbc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dtonhofer%2Ftesting_h2_and_spring_jdbc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dtonhofer%2Ftesting_h2_and_spring_jdbc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dtonhofer%2Ftesting_h2_and_spring_jdbc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dtonhofer","download_url":"https://codeload.github.com/dtonhofer/testing_h2_and_spring_jdbc/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dtonhofer%2Ftesting_h2_and_spring_jdbc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32556771,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-03T00:31:16.350Z","status":"online","status_checked_at":"2026-05-03T02:00:09.297Z","response_time":103,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["educational","h2-database","spring-boot","spring-jdbc","sql","transactions"],"created_at":"2024-12-13T11:32:10.129Z","updated_at":"2026-05-03T02:43:06.309Z","avatar_url":"https://github.com/dtonhofer.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Exercises with the H2 database and Spring JDBC\n\nCode written to exercise myself with H2 and Spring JDBC, including exercising the\nbehaviour of transactions. \n\nYou know the drill! \n\n\u003cimg src=\"https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/extras/trying_stuff_until_it_works.png\" alt=\"Trying stuff until it works\" width=\"300\" /\u003e\n\n## References\n\nSome links:\n\n- [Spring Data JDBC](https://spring.io/projects/spring-data-jdbc)\n- [The H2 database](http://h2database.com/html/main.html)\n- [The H2 mailing list](https://groups.google.com/g/h2-database) \n\nA must-read is this technical report, even though it could use a review:\n\n[A Critique of ANSI SQL Isolation Levels](https://arxiv.org/abs/cs/0701157)\u003cbr\u003e\n*Hal Berenson, Phil Bernstein, Jim Gray, Jim Melton, Elizabeth O'Neil, Patrick O'Neil*\u003cbr\u003e\n*Proc. ACM SIGMOD 95, pp. 1-10, San Jose CA, June 1995*\u003cbr\u003e\n*Microsoft Research Technical Report MSR-TR-95-51*\u003cbr\u003e\n\n## Exercise 1: Agents and Messages\n\nPackage \n[`name.heavycarbon.h2_exercises.agents_and_msgs`](https://github.com/dtonhofer/testing_h2_and_spring_jdbc/tree/master/src/test/java/name/heavycarbon/h2_exercises/agents_and_msgs).\n\nThis is some code to start \u0026 run a set of agents (i.e. threads running their `Runnables`) \nthat send messages to each other through an H2 table, poll for new messages, and acknowledge\nthe messages received.\n\nThere are two type of messages:\n\n- \"true messages\" carrying some (arbitrary) text from an agent A to an agent B;\n- \"ack messages\" sent from agent B to agent A after a \"true message\" from agent A\n  has been received at agent B. \"ack messages\" are not acknowledged themselves.\n\nMessages have a \"state\". A message that has just been set is in state \"fresh\". After \nit was found in the database (via polling) by the agent to whom the message was addressed,\nthe receiving agent updates the message's state to \"seen\" so that polling does not pick\nit up again during the next poll.\n\nThe executable part is packaged as a JUnit5 test but it doesn't check anything, it just runs\na fixed number of agents for a fixed number of seconds.\n\nThe JUnit5 test class to run is\n[`TestAgentsExchangingMsgs`](https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/src/test/java/name/heavycarbon/h2_exercises/agents_and_msgs/TestAgentsExchangingMsgs.java).\n\n## Exercise 2: Storing `java.time.Instant`\n\nPackage\n[`name.heavycarbon.h2_exercises.storing_instants`](https://github.com/dtonhofer/testing_h2_and_spring_jdbc/tree/master/src/test/java/name/heavycarbon/h2_exercises/storing_instants).\n\nA perennial problem is to make sure a \n[`java.util.Date`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Date.html) \nor (better and in concordance with more modern Java) a \n[`java.time.Instant`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Instant.html)\nis correctly stored in and retrieved from a database. One wants to see no mysterious shifts due\nlocal time zones configured in the database server, the JDBC driver, the system, or otherwise, possibly\nbeing applied only on one side of the back-and-forth of the data. Here we are testing that we can\nproperly store an `Instant`.\n\nThe JUnit5 test class to run is\n[`TestStoringInstants`](https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/src/test/java/name/heavycarbon/h2_exercises/storing_instants/TestStoringInstants.java).\n\n## Exercise 3: Testing transactions\n\nApart from getting data on and off the disk with some efficiency and providing a high-level interface to data definition and manipulation,\ntransactions are one of the core problems that databases are supposed to handle. In this exercise, we try to find a good way to use transactions\nwith Spring JDBC and then run a few tests.\n\n### Transaction isolation levels\n\nReading [*A Critique of ANSI SQL Isolation Levels*](https://arxiv.org/abs/cs/0701157), we find that ANSI 92 defintions of \"isolation levels\"\nare rather unfortunate because they are based not on a theoretical understanding of transactions but on the appearance of a number of \"phenomena\",\nwhich are supposed to be observable or disallowed at a given isolation level. These phenomena are found to be defined with some lack of clarity \nand do not form an exhaustive set of possibilities. (Maybe there are better definitions in later issues of the SQL standard?)\n\nA matrix of the ANSI SQL isolation levels and the phenomena which are allowed or disallowed, depending on the same:\n\n\u003cimg src=\"https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/general/isolation_levels_matrix.png\" alt=\"ANSI SQL isolation level vs phenomena matrix\"  /\u003e\n\nAlso available as [OpenDocument](https://en.wikipedia.org/wiki/OpenDocument) spreadsheet: [isolation_levels_matrix.ods](https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/general/isolation_levels_matrix.ods).\n\nIf one equates an \"isolation level\" to the set of possible \"histories\" that it allows, where a history is the timeline of interleaved actions\nperformed by a set of transactions on a set of data items, on obtains a graph expressing the \"stronger than\" relation, which is equivalent to\nthe subset relation among histories.\n\nThis image was adapted from [*A Critique of ANSI SQL Isolation Levels*](https://arxiv.org/abs/cs/0701157):\n\n\u003cimg src=\"https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/general/isolation_levels_hierarchy.png\" alt=\"Isolation level hierarchy\" width=\"1200\"  /\u003e\n\nAlso available as [GraphML](https://en.wikipedia.org/wiki/GraphML) file: [isolation_levels_hierarchy.graphml](https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/general/isolation_levels_hierarchy.graphml).\n\nTo illustrate what is happening in a particular test case, we will use swimlane diagrams with the following key:\n\n\u003cimg src=\"https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/swimlanes/swml_key.png\" alt=\"swimlane key\" /\u003e\n\nAlso available as [GraphML](https://en.wikipedia.org/wiki/GraphML) file: [swml_key.graphml](https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/swimlanes/swml_key.graphml).\n\nNote that in all case, the transactions are running on the same isolation level. In any case, H2 does not allow sessions with different isolation levels.\n\nFor notation purposes, ε (epsilon) is considered to be \"the empty datum\". In that view, if you read a nonexistent data item D, you obtain ε. \nIf you write ε to a data item D, you erase it.\n\n### Action sequencing\n\nImplementationwise, we run separate thread to animate separate transactions.\nThe reason that we have several threads is that a Spring Data Transaction is a \"per thread\" concept, being equatable to a \nstack frame that is pushed on \"transaction start\" and popped on \"transaction end\". We cannot just keep \nconnections in a data structure that is managed by a single thread.\n\nAs the threads run, they traverse a series of numbered \"actions\" in strict sequence, with an action implying operations\nlike reading, updating, inserting or deleting - generally just 1 operation. The strict action sequence is maintained by a\ncommon \"state\" integer variable (an `AtomicInteger` inside class `AppState`) which is incremented by 1 after an action has\nbeen executed by the proper thread.\n\nAt any time, only a single thread is able to run. This is done by having all threads synchronize on the single `AppState`\ninstance (i.e. acquire the monitor) early on (\"high in the call stack\") and never release the monitor except by calling\n[`AppState.notify()`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html#notify())\nand then \n[`AppState.wait()`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html#wait()) when they\nnotice that the current value of the state integer means it's not their turn. Calling `wait()` \nreleases the monitor which is then acquired by the other, previously notified thread. This works like a handy \n\"permission to act\" token that needs little code. The result is a state machine animated by two threads. \nWith this approach, it is impossible to simulate the case of two threads freely and asynchronously accessing the database \nthough.\n\nSome diagrams showing how the state machine works (but from an early code iteration so may no longer fully reflect the code)\n\n#### State machine for eliciting \"Dirty Reads\"\n\n\u003cimg src=\"https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/sequences/dirty_read_sequence.png\" alt=\"Dirty Read sequence\" width=\"600\" /\u003e\n\nGraphML file: [dirty_read_sequence.graphml](https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/sequences/dirty_read_sequence.graphml)\n\n#### State machine for eliciting \"Non-Repeatable Reads\"\n\n\u003cimg src=\"https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/sequences/non_repeatable_read_sequence.png\" alt=\"Non-Repeatable Read sequence\" width=\"600\" /\u003e\n\nGraphML file: [non_repeatable_read_sequence.graphml](https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/sequences/non_repeatable_read_sequence.graphml)\n\n#### State machine for eliciting \"Phantom Reads\"\n\n\u003cimg src=\"https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/sequences/phantom_read_sequence.png\" alt=\"Phantom Read sequence\" width=\"600\" /\u003e\n\nGraphML file: [phantom_read_sequence.graphml](https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/sequences/phantom_read_sequence.graphml)\n\n#### Call stack diagram\n\n\u003cimg src=\"https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/class_graphs/class_graph_dirty_read.png\" alt=\"Call stack and Classes for Dirty Read\" width=\"600\" /\u003e\n\nGraphML file: [class_graph_dirty_read.graphml](https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/class_graphs/class_graph_dirty_read.graphml)\n\n### Test 1: Eliciting \"Dirty Reads\"\n\n[TestElicitingDirtyReads.java](https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/src/test/java/name/heavycarbon/h2_exercises/transactions/TestElicitingDirtyReads.java)\n\nA \"dirty read\" (phenomenon \"P1\" in *A Critique of ANSI SQL Isolation Levels*) happens when transaction T2 (the \"reader\" transaction)\ncan read data written by, but not yet committed by, transaction T1 (the \"modifier\" transaction). \n\nThis unsoundness is supposed to go away at isolation level ANSI \"READ COMMITTED\" and stronger.\n\nTaking a \"data item\" to be a record identified by a fixed identifier, we test the following scenarios:\n\n- **UPDATE**: T1 updates an existing data item D with x in action 0.\n  After that, T2 can read the value x from D even though T1 is still active.\n  This is undesirable irrespective of whether T1 eventually rolls back (then T2 has\n  read something that never existed) or commits (then T2 has read something \"from the future\" which can lead to arbitrary problems.\n  T1 may also update D a second time with z for example).\n- **INSERT**: T1 inserts new data item D in action 0.\n  After that, T2 sees D even though T1 is still active.\n- **DELETE**: T1 deletes an existing data item D (considered as writing ε to D) in action 0.\n  After that, T2 can no longer access D (a read yields ε) even though T1 is still active.\n\n\u003cimg src=\"https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/swimlanes/swml_dirty_read.png\" alt=\"Dirty Read swimlanes\" width=\"600\" /\u003e\n\nGraphML file: [swml_dirty_read.graphml](https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/swimlanes/swml_dirty_read.graphml)\n\n**Result for H2**\n\nEverything is as expected. All three scenarios show up in isolation level ANSI \"READ UNCOMMITTED\" only.\n\n### Test 2: Eliciting \"Non-Repeatable Reads\" (aka \"Fuzzy Reads\")\n\n[TestElicitingNonRepeatableReads.java](https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/src/test/java/name/heavycarbon/h2_exercises/transactions/TestElicitingNonRepeatableReads.java)\n\nA \"non-repeatable read\" (phenomenon \"P2\" in *A Critique of ANSI SQL Isolation Levels*) happens when transaction T2 (the \"reader\" transaction)\nreads data item D, obtaining value item x. Transaction T1 (the \"modifier\" transaction) then updates D to y and commits. T2 then re-reads D and no longer finds the value x \nseen earlier but the value y written by T1, i.e. data entrained via reads into T2 may unexpectedly change during T2.\n\nThis unsoundness is supposed to go away at isolation level ANSI \"REPEATABLE READ\" and stronger.\n\nTaking a \"data item\" to be a record identified by a fixed identifier, we test the following scenarios:\n\n- **UPDATE**: T2 reads D in action 0, finding x.\n  Then, in action 1, T1 updates D to y and commits.\n  T2 then re-reads D and find it has unexpectedly changed, i.e. it obtains y instead of x.i\n- **INSERT**: T2 reads D in action 0, and finds it does not exist i.e. it obtains ε.\n  Then, in action 1, T1 creates D with value y and commits.\n  T2 then re-reads D and find it is unexpectedly present with value y.\n- **DELETE**: T2 reads D in action 0, finding x.\n  Then, in action 1, T1 then deletes D (considered as writing ε to D) and commits.\n  T2 then re-reads D and find it is unexpectedly gone, i.e. it obtains ε.\n\n\u003cimg src=\"https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/swimlanes/swml_non_repeatable_read.png\" alt=\"Non-Repeatable Read swimlanes\" width=\"600\" /\u003e\n\nGraphML file: [swml_non_repeatable_read.graphml](https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/swimlanes/swml_non_repeatable_read.graphml)\n\n**Result for H2**\n\nThe Non-Repeatable Read phenomenon, in the form of the three scenarios above, shows up in isolation level ANSI \"READ UNCOMMITTED\" and ANSI \"READ COMMITTED\" only, as expected.\nHowever, in level ANSI \"READ COMMITTED\", apparently randomly, in about ~0.17% of the cases, the phenomenon is *not* observed. \nSo something is going on.\n\n### Test 3: Eliciting \"Phantom Reads\"\n\n[TestElicitingPhantomReads.java](https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/src/test/java/name/heavycarbon/h2_exercises/transactions/TestElicitingPhantomReads.java)\n\nA \"Phantom Read\" is a more hairy phenomenon as it involves result sets defined by predicates (hence the concept of a \"predicate lock\"). \n\nA \"Phantom Read\" (phenomenon \"P3\" in *A Critique of ANSI SQL Isolation Levels*) happens when transaction T2 (the \"reader\" transaction)\nselects a set of data item using a predicate, obtaining the result Ds:P with value set Xs. Transaction T1 (the \"modifier\" transaction) then\nupdates the database so that Ds:P is extended to some Xs ∪ Ys (or reduced to some Xs - Ys) and commits. T2 then re-reads Ds:P and no longer\nfinds the value set Xs seen earlier but the value set Xs ∪ Ys (or Xs - Ys) written by T1, i.e. data entrained via predicate-based reads into T2\nmay unexpectedly grow or shrink during T2.\n\nThis are actually quite similar to \"non-repeatable reads\" and it is not immediately evident what the\nessential difference is. After all, if you select \"by id\" when trying to elicit a \"non-repeatable read\", you are really using a selection \"predicate\" \nalready.\n\nThis unsoundness is supposed to go away at isolation levels \"SERIALIZABLE\" and \"SNAPSHOT\".\n\nTaking a \"data item\" to be a record identified by a fixed identifier, we test the following scenarios:\n\n- **UPDATE-INTO-PREDICATE** and **INSERT-INTO-PREDICATE**: Ds:P grows due to an update of a record D previously outside of Ds:P resulting in it being in Ds:P.\n  T2 reads Ds:P in action 0, finding Xs.\n  In action 1, T1 then updates Ds:P to Xs ∪ Ys via an update or an insert, and commits.\n  T2 then re-reads Ds:P and find it has unexpectedly grown.\n- **UPDATE-OUT-OF-PREDICATE** and **DELETE-FROM-PREDICATE**: Ds:P shrinks due to an update of a record D previously inside of Ds:P resulting in it being outside of Ds:P.\n  T2 reads Ds:P in action 0, finding Xs.\n  In action 1, T1 then updates Ds:P to Xs - Ys via an update or a delete, and commits.\n  T2 then re-reads Ds:P and find it has unexpectedly shrunk.\n\n\u003cimg src=\"https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/swimlanes/swml_phantom_read.png\" alt=\"Non-Repeatable Read swimlanes\" width=\"600\" /\u003e\n\nGraphML file: [swml_non_repeatable_read.graphml](https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/swimlanes/swml_phantom_read.graphml)\n\n**Result for H2**\n\nI have actually been unable to produce a \"Phantom Read\" in isolation level ANSI \"REPEATABLE READ\". They only occur in \nlevels ANSI \"READ UNCOMMITTED\" and ANSI \"READ COMMITTED\". Maybe I'm doing something wrong or the H2 implementation fixes\nthe \"Phantom Read\" problem at lower levels already.\n\nMoreover, in level ANSI \"READ COMMITTED\", apparently randomly, in about ~0.13% of the cases, the phenomenon is *not* observed,\nsimilar to what happens to \"Non-Repeatabale Reads\". \n\n### Test 4: Eliciting \"SQL Timeout\"\n\n[TestElicitingSqlTimeout.java](https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/src/test/java/name/heavycarbon/h2_exercises/transactions/TestElicitingSqlTimeout.java)\n\nIf a write lock on some data item D is held by transaction T1 and then a transaction T2 tries to write that some item D,\nT2 will wait a few hundred milliseconds for that lock to be released before an exception signaling a timeout is raised.\n\nHere is the scenario.\n\n1. In actions 0 and 1, both transactions update non-conflicting data items with a marker string so that we can check that\nrollbeck properly happened. Actions 0 and 1 can also be left out.\n2. In action 2, transaction T1 writes to data item X but but does not yet commit. In action 3, transaction T2 tries to \nwrite to X too but cannot acquire the lock. After a wait time, an exception is raised on T2 is rolled back. \n3. Once the thread animating T2 has terminated, an internal lock is liberated and T1 can continue to action 4, after\nwhich it commits. Data item X will have been updated with the text of T1.\n\n\u003cimg src=\"https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/swimlanes/swml_sql_timeout.png\" alt=\"Dirty Read swimlanes\" width=\"600\" /\u003e\n\nGraphML file: [swml_sql_timeout.graphml](https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/swimlanes/swml_sql_timeout.graphml)\n\n**Result for H2**\n\n- At the JDBC level, the exception raised is an [`org.h2.jdbc.JdbcSQLTimeoutException`](https://h2database.com/javadoc/org/h2/jdbc/JdbcSQLTimeoutException.html)\n  with the text `Timeout trying to lock table \"STUFF\"; SQL statement: ...`\n- At the Spring Data JDBC level, the exception raised is an [`org.springframework.dao.QueryTimeoutException`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/dao/QueryTimeoutException.html), with the H2 exception as cause and with the text `PreparedStatementCallback; SQL [...]; Timeout trying to lock table \"STUFF\"; SQL statement: ...`\n- At the Spring Transaction level, a problem appears. Apparently Spring tries to \"translate\" the original exception somehow, but fails.\n  It then throws a [`org.springframework.transaction.TransactionSystemException`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/TransactionSystemException.html) with the message `JDBC rollback failed`, which is confusing. Can one fix that?\n\nEmpirically lock timeout is a few ms more than 2 seconds, although the manual for [`SET DEFAULT LOCK TIMEOUT`](https://www.h2database.com/html/commands.html#set_default_lock_timeout)\nsays it is actually 1 seconds. One can also set the lock timeout on a per-session basis: [`SET LOCK TIMEOUT`](https://www.h2database.com/html/commands.html#set_lock_timeout).\n\nRun `SELECT * FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME = 'DEFAULT_LOCK_TIMEOUT'` to check the current value. \n\nTry `SET DEFAULT_LOCK_TIMEOUT 500` to accelerate the tests.\n\n### Test 5: Eliciting \"Read Skew\" and \"Write Skew\"\n\nWe will pass on this for now, but here is a swimline to explain them (if I understoof them correctly):\n\n\u003cimg src=\"https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/swimlanes/swml_read_and_write_skew.png\" alt=\"Read Skew and Write Skew swimlanes\" width=\"600\" /\u003e\n\nGraphML file: [swml_read_and_write_skew.graphml](https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/swimlanes/swml_read_and_write_skew.graphml)\n\n### Test 6: Eliciting \"Deadlock\"\n\n[TestElicitingDeadlock.java](https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/src/test/java/name/heavycarbon/h2_exercises/transactions/TestElicitingDeadlock.java)\n\nHere we a number of scenarios. We consider three records:\n\n- `X`, the record which is updated by two transactions T1 (run by agent ALFA) and T2 (run by agent BRAVO), creating a conflict;\n- `Z`, an unrelated record in the same table as the one holding `X`;\n- `K`, an unrelated record in another table than the one holding `X`.\n\nThe basic scenario is as follows (see the accompanying diagram):\n\n- Agent ALFA starts transaction T1 and agent BRAVO starts transaction T2.\n- In step 2, BRAVO manipulates `X`, `Z`, or `K` in a test-dependent operation. The following oerations are tried:\n   - `None`, (i.e. do nothing)\n   - `Read X`, `Update X`, (i.e. manipulate the record of conflict `X`)\n   - `Read Z`, `Update Z`, `Insert Z` (which initially does not exist for this case), `Delete Z` (i.e. manipulate a record `Z` in the same table as `X`)\n   - `Read K`, `Update K`, `Insert K` (which initially does not exist for this case), `Delete K` (i.e. manipulate a record `K` in another table than `X`)\n- In step 3, ALFA then updates `X` with an arbitrary value.\n  **At this point, ALFA may encounter a \"timeout exception\"** (`org.h2.jdbc.JdbcSQLTimeoutException: Timeout trying to lock table \"STUFF\"`) because\n  it cannot acquire the lock to `X`, already held by BRAVO/T2 after a manipulation of `X`.\n- If step 3 does not result in an exception, ALFA commits, moving to step 4.\n- In step 5, BRAVO then updates `X` with an arbitrary value.\n  **At this point, ALFA may encounter a \"deadlock exception\"** (`org.h2.jdbc.JdbcSQLTransactionRollbackException: Deadlock detected`) because H2 detects a\n  dependency problem, depending on the isolation level and the operation applied in step 2, and it decides to stop ALFA from performing something unsound.\n\nThe above I call the \"late update\" scenario as ALFA performs the update of `X` in step 3 _after_ BRAVO has performed the test-dependent operation of step 2.\n \nA variation is the \"early update\" scenario whereby ALFA performs the update of `X` in step 1 _before_ BRAVO performs the test-dependent operation in step 3. \n\n**Result for H2**\n\nWe observe the following, and it is unimportant whether this is a \"late update\" or \"early update\" scenario, unless the test-dependent operation is `Update X`: \n\n- In all isolation levels, if the test-dependent operation is `Update X` then ALFA will\n  get a \"timeout exception\" when trying to acquire the lock on `X` in step 3, and roll back.\n  BRAVO then commits and thus has updated `X` successfully. In the \"early update\" scenario,\n  the \"timeout exception\" will happen for BRAVO instead.\n- In levels \"READ UNCOMMITTED\" and \"READ COMMITTED\" (this not being operation `Update X`)\n  both transactions terminate successfully and BRAVO wins the update race because it committs last.\n- In level \"REPEATABLE READ\" (this not being operation `Update X`) BRAVO gets a\n  \"deadlock exception\" in step 5 for the operations listed below. Otherwise both transactions terminate successfully:\n   - `Read X`, \n   - `Read Z`, `Update Z`, `Delete Z` (but not `Insert Z`)\n- In levels \"SERIALIZABLE\" or \"SNAPSHOT\" (this not being operation `Update X`) BRAVO gets a\n  \"deadlock exception\" in step 5 for *all* operations tried except for operation `None`, for which both transactions terminate successfully.\n  Thus H2 feels very conservative:\n   - `Read X`, \n   - `Read Z`, `Update Z`, `Delete Z`, `Insert Z`\n   - `Read K`, `Update K`, `Delete K`, `Insert K`\n\n\u003cimg src=\"https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/swimlanes/swml_deadlock.png\" alt=\"Deadlock swimlanes\" width=\"600\" /\u003e\n\nGraphML file: [swml_deadlock.graphml](https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/swimlanes/swml_deadlock.graphml)\n\n\u003cimg src=\"https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/swimlanes/swml_deadlock_variation.png\" alt=\"Deadlock variation swimlanes\" width=\"600\" /\u003e\n\nGraphML file: [swml_deadlock_variation.graphml](https://github.com/dtonhofer/testing_h2_and_spring_jdbc/blob/master/doc/swimlanes/swml_deadlock_variation.graphml)\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdtonhofer%2Ftesting_h2_and_spring_jdbc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdtonhofer%2Ftesting_h2_and_spring_jdbc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdtonhofer%2Ftesting_h2_and_spring_jdbc/lists"}