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:
@@ -0,0 +1,21 @@
|
||||
# java -version
|
||||
openjdk version "25.0.5" 2026-10-20 LTS
|
||||
OpenJDK Runtime Environment Temurin-25.0.5+8 (build 25.0.5+8-LTS)
|
||||
OpenJDK 64-Bit Server VM Temurin-25.0.5+8 (build 25.0.5+8-LTS, mixed mode, sharing)
|
||||
|
||||
# resolved libraries (mvn dependency:list, default profile)
|
||||
com.fasterxml.jackson.core:jackson-annotations:jar:2.21:compile -- module com.fasterxml.jackson.annotation
|
||||
com.mysql:mysql-connector-j:jar:9.7.0:compile -- module mysql.connector.j (auto)
|
||||
jakarta.persistence:jakarta.persistence-api:jar:3.2.0:compile -- module jakarta.persistence
|
||||
org.hibernate.orm:hibernate-core:jar:7.4.14.Final:compile -- module org.hibernate.orm.core [auto]
|
||||
org.postgresql:postgresql:jar:42.7.14:compile -- module org.postgresql.jdbc [auto]
|
||||
org.springframework.boot:spring-boot:jar:4.1.2:compile -- module spring.boot [auto]
|
||||
org.springframework.data:spring-data-jpa:jar:4.1.2:compile -- module spring.data.jpa [auto]
|
||||
tools.jackson.core:jackson-core:jar:3.1.7:compile -- module tools.jackson.core
|
||||
tools.jackson.core:jackson-databind:jar:3.1.7:compile -- module tools.jackson.databind
|
||||
|
||||
# PostgreSQL server
|
||||
PostgreSQL 18.6 on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0, 64-bit
|
||||
|
||||
# MySQL server
|
||||
8.0.46-0ubuntu0.24.04.4
|
||||
@@ -0,0 +1,36 @@
|
||||
# PostgreSQL 18.6 on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0, 64-bit
|
||||
# Hibernate 7.4.14.Final
|
||||
|
||||
== CREATE TABLE as Hibernate emits it (JPA script generation, nothing executed)
|
||||
create table t_coldef_json (id bigint generated by default as identity, doc json, primary key (id));
|
||||
create table t_coldef_jsonb (id bigint generated by default as identity, doc jsonb, primary key (id));
|
||||
create table t_default (id bigint generated by default as identity, doc jsonb, primary key (id));
|
||||
create table t_jdbctype_json (id bigint generated by default as identity, doc jsonb, primary key (id));
|
||||
create table t_jdbctype_jsonb (id bigint generated by default as identity, doc jsonb, primary key (id));
|
||||
create table t_list (id bigint generated by default as identity, items jsonb, primary key (id));
|
||||
|
||||
== information_schema after hbm2ddl=create (column, data_type)
|
||||
t_coldef_json.doc | json
|
||||
t_coldef_jsonb.doc | jsonb
|
||||
t_default.doc | jsonb
|
||||
t_jdbctype_json.doc | jsonb
|
||||
t_jdbctype_jsonb.doc | jsonb
|
||||
t_list.items | jsonb
|
||||
|
||||
== what is stored: pg_typeof(doc) and the text PostgreSQL returns
|
||||
t_default | jsonb | {"a": 1, "b": ["x", "y"]}
|
||||
t_coldef_json | json | {"a": 1, "b": ["x", "y"]}
|
||||
t_coldef_jsonb | jsonb | {"a": 1, "b": ["x", "y"]}
|
||||
t_jdbctype_json | jsonb | {"a": 1, "b": ["x", "y"]}
|
||||
t_jdbctype_jsonb | jsonb | {"a": 1, "b": ["x", "y"]}
|
||||
t_list | jsonb | ["p", "q"]
|
||||
|
||||
== reading each variant back through Hibernate
|
||||
t_default -> {a=1, b=[x, y]}
|
||||
t_coldef_json -> {a=1, b=[x, y]}
|
||||
t_jdbctype_json-> {a=1, b=[x, y]}
|
||||
|
||||
== json vs jsonb is observable: key order, duplicate keys and whitespace
|
||||
json | jsonb: {"z": 1, "a": 2, "a": 3} | {"a": 3, "z": 1}
|
||||
json = json: ERROR: operator does not exist: json = json
|
||||
jsonb = jsonb: t
|
||||
@@ -0,0 +1,28 @@
|
||||
|
||||
== persist(): the INSERT Hibernate sends (one bind per JSON column)
|
||||
sql> insert into product (attributes,dimensions,name,specs,tags) values (?,?,?,?,?)
|
||||
sql> update product set attributes=?,dimensions=?,name=?,specs=?,tags=? where id=?
|
||||
|
||||
== what PostgreSQL stored (jsonb text)
|
||||
attributes = {"color": "red", "price": 19.90, "ratio": 0.5, "stock": 12, "active": true, "nested": {"k": [1, 2, 3]}, "nothing": null, "bigNumber": 3000000000}
|
||||
tags = ["lighting", "office"]
|
||||
dimensions = {"depthMm": 180, "widthMm": 180, "heightMm": 420}
|
||||
specs = {"material": "steel", "weightKg": 1.8, "certifications": ["CE", "RoHS"]}
|
||||
|
||||
== loaded back in a new session: Java types of the Map values
|
||||
color -> String red
|
||||
price -> Double 19.9
|
||||
ratio -> Double 0.5
|
||||
stock -> Integer 12
|
||||
active -> Boolean true
|
||||
nested -> LinkedHashMap {k=[1, 2, 3]}
|
||||
nothing -> null null
|
||||
bigNumber -> Long 3000000000
|
||||
dimensions -> Dimensions[widthMm=180, heightMm=420, depthMm=180]
|
||||
specs -> steel, 1.8, [CE, RoHS]
|
||||
|
||||
== Java null vs JSON null vs empty
|
||||
null-attrs | attributes=SQL NULL | tags=SQL NULL | dimensions=SQL NULL
|
||||
empty-attrs | attributes={} | tags=[] | dimensions=SQL NULL
|
||||
loaded null-attrs : attributes=null tags=null
|
||||
loaded empty-attrs : attributes={} tags=[]
|
||||
@@ -0,0 +1,44 @@
|
||||
|
||||
== baseline: persist a Product whose Map holds only JSON-native Java types (String, Integer, Double, Map, List)
|
||||
sql> insert into product (attributes,dimensions,name,specs,tags) values (?,?,?,?,?)
|
||||
|
||||
== load, change nothing, commit
|
||||
UPDATE statements: 0
|
||||
|
||||
== in-place mutations: load, mutate the object graph, commit, count UPDATEs
|
||||
attributes.put("color", "blue") -> 1 UPDATE
|
||||
update product set attributes=?,dimensions=?,name=?,specs=?,tags=? where id=?
|
||||
attributes.remove("stock") -> 1 UPDATE
|
||||
update product set attributes=?,dimensions=?,name=?,specs=?,tags=? where id=?
|
||||
((Map) attributes.get("nested")).put("k2", 1) [nested map] -> 1 UPDATE
|
||||
update product set attributes=?,dimensions=?,name=?,specs=?,tags=? where id=?
|
||||
((List) nested.get("k")).add(4) [list inside map] -> 1 UPDATE
|
||||
update product set attributes=?,dimensions=?,name=?,specs=?,tags=? where id=?
|
||||
tags.add("c") -> 1 UPDATE
|
||||
update product set attributes=?,dimensions=?,name=?,specs=?,tags=? where id=?
|
||||
specs.setMaterial("brass") [POJO setter] -> 1 UPDATE
|
||||
update product set attributes=?,dimensions=?,name=?,specs=?,tags=? where id=?
|
||||
specs.getCertifications().add("UL") [list inside POJO] -> 1 UPDATE
|
||||
update product set attributes=?,dimensions=?,name=?,specs=?,tags=? where id=?
|
||||
setDimensions(new Dimensions(9,9,9)) [record replaced] -> 1 UPDATE
|
||||
update product set attributes=?,dimensions=?,name=?,specs=?,tags=? where id=?
|
||||
setDimensions(equal record) [no real change] -> 0 UPDATE
|
||||
|
||||
== the changes really reached the database
|
||||
attributes = {"color": "blue", "price": 19.9, "nested": {"k": [1, 2, 3, 4], "k2": 1}}
|
||||
tags = ["a", "b", "c"]
|
||||
specs = {"material": "brass", "weightKg": 1.8, "certifications": ["CE", "UL"]}
|
||||
|
||||
== spurious UPDATEs: nothing changed, Hibernate writes anyway
|
||||
persist() with attributes.price = new BigDecimal("19.90") -> 1 UPDATE right after the INSERT
|
||||
sql> insert into product (attributes,dimensions,name,specs,tags) values (?,?,?,?,?)
|
||||
sql> update product set attributes=?,dimensions=?,name=?,specs=?,tags=? where id=?
|
||||
stored price after that persist: 19.90
|
||||
then a plain load + commit of that row -> 0 UPDATE
|
||||
load + commit of a row whose POJO has NO equals(), nothing touched -> 1 UPDATE
|
||||
sql> select l1_0.id,l1_0.specs from loose l1_0 where l1_0.id=?
|
||||
sql> update loose set specs=? where id=?
|
||||
same for Specs, which HAS equals() (Product row, nothing touched) -> 0 UPDATE
|
||||
|
||||
== @DynamicUpdate: only the changed JSON column is written
|
||||
sql> update dyn set a=? where id=?
|
||||
@@ -0,0 +1,161 @@
|
||||
# PostgreSQL 18.6 on x86_64-pc-linux-gnu
|
||||
|
||||
== PART 1: default settings (hibernate.query.hql.json_functions_enabled not set)
|
||||
|
||||
== default: json_value
|
||||
hql> select p.name, json_value(p.attributes, '$.color') from Product p where p.id = 1
|
||||
FAILED IllegalArgumentException: Can't use function 'json_value', because tech preview JSON functions are not enabled. To enable, set the 'hibernate.query.hql.json_functions_enabled' setting to 'true'. [select p.name, json_value(p.attributes, '$.color') from Product p where p.id = 1]
|
||||
|
||||
== default: json_exists
|
||||
hql> select p.name from Product p where json_exists(p.attributes, '$.color')
|
||||
FAILED IllegalArgumentException: Can't use function 'json_exists', because tech preview JSON functions are not enabled. To enable, set the 'hibernate.query.hql.json_functions_enabled' setting to 'true'. [select p.name from Product p where json_exists(p.attributes, '$.color')]
|
||||
|
||||
== default: json_set (UPDATE)
|
||||
hql> update Product p set p.attributes = json_set(p.attributes, '$.color', 'green') where p.id = 1
|
||||
sql> update product p1_0 set attributes=jsonb_set(p1_0.attributes,array['color']::text[],to_jsonb('green'::text),true) where p1_0.id=1
|
||||
OK, 1 row(s) updated
|
||||
|
||||
== PART 2: hibernate.query.hql.json_functions_enabled=true
|
||||
|
||||
== json_value, string
|
||||
hql> select p.name, json_value(p.attributes, '$.color') from Product p order by p.id
|
||||
sql> select p1_0.name,json_value(p1_0.attributes,'$.color' returning jsonb)#>>'{}' from product p1_0 order by p1_0.id
|
||||
OK (Desk lamp, red); (Floor lamp, black); (Office chair, black); (Bookshelf, oak); (Monitor arm, silver)
|
||||
|
||||
== json_value, returning integer
|
||||
hql> select p.name, json_value(p.attributes, '$.stock' returning integer) from Product p order by p.id
|
||||
sql> select p1_0.name,json_value(p1_0.attributes,'$.stock' returning integer) from product p1_0 order by p1_0.id
|
||||
OK (Desk lamp, 12); (Floor lamp, 3); (Office chair, 0); (Bookshelf, 7); (Monitor arm, 25)
|
||||
|
||||
== json_value in where
|
||||
hql> select p.name from Product p where json_value(p.attributes, '$.color') = 'black' order by p.id
|
||||
sql> select p1_0.name from product p1_0 where json_value(p1_0.attributes,'$.color' returning jsonb)#>>'{}'='black' order by p1_0.id
|
||||
OK Floor lamp; Office chair
|
||||
|
||||
== json_value nested path + array index
|
||||
hql> select p.name, json_value(p.attributes, '$.nested.aisles[1]' returning integer) from Product p order by p.id
|
||||
sql> select p1_0.name,json_value(p1_0.attributes,'$.nested.aisles[1]' returning integer) from product p1_0 order by p1_0.id
|
||||
OK (Desk lamp, 7); (Floor lamp, 7); (Office chair, 7); (Bookshelf, 7); (Monitor arm, 7)
|
||||
|
||||
== json_value on a record column
|
||||
hql> select p.name, json_value(p.dimensions, '$.widthMm' returning integer) from Product p order by p.id
|
||||
sql> select p1_0.name,json_value(p1_0.dimensions,'$.widthMm' returning integer) from product p1_0 order by p1_0.id
|
||||
OK (Desk lamp, 180); (Floor lamp, 300); (Office chair, 600); (Bookshelf, 900); (Monitor arm, 150)
|
||||
|
||||
== json_value on a list column
|
||||
hql> select p.name, json_value(p.tags, '$[0]') from Product p order by p.id
|
||||
sql> select p1_0.name,json_value(p1_0.tags,'$[0]' returning jsonb)#>>'{}' from product p1_0 order by p1_0.id
|
||||
OK (Desk lamp, lighting); (Floor lamp, lighting); (Office chair, office); (Bookshelf, furniture); (Monitor arm, office)
|
||||
|
||||
== json_value, missing key
|
||||
hql> select p.name, json_value(p.attributes, '$.nope') from Product p where p.id = 1
|
||||
sql> select p1_0.name,json_value(p1_0.attributes,'$.nope' returning jsonb)#>>'{}' from product p1_0 where p1_0.id=1
|
||||
OK (Desk lamp, null)
|
||||
|
||||
== json_query (sub-document)
|
||||
hql> select p.name, json_query(p.attributes, '$.nested') from Product p where p.id = 1
|
||||
sql> select p1_0.name,json_query(p1_0.attributes,'$.nested') from product p1_0 where p1_0.id=1
|
||||
OK (Desk lamp, {"aisles": [3, 7], "warehouse": "PUNE"})
|
||||
|
||||
== json_query with wrapper
|
||||
hql> select p.name, json_query(p.attributes, '$.nested.aisles[*]' with wrapper) from Product p where p.id = 1
|
||||
sql> select p1_0.name,json_query(p1_0.attributes,'$.nested.aisles[*]' with wrapper) from product p1_0 where p1_0.id=1
|
||||
OK (Desk lamp, [3, 7])
|
||||
|
||||
== json_exists
|
||||
hql> select p.name from Product p where json_exists(p.attributes, '$.nested.warehouse') order by p.id
|
||||
sql> select p1_0.name from product p1_0 where json_exists(p1_0.attributes,'$.nested.warehouse') order by p1_0.id
|
||||
OK Desk lamp; Floor lamp; Office chair; Bookshelf; Monitor arm
|
||||
|
||||
== json_exists with filter
|
||||
hql> select p.name from Product p where json_exists(p.attributes, '$.stock ? (@ > 5)') order by p.id
|
||||
sql> select p1_0.name from product p1_0 where json_exists(p1_0.attributes,'$.stock ? (@ > 5)') order by p1_0.id
|
||||
OK Desk lamp; Bookshelf; Monitor arm
|
||||
|
||||
== json_object
|
||||
hql> select json_object('name', p.name, 'id', p.id) from Product p where p.id = 1
|
||||
sql> select json_object('name' value p1_0.name,'id' value p1_0.id) from product p1_0 where p1_0.id=1
|
||||
OK {"name" : "Desk lamp", "id" : 1}
|
||||
|
||||
== json_object, key/value keywords
|
||||
hql> select json_object(key 'name' value p.name) from Product p where p.id = 1
|
||||
sql> select json_object('name' value p1_0.name) from product p1_0 where p1_0.id=1
|
||||
OK {"name" : "Desk lamp"}
|
||||
|
||||
== json_array
|
||||
hql> select json_array(p.name, p.id, null) from Product p where p.id = 1
|
||||
sql> select json_array(p1_0.name,p1_0.id,null) from product p1_0 where p1_0.id=1
|
||||
OK ["Desk lamp", 1]
|
||||
|
||||
== json_arrayagg
|
||||
hql> select json_arrayagg(p.name) from Product p
|
||||
sql> select json_arrayagg(p1_0.name absent on null returning jsonb) from product p1_0
|
||||
OK ["Desk lamp", "Floor lamp", "Office chair", "Bookshelf", "Monitor arm"]
|
||||
|
||||
== json_objectagg
|
||||
hql> select json_objectagg(key p.name value p.id) from Product p
|
||||
sql> select json_objectagg(p1_0.name:p1_0.id absent on null returning jsonb) from product p1_0
|
||||
OK {"Bookshelf": 4, "Desk lamp": 1, "Floor lamp": 2, "Monitor arm": 5, "Office chair": 3}
|
||||
|
||||
== json_table over a list column
|
||||
hql> select p.name, t.tag from Product p join lateral json_table(p.tags, '$[*]' columns (tag String path '$')) t where p.id = 1
|
||||
sql> select p1_0.name,t1_0.tag from product p1_0 join lateral json_table(p1_0.tags,'$[*]' columns(tag text path '$')) t1_0 on true where p1_0.id=1
|
||||
OK (Desk lamp, lighting); (Desk lamp, office)
|
||||
|
||||
== json_table, tags counted
|
||||
hql> select t.tag, count(*) from Product p join lateral json_table(p.tags, '$[*]' columns (tag String path '$')) t group by t.tag order by t.tag
|
||||
sql> select t1_0.tag,count(*) from product p1_0 join lateral json_table(p1_0.tags,'$[*]' columns(tag text path '$')) t1_0 on true group by t1_0.tag order by t1_0.tag
|
||||
OK (accessories, 1); (furniture, 2); (home, 2); (lighting, 2); (office, 3)
|
||||
|
||||
== json_set
|
||||
hql> update Product p set p.attributes = json_set(p.attributes, '$.color', 'green') where p.id = 1
|
||||
sql> update product p1_0 set attributes=jsonb_set(p1_0.attributes,array['color']::text[],to_jsonb('green'::text),true) where p1_0.id=1
|
||||
OK, 1 row(s) updated
|
||||
|
||||
== json_insert
|
||||
hql> update Product p set p.attributes = json_insert(p.attributes, '$.brand', 'Acme') where p.id = 1
|
||||
sql> update product p1_0 set attributes=(select case when (t.d)#>>t.p is not null then t.d else jsonb_insert(t.d,t.p,to_jsonb('Acme'::text),true) end from (values(p1_0.attributes,array['brand']::text[])) t(d,p)) where p1_0.id=1
|
||||
OK, 1 row(s) updated
|
||||
|
||||
== json_replace
|
||||
hql> update Product p set p.attributes = json_replace(p.attributes, '$.stock', 99) where p.id = 1
|
||||
sql> update product p1_0 set attributes=jsonb_set(p1_0.attributes,array['stock']::text[],to_jsonb(99),false) where p1_0.id=1
|
||||
OK, 1 row(s) updated
|
||||
|
||||
== json_remove
|
||||
hql> update Product p set p.attributes = json_remove(p.attributes, '$.price') where p.id = 1
|
||||
sql> update product p1_0 set attributes=p1_0.attributes#-array['price']::text[] where p1_0.id=1
|
||||
OK, 1 row(s) updated
|
||||
|
||||
== json_mergepatch
|
||||
hql> update Product p set p.attributes = json_mergepatch(p.attributes, '{"stock": 1, "brand": null}') where p.id = 2
|
||||
sql> update product p1_0 set attributes=(with recursive args(d0,d1) as(select p1_0.attributes,cast('{"stock": 1, "brand": null}' as jsonb)),val0(p,k,v) as (select '{}'::text[],s.k,t.d0->s.k from args t join lateral jsonb_object_keys(t.d0) s(k) on 1=1 union select v.p||v.k,s.k,v.v->s.k from val0 v join lateral jsonb_object_keys(v.v) s(k) on jsonb_typeof(v.v)='object'),val1(p,k,v) as (select '{}'::text[],s.k,t.d1->s.k from args t join lateral jsonb_object_keys(t.d1) s(k) on 1=1 union select v.p||v.k,s.k,v.v->s.k from val1 v join lateral jsonb_object_keys(v.v) s(k) on jsonb_typeof(v.v)='object'),res(v,p,l) as(select jsonb_object_agg(coalesce(v1.k,v0.k),coalesce(v1.v,v0.v)),coalesce(v1.p,v0.p),cardinality(coalesce(v1.p,v0.p)) from val0 v0 full join val1 v1 on v0.p=v1.p and v0.k=v1.k where cardinality(coalesce(v1.p,v0.p))=(select cardinality(v.p) from val0 v union select cardinality(v.p) from val1 v order by 1 desc limit 1) and jsonb_typeof(coalesce(v1.v)) is distinct from 'null' group by coalesce(v1.p,v0.p),cardinality(coalesce(v1.p,v0.p)) union all select jsonb_object_agg(coalesce(v1.k,v0.k),coalesce(case when coalesce(v1.k,v0.k)=r.p[cardinality(r.p)] then r.v end,v1.v,v0.v)) filter (where coalesce(case when coalesce(v1.k,v0.k)=r.p[cardinality(r.p)] then r.v end,v1.v,v0.v) is not null),coalesce(v1.p,v0.p),r.l-1 from val0 v0 full join val1 v1 on v0.p=v1.p and v0.k=v1.k join (select * from res r order by r.l fetch first 1 rows with ties) r on cardinality(coalesce(v1.p,v0.p))=r.l-1 and jsonb_typeof(coalesce(v1.v)) is distinct from 'null' and r.l<>0 group by coalesce(v1.p,v0.p),r.l-1) select r.v from res r where r.l=0) where p1_0.id=2
|
||||
OK, 1 row(s) updated
|
||||
|
||||
== json_array_append, root path $
|
||||
hql> update Product p set p.tags = json_array_append(p.tags, '$', 'sale') where p.id = 1
|
||||
sql> update product p1_0 set tags=(select jsonb_set_lax(t.d,t.p,(t.d)#>t.p||to_jsonb('sale'::text),false,'return_target') from (values(p1_0.tags,array]::text[])) t(d,p)) where p1_0.id=1
|
||||
FAILED SQLGrammarException: ERROR: syntax error at or near "]"
|
||||
|
||||
== json_array_append, nested path
|
||||
hql> update Product p set p.attributes = json_array_append(p.attributes, '$.nested.aisles', 11) where p.id = 1
|
||||
sql> update product p1_0 set attributes=(select jsonb_set_lax(t.d,t.p,(t.d)#>t.p||to_jsonb(11),false,'return_target') from (values(p1_0.attributes,array['nested','aisles']::text[])) t(d,p)) where p1_0.id=1
|
||||
OK, 1 row(s) updated
|
||||
|
||||
== json_array_insert
|
||||
hql> update Product p set p.tags = json_array_insert(p.tags, '$[0]', 'first') where p.id = 2
|
||||
sql> update product p1_0 set tags=jsonb_insert(p1_0.tags,array['0']::text[],to_jsonb('first'::text)) where p1_0.id=2
|
||||
OK, 1 row(s) updated
|
||||
|
||||
== attribute navigation p.attributes.color
|
||||
hql> select p.name from Product p where p.attributes.color = 'black'
|
||||
FAILED IllegalArgumentException: Could not interpret attribute 'color' of basic-valued path 'com.ankurm.jsoncolumns.model.Product(p).attributes'
|
||||
|
||||
== function('jsonb_extract_path_text')
|
||||
hql> select function('jsonb_extract_path_text', p.attributes, 'color') from Product p where p.id = 3
|
||||
sql> select jsonb_extract_path_text(p1_0.attributes,'color') from product p1_0 where p1_0.id=3
|
||||
OK black
|
||||
|
||||
== rows after the UPDATE experiments (raw jsonb)
|
||||
1 {"brand": "Acme", "color": "green", "stock": 99, "nested": {"aisles": [3, 7, 11], "warehouse": "PUNE"}} ["lighting", "office"]
|
||||
2 {"color": "black", "price": 89.5, "stock": 1, "nested": {"aisles": [3, 7], "warehouse": "PUNE"}} ["first", "lighting", "home"]
|
||||
@@ -0,0 +1,28 @@
|
||||
# PostgreSQL 18.6 on x86_64-pc-linux-gnu
|
||||
|
||||
== DDL
|
||||
id bigint
|
||||
name character varying
|
||||
address jsonb
|
||||
|
||||
== stored
|
||||
Asha {"zip": "411001", "city": "Pune", "floor": 3}
|
||||
Ravi {"zip": "400001", "city": "Mumbai", "floor": 12}
|
||||
Meera {"zip": "411045", "city": "Pune", "floor": 1}
|
||||
|
||||
== HQL: where c.address.city = 'Pune'
|
||||
sql> select c1_0.name from customer c1_0 where cast(c1_0.address->>'city' as varchar)='Pune' order by c1_0.id
|
||||
result: [Asha, Meera]
|
||||
|
||||
== HQL: select and order by an int attribute inside the JSON
|
||||
sql> select cast(c1_0.address->>'floor' as integer) from customer c1_0 where cast(c1_0.address->>'floor' as integer)>2 order by cast(c1_0.address->>'floor' as integer)
|
||||
result: [3, 12]
|
||||
|
||||
== HQL: update c.address.floor = 99
|
||||
sql> update customer c1_0 set address=coalesce(c1_0.address,'{}')||jsonb_build_object('floor',to_jsonb(99)) where c1_0.name='Ravi'
|
||||
rows updated: 1; stored: {"zip": "400001", "city": "Mumbai", "floor": 99}
|
||||
|
||||
== HQL: group by c.address.city
|
||||
sql> select cast(c1_0.address->>'city' as varchar),count(*) from customer c1_0 group by cast(c1_0.address->>'city' as varchar) order by cast(c1_0.address->>'city' as varchar)
|
||||
row: Mumbai 1
|
||||
row: Pune 2
|
||||
@@ -0,0 +1,94 @@
|
||||
# PostgreSQL 18.6 on x86_64-pc-linux-gnu
|
||||
|
||||
== ->> text extraction
|
||||
sql> select name from product where attributes->>'color' = 'black' order by id
|
||||
OK Floor lamp; Office chair
|
||||
|
||||
== ->> with a named parameter
|
||||
sql> select name from product where attributes->>'color' = :c order by id -- {c=black}
|
||||
OK Floor lamp; Office chair
|
||||
|
||||
== -> then #>> path
|
||||
sql> select name from product where attributes #>> '{nested,warehouse}' = 'PUNE' and id = 1
|
||||
OK Desk lamp
|
||||
|
||||
== ->> cast to int for comparison
|
||||
sql> select name from product where (attributes->>'stock')::int > 5 order by id
|
||||
OK Desk lamp; Bookshelf; Monitor arm
|
||||
|
||||
== @> containment, literal
|
||||
sql> select name from product where tags @> '["office"]' order by id
|
||||
OK Desk lamp; Office chair; Monitor arm
|
||||
|
||||
== @> containment, parameter with cast(:p as jsonb)
|
||||
sql> select name from product where attributes @> cast(:p as jsonb) order by id -- {p={"color":"black"}}
|
||||
OK Floor lamp; Office chair
|
||||
|
||||
== @> containment, parameter with :p::jsonb
|
||||
sql> select name from product where attributes @> :p::jsonb order by id -- {p={"color":"black"}}
|
||||
FAILED IllegalArgumentException: No parameter named ':p' in query with named parameters [p::jsonb]
|
||||
|
||||
== ? key-exists, bare question mark
|
||||
sql> select name from product where attributes ? 'color' order by id
|
||||
FAILED QueryParameterException: No argument for ordinal parameter '?1'
|
||||
|
||||
== ? key-exists, backslash-escaped
|
||||
sql> select name from product where attributes \? 'color' order by id
|
||||
FAILED DataException: No value specified for parameter 1.
|
||||
|
||||
== ? key-exists, each ? escaped: \?\?
|
||||
sql> select name from product where attributes \?\? 'color' order by id
|
||||
OK Desk lamp; Floor lamp; Office chair; Bookshelf; Monitor arm
|
||||
|
||||
== ? key-exists, doubled ??
|
||||
sql> select name from product where attributes ?? 'color' order by id
|
||||
FAILED QueryParameterException: No argument for ordinal parameter '?1'
|
||||
|
||||
== jsonb_exists() function instead of ?
|
||||
sql> select name from product where jsonb_exists(attributes, 'color') order by id
|
||||
OK Desk lamp; Floor lamp; Office chair; Bookshelf; Monitor arm
|
||||
|
||||
== ?| any-key, doubled ??|
|
||||
sql> select name from product where tags ??| array['home','accessories'] order by id
|
||||
FAILED QueryParameterException: No argument for ordinal parameter '?1'
|
||||
|
||||
== ?| any-key, escaped: \?\?|
|
||||
sql> select name from product where tags \?\?| array['home','accessories'] order by id
|
||||
OK Floor lamp; Bookshelf; Monitor arm
|
||||
|
||||
== jsonb_exists_any() function instead of ?|
|
||||
sql> select name from product where jsonb_exists_any(tags, array['home','accessories']) order by id
|
||||
OK Floor lamp; Bookshelf; Monitor arm
|
||||
|
||||
== jsonb_path_exists()
|
||||
sql> select name from product where jsonb_path_exists(attributes, '$.stock ? (@ > 5)') order by id
|
||||
OK Desk lamp; Bookshelf; Monitor arm
|
||||
|
||||
== @? path operator
|
||||
sql> select name from product where attributes @? '$.stock ? (@ > 5)' order by id
|
||||
FAILED QueryParameterException: No argument for ordinal parameter '?1'
|
||||
|
||||
== @? path operator, escaped: @\?\?
|
||||
sql> select name from product where attributes @\?\? '$.stock ? (@ > 5)' order by id
|
||||
OK Desk lamp; Bookshelf; Monitor arm
|
||||
|
||||
== @@ path predicate
|
||||
sql> select name from product where attributes @@ '$.stock > 5' order by id
|
||||
OK Desk lamp; Bookshelf; Monitor arm
|
||||
|
||||
== jsonb_array_elements_text in a lateral join
|
||||
sql> select t.tag, count(*) from product p, jsonb_array_elements_text(p.tags) t(tag) group by t.tag order by t.tag
|
||||
OK (accessories, 1); (furniture, 2); (home, 2); (lighting, 2); (office, 3)
|
||||
|
||||
== jsonb_set in an UPDATE (native)
|
||||
sql> update product set attributes = jsonb_set(attributes, '{color}', to_jsonb(cast(:v as text))) where id = 3 -- {v=white}
|
||||
OK, 1 row(s) updated
|
||||
|
||||
== What does a native SELECT of a jsonb column hand back to Java?
|
||||
createNativeQuery(sql).getSingleResult() -> java.lang.String
|
||||
createNativeQuery(sql, Map.class) -> java.util.HashMap
|
||||
createNativeQuery(sql, Product.class) -> attributes.color = red
|
||||
|
||||
== HQL equality on a jsonb column with a Java value as parameter
|
||||
where p.tags = :t with List.of("furniture","home") -> [Bookshelf]
|
||||
where p.tags = :t with List.of("home","furniture") -> [] (jsonb arrays are ordered)
|
||||
@@ -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
|
||||
@@ -0,0 +1,186 @@
|
||||
# MySQL 8.0.46-0ubuntu0.24.04.4
|
||||
# Hibernate 7.4.14.Final
|
||||
|
||||
== CREATE TABLE as Hibernate emits it for MySQL
|
||||
create table product (id bigint not null auto_increment, name varchar(255), attributes json, dimensions json, specs json, tags json, primary key (id)) engine=InnoDB;
|
||||
|
||||
== column types in information_schema
|
||||
attributes json
|
||||
dimensions json
|
||||
specs json
|
||||
tags json
|
||||
|
||||
== what MySQL stored (it normalises: keys sorted by length, then alphabetically)
|
||||
attributes = {"color": "red", "price": 19.9, "stock": 12, "nested": {"aisles": [3, 7], "warehouse": "PUNE"}}
|
||||
dimensions = {"depthMm": 180, "widthMm": 180, "heightMm": 360}
|
||||
|
||||
== dirty checking on MySQL
|
||||
load, change nothing: 0 UPDATE; attributes.put("color","teal"): 1 UPDATE
|
||||
stored color: teal
|
||||
|
||||
== HQL JSON functions on MySQL (same statements as 04-hql-json-functions-postgresql.txt)
|
||||
|
||||
== json_value, string
|
||||
hql> select p.name, json_value(p.attributes, '$.color') from Product p order by p.id
|
||||
sql> select p1_0.name,json_unquote(nullif(json_extract(p1_0.attributes,'$.color'),cast('null' as json))) from product p1_0 order by p1_0.id
|
||||
OK (Desk lamp, teal); (Floor lamp, black); (Office chair, black); (Bookshelf, oak); (Monitor arm, silver)
|
||||
|
||||
== json_value, returning integer
|
||||
hql> select p.name, json_value(p.attributes, '$.stock' returning integer) from Product p order by p.id
|
||||
sql> select p1_0.name,cast(json_unquote(nullif(json_extract(p1_0.attributes,'$.stock'),cast('null' as json))) as signed) from product p1_0 order by p1_0.id
|
||||
OK (Desk lamp, 12); (Floor lamp, 3); (Office chair, 0); (Bookshelf, 7); (Monitor arm, 25)
|
||||
|
||||
== json_value in where
|
||||
hql> select p.name from Product p where json_value(p.attributes, '$.color') = 'black' order by p.id
|
||||
sql> select p1_0.name from product p1_0 where json_unquote(nullif(json_extract(p1_0.attributes,'$.color'),cast('null' as json)))='black' order by p1_0.id
|
||||
OK Floor lamp; Office chair
|
||||
|
||||
== json_value nested path + array index
|
||||
hql> select p.name, json_value(p.attributes, '$.nested.aisles[1]' returning integer) from Product p order by p.id
|
||||
sql> select p1_0.name,cast(json_unquote(nullif(json_extract(p1_0.attributes,'$.nested.aisles[1]'),cast('null' as json))) as signed) from product p1_0 order by p1_0.id
|
||||
OK (Desk lamp, 7); (Floor lamp, 7); (Office chair, 7); (Bookshelf, 7); (Monitor arm, 7)
|
||||
|
||||
== json_value on a record column
|
||||
hql> select p.name, json_value(p.dimensions, '$.widthMm' returning integer) from Product p order by p.id
|
||||
sql> select p1_0.name,cast(json_unquote(nullif(json_extract(p1_0.dimensions,'$.widthMm'),cast('null' as json))) as signed) from product p1_0 order by p1_0.id
|
||||
OK (Desk lamp, 180); (Floor lamp, 300); (Office chair, 600); (Bookshelf, 900); (Monitor arm, 150)
|
||||
|
||||
== json_value on a list column
|
||||
hql> select p.name, json_value(p.tags, '$[0]') from Product p order by p.id
|
||||
sql> select p1_0.name,json_unquote(nullif(json_extract(p1_0.tags,'$[0]'),cast('null' as json))) from product p1_0 order by p1_0.id
|
||||
OK (Desk lamp, lighting); (Floor lamp, lighting); (Office chair, office); (Bookshelf, furniture); (Monitor arm, office)
|
||||
|
||||
== json_value, missing key
|
||||
hql> select p.name, json_value(p.attributes, '$.nope') from Product p where p.id = 1
|
||||
sql> select p1_0.name,json_unquote(nullif(json_extract(p1_0.attributes,'$.nope'),cast('null' as json))) from product p1_0 where p1_0.id=1
|
||||
OK (Desk lamp, null)
|
||||
|
||||
== json_query (sub-document)
|
||||
hql> select p.name, json_query(p.attributes, '$.nested') from Product p where p.id = 1
|
||||
sql> select p1_0.name,nullif(json_extract(p1_0.attributes,'$.nested'),cast('null' as json)) from product p1_0 where p1_0.id=1
|
||||
OK (Desk lamp, {"aisles": [3, 7], "warehouse": "PUNE"})
|
||||
|
||||
== json_query with wrapper
|
||||
hql> select p.name, json_query(p.attributes, '$.nested.aisles[*]' with wrapper) from Product p where p.id = 1
|
||||
sql> select p1_0.name,nullif(json_extract(p1_0.attributes,'$.nested.aisles[*]'),cast('null' as json)) from product p1_0 where p1_0.id=1
|
||||
OK (Desk lamp, [3, 7])
|
||||
|
||||
== json_exists
|
||||
hql> select p.name from Product p where json_exists(p.attributes, '$.nested.warehouse') order by p.id
|
||||
sql> select p1_0.name from product p1_0 where json_contains_path(p1_0.attributes,'one','$.nested.warehouse') order by p1_0.id
|
||||
OK Desk lamp; Floor lamp; Office chair; Bookshelf; Monitor arm
|
||||
|
||||
== json_exists with filter
|
||||
hql> select p.name from Product p where json_exists(p.attributes, '$.stock ? (@ > 5)') order by p.id
|
||||
sql> select p1_0.name from product p1_0 where json_contains_path(p1_0.attributes,'one','$.stock ? (@ > 5)') order by p1_0.id
|
||||
FAILED SQLGrammarException: Invalid JSON path expression. The error is around character position 8.
|
||||
|
||||
== json_object
|
||||
hql> select json_object('name', p.name, 'id', p.id) from Product p where p.id = 1
|
||||
sql> select json_object('name',p1_0.name,'id',p1_0.id) from product p1_0 where p1_0.id=1
|
||||
OK {"id": 1, "name": "Desk lamp"}
|
||||
|
||||
== json_object, key/value keywords
|
||||
hql> select json_object(key 'name' value p.name) from Product p where p.id = 1
|
||||
sql> select json_object('name',p1_0.name) from product p1_0 where p1_0.id=1
|
||||
OK {"name": "Desk lamp"}
|
||||
|
||||
== json_array
|
||||
hql> select json_array(p.name, p.id, null) from Product p where p.id = 1
|
||||
sql> select json_array(p1_0.name,p1_0.id,null) from product p1_0 where p1_0.id=1
|
||||
OK ["Desk lamp", 1, null]
|
||||
|
||||
== json_arrayagg
|
||||
hql> select json_arrayagg(p.name) from Product p
|
||||
sql> select cast(concat('[',group_concat(nullif(json_extract(json_array(p1_0.name),'$[0]'),cast('null' as json)) separator ','),']') as json) from product p1_0
|
||||
OK ["Desk lamp", "Floor lamp", "Office chair", "Bookshelf", "Monitor arm"]
|
||||
|
||||
== json_objectagg
|
||||
hql> select json_objectagg(key p.name value p.id) from Product p
|
||||
sql> select concat('{',group_concat(concat(json_quote(p1_0.name),':',nullif(json_extract(json_array(p1_0.id),'$[0]'),cast('null' as json))) separator ','),'}') from product p1_0
|
||||
OK {"Desk lamp":1,"Floor lamp":2,"Office chair":3,"Bookshelf":4,"Monitor arm":5}
|
||||
|
||||
== json_table over a list column
|
||||
hql> select p.name, t.tag from Product p join lateral json_table(p.tags, '$[*]' columns (tag String path '$')) t where p.id = 1
|
||||
sql> select p1_0.name,t1_0.tag from product p1_0 join json_table(p1_0.tags,'$[*]' columns(tag longtext path '$')) t1_0 on true where p1_0.id=1
|
||||
OK (Desk lamp, lighting); (Desk lamp, office)
|
||||
|
||||
== json_table, tags counted
|
||||
hql> select t.tag, count(*) from Product p join lateral json_table(p.tags, '$[*]' columns (tag String path '$')) t group by t.tag order by t.tag
|
||||
sql> select t1_0.tag,count(*) from product p1_0 join json_table(p1_0.tags,'$[*]' columns(tag longtext path '$')) t1_0 on true group by t1_0.tag order by t1_0.tag
|
||||
OK (accessories, 1); (furniture, 2); (home, 2); (lighting, 2); (office, 3)
|
||||
|
||||
== json_set
|
||||
hql> update Product p set p.attributes = json_set(p.attributes, '$.color', 'green') where p.id = 1
|
||||
sql> update product p1_0 set attributes=json_set(p1_0.attributes,'$.color','green') where p1_0.id=1
|
||||
OK, 1 row(s) updated
|
||||
|
||||
== json_insert
|
||||
hql> update Product p set p.attributes = json_insert(p.attributes, '$.brand', 'Acme') where p.id = 1
|
||||
sql> update product p1_0 set attributes=json_insert(p1_0.attributes,'$.brand','Acme') where p1_0.id=1
|
||||
OK, 1 row(s) updated
|
||||
|
||||
== json_replace
|
||||
hql> update Product p set p.attributes = json_replace(p.attributes, '$.stock', 99) where p.id = 1
|
||||
sql> update product p1_0 set attributes=json_replace(p1_0.attributes,'$.stock',99) where p1_0.id=1
|
||||
OK, 1 row(s) updated
|
||||
|
||||
== json_remove
|
||||
hql> update Product p set p.attributes = json_remove(p.attributes, '$.price') where p.id = 1
|
||||
sql> update product p1_0 set attributes=json_remove(p1_0.attributes,'$.price') where p1_0.id=1
|
||||
OK, 1 row(s) updated
|
||||
|
||||
== json_mergepatch
|
||||
hql> update Product p set p.attributes = json_mergepatch(p.attributes, '{"stock": 1, "brand": null}') where p.id = 2
|
||||
sql> update product p1_0 set attributes=json_merge_patch(p1_0.attributes,'{"stock": 1, "brand": null}') where p1_0.id=2
|
||||
OK, 1 row(s) updated
|
||||
|
||||
== json_array_append, root path $
|
||||
hql> update Product p set p.tags = json_array_append(p.tags, '$', 'sale') where p.id = 1
|
||||
sql> update product p1_0 set tags=json_array_append(p1_0.tags,'$','sale') where p1_0.id=1
|
||||
OK, 1 row(s) updated
|
||||
|
||||
== json_array_append, nested path
|
||||
hql> update Product p set p.attributes = json_array_append(p.attributes, '$.nested.aisles', 11) where p.id = 1
|
||||
sql> update product p1_0 set attributes=json_array_append(p1_0.attributes,'$.nested.aisles',11) where p1_0.id=1
|
||||
OK, 1 row(s) updated
|
||||
|
||||
== json_array_insert
|
||||
hql> update Product p set p.tags = json_array_insert(p.tags, '$[0]', 'first') where p.id = 2
|
||||
sql> update product p1_0 set tags=json_array_insert(p1_0.tags,'$[0]','first') where p1_0.id=2
|
||||
OK, 1 row(s) updated
|
||||
|
||||
== attribute navigation p.attributes.color
|
||||
hql> select p.name from Product p where p.attributes.color = 'black'
|
||||
FAILED IllegalArgumentException: Could not interpret attribute 'color' of basic-valued path 'com.ankurm.jsoncolumns.model.Product(p).attributes'
|
||||
|
||||
== native JSON functions and operators (MySQL spellings)
|
||||
->> text extraction -> Floor lamp; Office chair
|
||||
JSON_CONTAINS(tags, '"office"') -> Desk lamp; Office chair; Monitor arm
|
||||
'office' MEMBER OF (tags) -> Desk lamp; Office chair; Monitor arm
|
||||
JSON_OVERLAPS(tags, '["home","accessories"]') -> Floor lamp; Bookshelf; Monitor arm
|
||||
JSON_CONTAINS_PATH(attributes, 'one', '$.nested.warehouse') -> 5
|
||||
|
||||
== @Embeddable as JSON on MySQL
|
||||
sql> select c1_0.name from customer c1_0 where cast(json_unquote(nullif(json_extract(c1_0.address,'$.city'),cast('null' as json))) as char)='Pune'
|
||||
result: [Asha]
|
||||
|
||||
== indexing 50,000 rows on MySQL
|
||||
rows: 50000
|
||||
|
||||
== no index
|
||||
sql> select count(*) from event where payload->>'$.userId' = '42'
|
||||
type=ALL key=null rows=49800 extra=Using where
|
||||
sql> select count(*) from event where cast(payload->>'$.userId' as unsigned) = 42
|
||||
type=ALL key=null rows=49800 extra=Using where
|
||||
sql> select count(*) from event where 'vip' member of (payload->'$.tags')
|
||||
type=ALL key=null rows=49800 extra=Using where
|
||||
|
||||
== functional index on cast(payload->>'$.userId' as unsigned), multi-valued index on tags
|
||||
sql> select count(*) from event where payload->>'$.userId' = '42'
|
||||
type=ALL key=null rows=49800 extra=Using where
|
||||
sql> select count(*) from event where cast(payload->>'$.userId' as unsigned) = 42
|
||||
type=ref key=idx_user rows=10 extra=null
|
||||
sql> select count(*) from event where 'vip' member of (payload->'$.tags')
|
||||
type=ref key=idx_tags rows=1000 extra=Using where
|
||||
count via index: 10 / vip rows: 1000
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
== which FormatMapper with only Jackson 3 on the classpath, nothing configured
|
||||
getJsonFormatMapper() -> org.hibernate.type.format.jackson.Jackson3JsonFormatMapper
|
||||
Jackson 2 present? false; Jackson 3 present? true
|
||||
|
||||
== schema evolution: stored JSON that does not match the record
|
||||
extra-key -> FAILED tools.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized property "colour" (class com.ankurm.jsoncolumns.model.Dimensions), not marked as ignorable (3 known properties: "widthMm", "heightMm", "depthMm")
|
||||
missing-key -> loaded Dimensions[widthMm=1, heightMm=0, depthMm=0]
|
||||
wrong-type -> FAILED tools.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `int` from String "wide": not a valid `int` value
|
||||
not-an-object -> FAILED tools.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `com.ankurm.jsoncolumns.model.Dimensions` from Array value (token `JsonToken.START_ARRAY`)
|
||||
|
||||
== the fix for extra keys: @JsonIgnoreProperties(ignoreUnknown = true) on the record
|
||||
stored {"widthMm":7,"colour":"red"} -> loaded Tolerant[widthMm=7]
|
||||
|
||||
== hibernate.type.json_format_mapper = your own Jackson 3 JsonMapper (snake_case)
|
||||
dimensions = {"depth_mm": 3, "width_mm": 1, "height_mm": 2}
|
||||
specs = {"material": "tin", "weight_kg": 0.5, "certifications": []}
|
||||
read back = Dimensions[widthMm=1, heightMm=2, depthMm=3]
|
||||
|
||||
== java.time inside a JSON value with the default mapper
|
||||
Map.of("at", Instant) stored as {"at": 1793355330.000000000}
|
||||
read back as {at=1.79335533E9} (Double)
|
||||
@@ -0,0 +1,57 @@
|
||||
# Hibernate hibernate-core-7.4.14.Final.jar
|
||||
== A. Jackson 3 only, setting not set
|
||||
setting : hibernate.type.json_format_mapper = (not set)
|
||||
on classpath: jackson2=false jackson3=true jakarta.json=false
|
||||
mapper : org.hibernate.type.format.jackson.Jackson3JsonFormatMapper
|
||||
stored : {"n": 1, "list": ["a", "b"]} ; {"at": 1793355330.000000000, "label": "boot"}
|
||||
read back : {n=1, list=[a, b]} ; Stamp[label=boot, at=2026-10-30T10:15:30Z]
|
||||
|
||||
== B. Jackson 2 + Jackson 3, setting not set
|
||||
setting : hibernate.type.json_format_mapper = (not set)
|
||||
on classpath: jackson2=true jackson3=true jakarta.json=false
|
||||
mapper : org.hibernate.type.format.jackson.JacksonJsonFormatMapper
|
||||
FAILED : com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.Instant` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (or disable `MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_TIMES`) (through reference chain: com.ankurm.jsoncolumns.FormatMapperProbe$Stamp["at"])
|
||||
|
||||
== C. Jackson 2 only, setting not set
|
||||
setting : hibernate.type.json_format_mapper = (not set)
|
||||
on classpath: jackson2=true jackson3=false jakarta.json=false
|
||||
mapper : org.hibernate.type.format.jackson.JacksonJsonFormatMapper
|
||||
FAILED : com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.Instant` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (or disable `MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_TIMES`) (through reference chain: com.ankurm.jsoncolumns.FormatMapperProbe$Stamp["at"])
|
||||
|
||||
== D. no Jackson at all, setting not set
|
||||
setting : hibernate.type.json_format_mapper = (not set)
|
||||
on classpath: jackson2=false jackson3=false jakarta.json=false
|
||||
FAILED : org.hibernate.HibernateException: Could not find a FormatMapper for the JSON format, which is required for mapping JSON types. JSON FormatMapper configuration is automatic, but requires that you have either Jackson or a JSONB implementation like Yasson on the class path.
|
||||
|
||||
== E. Jackson 2 + Jackson 3, setting = jackson (Jackson 2)
|
||||
setting : hibernate.type.json_format_mapper = jackson
|
||||
on classpath: jackson2=true jackson3=true jakarta.json=false
|
||||
mapper : org.hibernate.type.format.jackson.JacksonJsonFormatMapper
|
||||
FAILED : com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.Instant` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (or disable `MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_TIMES`) (through reference chain: com.ankurm.jsoncolumns.FormatMapperProbe$Stamp["at"])
|
||||
|
||||
== F. Jackson 3 only, setting = jackson (Jackson 2 absent)
|
||||
setting : hibernate.type.json_format_mapper = jackson
|
||||
on classpath: jackson2=false jackson3=true jakarta.json=false
|
||||
FAILED : java.lang.ClassNotFoundException: com.fasterxml.jackson.core.JsonProcessingException
|
||||
|
||||
== G. Jackson 2 + Jackson 3, setting = jackson3
|
||||
setting : hibernate.type.json_format_mapper = jackson3
|
||||
on classpath: jackson2=true jackson3=true jakarta.json=false
|
||||
mapper : org.hibernate.type.format.jackson.Jackson3JsonFormatMapper
|
||||
stored : {"n": 1, "list": ["a", "b"]} ; {"at": 1793355330.000000000, "label": "boot"}
|
||||
read back : {n=1, list=[a, b]} ; Stamp[label=boot, at=2026-10-30T10:15:30Z]
|
||||
|
||||
== H. Jackson 2 + jackson-datatype-jsr310, setting not set
|
||||
setting : hibernate.type.json_format_mapper = (not set)
|
||||
on classpath: jackson2=true jackson3=false jakarta.json=false
|
||||
mapper : org.hibernate.type.format.jackson.JacksonJsonFormatMapper
|
||||
stored : {"n": 1, "list": ["a", "b"]} ; {"at": 1793355330.000000000, "label": "boot"}
|
||||
read back : {n=1, list=[a, b]} ; Stamp[label=boot, at=2026-10-30T10:15:30Z]
|
||||
|
||||
== I. Jackson 2 + Jackson 3 + jsr310, setting not set
|
||||
setting : hibernate.type.json_format_mapper = (not set)
|
||||
on classpath: jackson2=true jackson3=true jakarta.json=false
|
||||
mapper : org.hibernate.type.format.jackson.JacksonJsonFormatMapper
|
||||
stored : {"n": 1, "list": ["a", "b"]} ; {"at": 1793355330.000000000, "label": "boot"}
|
||||
read back : {n=1, list=[a, b]} ; Stamp[label=boot, at=2026-10-30T10:15:30Z]
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
== what the 2018-2022 recipes rely on, checked against Hibernate 7.4.14.Final
|
||||
org.hibernate.annotations.TypeDef present? false
|
||||
org.hibernate.annotations.TypeDefs present? false
|
||||
org.hibernate.annotations.Type attributes: [parameters, value]
|
||||
|
||||
== jars on the test classpath that mention jackson or hypersistence
|
||||
hypersistence-tsid-2.1.4.jar
|
||||
hypersistence-utils-hibernate-73-3.16.0.jar
|
||||
jackson-annotations-2.21.jar
|
||||
jackson-core-3.1.7.jar
|
||||
jackson-databind-3.1.7.jar
|
||||
spring-boot-jackson-4.1.2.jar
|
||||
spring-boot-starter-jackson-4.1.2.jar
|
||||
|
||||
== FormatMapper Hibernate picked with hypersistence-utils on the classpath
|
||||
getJsonFormatMapper() -> org.hibernate.type.format.jackson.Jackson3JsonFormatMapper
|
||||
|
||||
== @Type(JsonType.class) @Column(columnDefinition = "jsonb") on Hibernate 7.4.14
|
||||
stored: jsonb {"k": "v", "n": 1}
|
||||
in-place Map.put -> 1 UPDATE; stored: {"k": "changed", "n": 1}
|
||||
|
||||
== the built-in mapping on the same classpath: Product (Map, List, record, POJO) round trip
|
||||
round trip OK: Dimensions[widthMm=1, heightMm=2, depthMm=3] m
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
== what the 2018-2022 recipes rely on, checked against Hibernate 7.4.14.Final
|
||||
org.hibernate.annotations.TypeDef present? false
|
||||
org.hibernate.annotations.TypeDefs present? false
|
||||
org.hibernate.annotations.Type attributes: [parameters, value]
|
||||
|
||||
== jars on the test classpath that mention jackson or hypersistence
|
||||
hypersistence-tsid-2.1.4.jar
|
||||
hypersistence-utils-hibernate-73-3.16.0.jar
|
||||
jackson-annotations-2.21.jar
|
||||
jackson-core-2.21.7.jar
|
||||
jackson-core-3.1.7.jar
|
||||
jackson-databind-2.22.4.jar
|
||||
jackson-databind-3.1.7.jar
|
||||
spring-boot-jackson-4.1.2.jar
|
||||
spring-boot-starter-jackson-4.1.2.jar
|
||||
|
||||
== FormatMapper Hibernate picked with hypersistence-utils on the classpath
|
||||
getJsonFormatMapper() -> org.hibernate.type.format.jackson.JacksonJsonFormatMapper
|
||||
|
||||
== @Type(JsonType.class) @Column(columnDefinition = "jsonb") on Hibernate 7.4.14
|
||||
stored: jsonb {"k": "v", "n": 1}
|
||||
in-place Map.put -> 1 UPDATE; stored: {"k": "changed", "n": 1}
|
||||
|
||||
== the built-in mapping on the same classpath: Product (Map, List, record, POJO) round trip
|
||||
round trip OK: Dimensions[widthMm=1, heightMm=2, depthMm=3] m
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
== default Boot wiring
|
||||
JsonMapper bean from Boot : tools.jackson.databind.json.JsonMapper
|
||||
Hibernate FormatMapper : org.hibernate.type.format.jackson.Jackson3JsonFormatMapper
|
||||
same JsonMapper instance? : false
|
||||
native @> (findByAttributesContaining {"color":"black"}): [Floor lamp, Office chair]
|
||||
HQL json_value (findByColor black) : [Floor lamp, Office chair]
|
||||
derived query findByDimensions(new Dimensions(300,600,300)): [Floor lamp]
|
||||
@Modifying json_set repaint(1, "green") -> 1 row; color now green
|
||||
|
||||
== profile shared-mapper: HibernatePropertiesCustomizer hands Boot's JsonMapper to Hibernate
|
||||
same JsonMapper instance? : true
|
||||
@@ -0,0 +1,24 @@
|
||||
# hibernate-core-7.4.14.Final.jar
|
||||
|
||||
$ javap -p -c -cp hibernate-core-7.4.14.Final.jar org.hibernate.type.descriptor.java.spi.FormatMapperBasedJavaType (deepCopy only)
|
||||
public T deepCopy(T);
|
||||
Code:
|
||||
0: aload_1
|
||||
1: ifnonnull 8
|
||||
4: aconst_null
|
||||
5: goto 17
|
||||
8: aload_0
|
||||
9: aload_0
|
||||
10: aload_1
|
||||
11: invokevirtual #82 // Method toString:(Ljava/lang/Object;)Ljava/lang/String;
|
||||
14: invokevirtual #85 // Method fromString:(Ljava/lang/CharSequence;)Ljava/lang/Object;
|
||||
17: areturn
|
||||
|
||||
|
||||
$ javap -p -c -cp hibernate-core-7.4.14.Final.jar org.hibernate.type.descriptor.java.AbstractJavaType (areEqual only)
|
||||
public boolean areEqual(T, T);
|
||||
Code:
|
||||
0: aload_1
|
||||
1: aload_2
|
||||
2: invokestatic #51 // Method java/util/Objects.equals:(Ljava/lang/Object;Ljava/lang/Object;)Z
|
||||
5: ireturn
|
||||
@@ -0,0 +1,19 @@
|
||||
# Maven Central facts, read 2026-10-30
|
||||
|
||||
# maven-metadata.xml: <release> and <lastUpdated> (yyyyMMddHHmmss, UTC)
|
||||
com.vladmihalcea:hibernate-types-60 : release=2.21.1 lastUpdated=20221224094656
|
||||
com.vladmihalcea:hibernate-types-55 : release=2.21.1 lastUpdated=20221224094655
|
||||
com.vladmihalcea:hibernate-types-5 : release=2.21.1 lastUpdated=20221224094653
|
||||
io.hypersistence:hypersistence-utils-hibernate-71: release=3.16.0 lastUpdated=20260920123250
|
||||
io.hypersistence:hypersistence-utils-hibernate-73: release=3.16.0 lastUpdated=20260920123251
|
||||
|
||||
# jar Last-Modified on Central (upload time)
|
||||
hibernate-core-7.4.14.Final.jar: Sun, 25 Oct 2026 00:27:17 GMT
|
||||
spring-boot-4.1.2.jar: Thu, 22 Oct 2026 12:04:04 GMT
|
||||
hypersistence-utils-hibernate-73-3.16.0.jar: Sun, 20 Sep 2026 12:31:01 GMT
|
||||
|
||||
# hibernate.version that hypersistence-utils-hibernate-73:3.16.0 was built against (its pom)
|
||||
<hibernate.version>7.4.8.Final</hibernate.version>
|
||||
|
||||
# newest Jackson 3 databind on Central
|
||||
<release>3.2.4</release>
|
||||
Reference in New Issue
Block a user