Add json-columns module: JSON columns in Hibernate 7.4.14 (@JdbcTypeCode(SqlTypes.JSON)), JSONB querying, GIN/expression indexes, dirty checking, Jackson 3 vs 2 on PostgreSQL 18.6 and MySQL 8.0.46

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_017ic4b6Kg9RWSTJzyz6KP9X
This commit is contained in:
Claude
2026-10-30 17:00:46 +00:00
parent 98f4ed7411
commit b83cc9988e
51 changed files with 3178 additions and 0 deletions
+207
View File
@@ -0,0 +1,207 @@
# PostgreSQL 18.6 on x86_64-pc-linux-gnu
# table created by Hibernate from the Event entity; rows from generate_series; max_parallel_workers_per_gather=0 for stable plans
# Execution Time values are from this machine and drift between runs; the plan shapes are what the tests assert.
== data
rows: 50000
sample: {"tags": ["standard"], "type": "purchase", "amount": 4.2000000000000000, "device": {"os": "linux", "version": 8}, "userId": 42}
table size (heap): 10000 kB
== 0 no index | Q1 containment @> {userId:42,type:purchase}
sql> select count(*) from event where payload @> '{"userId":42,"type":"purchase"}'
Aggregate (actual time=10.129..10.130 rows=1.00 loops=1)
-> Seq Scan on event (actual time=0.027..10.113 rows=10.00 loops=1)
Filter: (payload @> '{"type": "purchase", "userId": 42}'::jsonb)
Rows Removed by Filter: 49990
Planning Time: 0.362 ms
Execution Time: 10.185 ms
== 0 no index | Q2 ->> equality userId = '42'
sql> select count(*) from event where payload->>'userId' = '42'
Aggregate (actual time=24.353..24.355 rows=1.00 loops=1)
-> Seq Scan on event (actual time=0.037..24.326 rows=10.00 loops=1)
Filter: ((payload ->> 'userId'::text) = '42'::text)
Rows Removed by Filter: 49990
Planning Time: 0.414 ms
Execution Time: 24.412 ms
== 0 no index | Q3 key exists ? 'coupon'
sql> select count(*) from event where payload ? 'coupon'
Aggregate (actual time=15.209..15.211 rows=1.00 loops=1)
-> Seq Scan on event (actual time=0.064..15.183 rows=100.00 loops=1)
Filter: (payload ? 'coupon'::text)
Rows Removed by Filter: 49900
Planning Time: 0.261 ms
Execution Time: 15.256 ms
== 0 no index | Q4 containment @> {tags:[vip]}
sql> select count(*) from event where payload @> '{"tags":["vip"]}'
Aggregate (actual time=20.045..20.046 rows=1.00 loops=1)
-> Seq Scan on event (actual time=0.023..19.969 rows=1000.00 loops=1)
Filter: (payload @> '{"tags": ["vip"]}'::jsonb)
Rows Removed by Filter: 49000
Planning Time: 0.314 ms
Execution Time: 20.091 ms
== 1 GIN (payload) | index size
idx_event_gin: 1840 kB
== 1 GIN (payload) | Q1 containment @> {userId:42,type:purchase}
sql> select count(*) from event where payload @> '{"userId":42,"type":"purchase"}'
Aggregate (actual time=0.626..0.627 rows=1.00 loops=1)
-> Bitmap Heap Scan on event (actual time=0.572..0.622 rows=10.00 loops=1)
Recheck Cond: (payload @> '{"type": "purchase", "userId": 42}'::jsonb)
Heap Blocks: exact=10
-> Bitmap Index Scan on idx_event_gin (actual time=0.561..0.561 rows=10.00 loops=1)
Index Cond: (payload @> '{"type": "purchase", "userId": 42}'::jsonb)
Index Searches: 1
Planning Time: 0.413 ms
Execution Time: 0.676 ms
== 1 GIN (payload) | Q2 ->> equality userId = '42'
sql> select count(*) from event where payload->>'userId' = '42'
Aggregate (actual time=27.085..27.088 rows=1.00 loops=1)
-> Seq Scan on event (actual time=0.035..27.059 rows=10.00 loops=1)
Filter: ((payload ->> 'userId'::text) = '42'::text)
Rows Removed by Filter: 49990
Planning Time: 0.432 ms
Execution Time: 27.135 ms
== 1 GIN (payload) | Q3 key exists ? 'coupon'
sql> select count(*) from event where payload ? 'coupon'
Aggregate (actual time=0.413..0.414 rows=1.00 loops=1)
-> Bitmap Heap Scan on event (actual time=0.054..0.404 rows=100.00 loops=1)
Recheck Cond: (payload ? 'coupon'::text)
Heap Blocks: exact=100
-> Bitmap Index Scan on idx_event_gin (actual time=0.030..0.030 rows=100.00 loops=1)
Index Cond: (payload ? 'coupon'::text)
Index Searches: 1
Planning Time: 0.426 ms
Execution Time: 0.471 ms
== 1 GIN (payload) | Q4 containment @> {tags:[vip]}
sql> select count(*) from event where payload @> '{"tags":["vip"]}'
Aggregate (actual time=2.906..2.909 rows=1.00 loops=1)
-> Bitmap Heap Scan on event (actual time=1.040..2.846 rows=1000.00 loops=1)
Recheck Cond: (payload @> '{"tags": ["vip"]}'::jsonb)
Heap Blocks: exact=1000
-> Bitmap Index Scan on idx_event_gin (actual time=0.922..0.923 rows=1000.00 loops=1)
Index Cond: (payload @> '{"tags": ["vip"]}'::jsonb)
Index Searches: 1
Planning Time: 0.447 ms
Execution Time: 2.963 ms
== 2 GIN (payload jsonb_path_ops) | index size
idx_event_gin_path: 1400 kB
== 2 GIN (payload jsonb_path_ops) | Q1 containment @> {userId:42,type:purchase}
sql> select count(*) from event where payload @> '{"userId":42,"type":"purchase"}'
Aggregate (actual time=0.124..0.125 rows=1.00 loops=1)
-> Bitmap Heap Scan on event (actual time=0.072..0.120 rows=10.00 loops=1)
Recheck Cond: (payload @> '{"type": "purchase", "userId": 42}'::jsonb)
Heap Blocks: exact=10
-> Bitmap Index Scan on idx_event_gin_path (actual time=0.059..0.059 rows=10.00 loops=1)
Index Cond: (payload @> '{"type": "purchase", "userId": 42}'::jsonb)
Index Searches: 1
Planning Time: 0.487 ms
Execution Time: 0.177 ms
== 2 GIN (payload jsonb_path_ops) | Q3 key exists ? 'coupon'
sql> select count(*) from event where payload ? 'coupon'
Aggregate (actual time=7.851..7.853 rows=1.00 loops=1)
-> Seq Scan on event (actual time=0.076..7.829 rows=100.00 loops=1)
Filter: (payload ? 'coupon'::text)
Rows Removed by Filter: 49900
Planning Time: 0.479 ms
Execution Time: 7.893 ms
== 2 GIN (payload jsonb_path_ops) | Q4 containment @> {tags:[vip]}
sql> select count(*) from event where payload @> '{"tags":["vip"]}'
Aggregate (actual time=2.785..2.787 rows=1.00 loops=1)
-> Bitmap Heap Scan on event (actual time=0.741..2.724 rows=1000.00 loops=1)
Recheck Cond: (payload @> '{"tags": ["vip"]}'::jsonb)
Heap Blocks: exact=1000
-> Bitmap Index Scan on idx_event_gin_path (actual time=0.616..0.616 rows=1000.00 loops=1)
Index Cond: (payload @> '{"tags": ["vip"]}'::jsonb)
Index Searches: 1
Planning Time: 0.698 ms
Execution Time: 2.859 ms
== 3 expression b-tree | index sizes
idx_event_user_text: 480 kB
idx_event_user_int: 480 kB
== 3 expression b-tree | Q2 ->> equality userId = '42'
sql> select count(*) from event where payload->>'userId' = '42'
Aggregate (actual time=0.090..0.091 rows=1.00 loops=1)
-> Bitmap Heap Scan on event (actual time=0.040..0.084 rows=10.00 loops=1)
Recheck Cond: ((payload ->> 'userId'::text) = '42'::text)
Heap Blocks: exact=10
-> Bitmap Index Scan on idx_event_user_text (actual time=0.029..0.029 rows=10.00 loops=1)
Index Cond: ((payload ->> 'userId'::text) = '42'::text)
Index Searches: 1
Planning Time: 0.540 ms
Execution Time: 0.134 ms
== 3 expression b-tree | Q2b (payload->>'userId')::int = 42
sql> select count(*) from event where (payload->>'userId')::int = 42
Aggregate (actual time=0.095..0.096 rows=1.00 loops=1)
-> Bitmap Heap Scan on event (actual time=0.049..0.088 rows=10.00 loops=1)
Recheck Cond: (((payload ->> 'userId'::text))::integer = 42)
Heap Blocks: exact=10
-> Bitmap Index Scan on idx_event_user_int (actual time=0.039..0.039 rows=10.00 loops=1)
Index Cond: (((payload ->> 'userId'::text))::integer = 42)
Index Searches: 1
Planning Time: 0.392 ms
Execution Time: 0.133 ms
== 4 does the SQL that Hibernate generates use these indexes?
results: json_value=10, embeddable=10, function=10
== 4 | HQL json_value(e.payload, '$.userId') = '42'
sql> select count(*) from event e1_0 where json_value(e1_0.payload,'$.userId' returning jsonb)#>>'{}'='42'
Aggregate (actual time=43.806..43.809 rows=1.00 loops=1)
-> Seq Scan on event e1_0 (actual time=0.060..43.792 rows=10.00 loops=1)
Filter: ((JSON_VALUE(payload, '$."userId"' RETURNING jsonb) #>> '{}'::text[]) = '42'::text)
Rows Removed by Filter: 49990
Planning Time: 0.400 ms
Execution Time: 43.848 ms
== 4 | HQL e.payload.userId = 42 (typed @Embeddable)
sql> select count(*) from event te1_0 where cast(te1_0.payload->>'userId' as integer)=42
Aggregate (actual time=0.078..0.078 rows=1.00 loops=1)
-> Bitmap Heap Scan on event te1_0 (actual time=0.027..0.071 rows=10.00 loops=1)
Recheck Cond: (((payload ->> 'userId'::text))::integer = 42)
Heap Blocks: exact=10
-> Bitmap Index Scan on idx_event_user_int (actual time=0.015..0.015 rows=10.00 loops=1)
Index Cond: (((payload ->> 'userId'::text))::integer = 42)
Index Searches: 1
Planning Time: 0.517 ms
Execution Time: 0.122 ms
== 4 | HQL function('jsonb_extract_path_text', ...)
sql> select count(*) from event e1_0 where jsonb_extract_path_text(e1_0.payload,'userId')='42'
Aggregate (actual time=12.766..12.767 rows=1.00 loops=1)
-> Seq Scan on event e1_0 (actual time=0.028..12.753 rows=10.00 loops=1)
Filter: (jsonb_extract_path_text(payload, VARIADIC '{userId}'::text[]) = '42'::text)
Rows Removed by Filter: 49990
Planning Time: 0.451 ms
Execution Time: 12.808 ms
== summary of Execution Time (ms), this machine, indicative only
0 no index | Q1 containment @> {userId:42,type:purchase} 10.185
0 no index | Q2 ->> equality userId = '42' 24.412
0 no index | Q3 key exists ? 'coupon' 15.256
0 no index | Q4 containment @> {tags:[vip]} 20.091
1 GIN (payload) | Q1 containment @> {userId:42,type:purchase} 0.676
1 GIN (payload) | Q2 ->> equality userId = '42' 27.135
1 GIN (payload) | Q3 key exists ? 'coupon' 0.471
1 GIN (payload) | Q4 containment @> {tags:[vip]} 2.963
2 GIN (payload jsonb_path_ops) | Q1 containment @> {userId:42,type:purchase} 0.177
2 GIN (payload jsonb_path_ops) | Q3 key exists ? 'coupon' 7.893
2 GIN (payload jsonb_path_ops) | Q4 containment @> {tags:[vip]} 2.859
3 expression b-tree | Q2 ->> equality userId = '42' 0.134
3 expression b-tree | Q2b (payload->>'userId')::int = 42 0.133
4 | HQL json_value(e.payload, '$.userId') = '42' 43.848
4 | HQL e.payload.userId = 42 (typed @Embeddable) 0.122
4 | HQL function('jsonb_extract_path_text', ...) 12.808