PostgreSQL Hibernate 6 JSON Example
1. Introduction
Hibernate 6 enhances the Object-Relational Mapping(ORM) framework by supporting JSON Data types. In this example, I will create a PostgreSQL Hibernate 6 JSON data example that uses Hibernate 6 @JdbcTypeCode(SqlTypes.JSON) to map PostgreSQL JSONB and JSON data types. This example also queries PostgreSQL JSON
and JSONB
data via native SQL, Hibernate Query Language (HQL), Criteria API, and Java Persistence Query Language (JPQL) with PostgreSQL JSON functions. It also includes a DemoRestController
that provides save, update, and search JSON Rest APIs.
2. Setup PostgreSQL Hibernate 6 JSON Data Example
In this step, I will create a gradle project via https://start.spring.io/ along with spring-data-jpa, spring-boot-starter-validation, spring-web, lombok, postgresql, and Junit5 libraries.
build.gradle
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | plugins { id 'java' id 'org.springframework.boot' version '3.4.2' id 'io.spring.dependency-management' version '1.1.7' } group = 'com.zheng.demo.jsonb' version = '0.0.1-SNAPSHOT' java { toolchain { languageVersion = JavaLanguageVersion.of(17) } } configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' compileOnly 'org.projectlombok:lombok' runtimeOnly 'org.postgresql:postgresql' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } tasks.named('test') { useJUnitPlatform() } |
2.1 Generated Spring Boot Application
No modification is needed for the generated JsonbPostgreDemoApplication.java
.
JsonbPostgreDemoApplication.java
01 02 03 04 05 06 07 08 09 10 11 12 13 | package com.zheng.demo.jsonb; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class JsonbPostgreDemoApplication { public static void main(String[] args) { SpringApplication.run(JsonbPostgreDemoApplication. class , args); } } |
2.2 Generated Spring Boot Application Test
No modification is needed for the generated JsonbPostgreDemoApplicationTest.java
.
JsonbPostgreDemoApplicationTest.java
01 02 03 04 05 06 07 08 09 10 11 12 13 | package com.zheng.demo.jsonb; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class JsonbPostgreDemoApplicationTests { @Test void contextLoads() { } } |
2.3 Generated application.properties
Modify the generated application.properties
to enable the hibernate logging so we can see the database DDL and queries.
application.properties
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 | spring.application.name=postgresJsonDemoJackson # DataSource configuration spring.datasource.url=jdbc:postgresql://localhost:5432/postgres spring.datasource.username=postgres spring.datasource.password= spring.datasource.driver-class-name=org.postgresql.Driver # Hibernate Dialect for PostgreSQL spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect # Hibernate Settings spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true logging.level.org.hibernate.orm.jdbc.bind=trace logging.level.org.hibernate.type.descriptor.sql.BasicBinder=trace # PostgreSQL connection pool settings (optional) spring.datasource.hikari.maximum-pool-size=10 |
- Line 15: configure the property to format the SQL so it’s easy to read.
- Line 16: configure the property to print out the SQL binding parameters and their values.
- Line 17: configure the property to print out the SQL generated by the Criteria API.
3. Entity
In this step, I will create a DemoEntityWithJsonFields.java
that has 4 fields:
id
for the primary key.jsonbField
field maps to “jsonb_Field
” column with “jsonb
” database type.jsonData
field maps to “json_data
” column with “json
” database type.testStatus
field maps to “status_id
” column with “int
” database type.
DemoEntityWithJsonFields.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | package com.zheng.demo.jsonb.entity; import java.util.Map; import java.util.UUID; import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.type.SqlTypes; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.Data; @Entity @Table (name = "Demo_Entity_With_Json_Fields" ) @Data public class DemoEntityWithJsonFields { @Id @GeneratedValue (strategy = GenerationType.UUID) private UUID id; @JdbcTypeCode (SqlTypes.JSON) @Column (name = "jsonb_Field" , columnDefinition = "jsonb" ) private Map jsonbField; @JdbcTypeCode (SqlTypes.JSON) @Column (name = "json_data" , columnDefinition = "json" ) private Map jsonData; //the TestStatus order must be always ACTIVE, DELETE @Enumerated (EnumType.ORDINAL) @Column (name = "status_id" ) private TestStatus testStatus; } |
- Line 29: defines
jsonbField
for “jsonb
” type. - Line 33: defines
jsonData
for “json
” type. - Line 28, 32: annonates
@JdbcTypeCode(SqlTypes.JSON)
for bothjsonbField
andjson
Data. This annotation simplifies the mapping of JSON columns. By setting the SQL type toSqlTypes.JSON
, Hibernate handles serialization and deserialization ofJSON
data. - Line 30, 34: both
JSON
andJSONB
columns map to theMap
object. Hibernate 6 will do the serialization and deserialization.
Figure 1 shows the table demo_entity_with_json_fields
details. The Json
and Jsonb
fields are pointed by a red arrow.
3.1 TestStatus Enum
In this step, I will create a TestStatus.java
that has Enum values: ACTIVE
and INACTIVE
.
TestStatus.java
1 2 3 4 5 6 | package com.zheng.demo.jsonb.entity; public enum TestStatus { //the order must be always this order ACTIVE, INACTIVE, ACTIVE, INACTIVE; } |
4. Repository
4.1 DemoEntityWithJsonFieldRepo
In this step, I will create a DemoEntityWithJsonFieldRepo.java
interface that extends from JpaRepostory
with the additional query JSON and JSONB data via native query, HQL, and JPQL queries.
findByTestStatus
– finds the entities based on the “testStatus
” field with spring data JPA naming convention.searchActiveJsonbViaJPQL
– since we cannot use the JPA naming convention for the JSONB field, therefore we create a JPQL Query along with PostgreSQL functionjsonb_extract_path_text
to search active entities with JSONB key and value.searchActiveJsonbViaNativeQuery
– searches the active entities with the JSON’s key value via the native query. Note: the->>
operator is supported by PostgreSQL to retrieve the JSON element’s value asText
.searchJsonbViaJPQL
– this is similar tosearchActiveJsonbViaJPQL
, but without filtering status.searchJsonbViaNativeQuery
– this is similar tosearchActiveJsonbViaNativeQuery
, but without filtering status.searchJsonViaNativeQuery
– this is similar tosearchJsonbViaNativeQuery
.searchActiveJsonbViaHQL
– uses HQL andjsonb_extract_path_text
to search JSONB element key and value.searchActiveJsonViaJPQL
– creates a JPQL Query along with PostgreSQL functionjson_extract_path_text
to search active entities with JSON key and value.
DemoEntityWithJsonFieldRepo.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | package com.zheng.demo.jsonb.repo; import java.util.List; import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import com.zheng.demo.jsonb.entity.DemoEntityWithJsonFields; import com.zheng.demo.jsonb.entity.TestStatus; public interface DemoEntityWithJsonFieldRepo extends JpaRepository<DemoEntityWithJsonFields, UUID> { List<DemoEntityWithJsonFields> findByTestStatus(TestStatus status); @Query (value = "from DemoEntityWithJsonFields e where jsonb_extract_path_text(e.jsonbField, :key) = :value and testStatus = com.zheng.demo.jsonb.entity.TestStatus.ACTIVE" ) List<DemoEntityWithJsonFields> searchActiveJsonbViaHQL( @Param ( "key" ) String key, @Param ( "value" ) String value); @Query (value = "select e from DemoEntityWithJsonFields e where jsonb_extract_path_text(e.jsonbField, :key) = :value and testStatus = com.zheng.demo.jsonb.entity.TestStatus.ACTIVE" ) List<DemoEntityWithJsonFields> searchActiveJsonbViaJPQL( @Param ( "key" ) String key, @Param ( "value" ) String value); @Query (nativeQuery = true , value = "select * from public.demo_entity_with_json_fields where jsonb_field->>:key = :value and status_id = 0" ) List<DemoEntityWithJsonFields> searchActiveJsonbViaNativeQuery( @Param ( "key" ) String key, @Param ( "value" ) String value); @Query (value = "select e from DemoEntityWithJsonFields e where jsonb_extract_path_text(e.jsonbField, :key) = :value" ) List<DemoEntityWithJsonFields> searchJsonbViaJPQL( @Param ( "key" ) String key, @Param ( "value" ) String value); @Query (nativeQuery = true , value = "select * from public.Demo_Entity_With_Json_Fields where jsonb_field->>:key = :value" ) List<DemoEntityWithJsonFields> searchJsonbViaNativeQuery( @Param ( "key" ) String key, @Param ( "value" ) String value); @Query (nativeQuery = true , value = "select * from public.Demo_Entity_With_Json_Fields where json_data->>:key = :value" ) List<DemoEntityWithJsonFields> searchJsonViaNativeQuery( @Param ( "key" ) String key, @Param ( "value" ) String value); @Query (value = "select e from DemoEntityWithJsonFields e where json_extract_path_text(e.jsonData, :key) = :value and testStatus = com.zheng.demo.jsonb.entity.TestStatus.ACTIVE" ) List<DemoEntityWithJsonFields> searchActiveJsonViaJPQL( @Param ( "key" ) String key, @Param ( "value" ) String value); } |
- Line 16:
searchActiveJsonbViaHQL
uses HQL with functionjsonb_extract_path_text
. It includes an Enum status check. Thejsonb_extract_path_text(from_json json, VARIADIC path_elements text[])
is the PostgreSQL function to return JSON value pointed to by path_elements as text. - Line 19:
searchActiveJsonbViaJPQL
uses JPQL with functionjsonb_extract_path_text
. It includes an Enum status check. - Line 23:
searchActiveJsonbViaNativeQuery
uses native query with PostgreSQL JSON->>
operator that retrieves a JSON object field by key and returns the result as text (string
). - Line 26:
searchJsonbViaJPQL
is similar tosearchActiveJsonbViaJPQL
but without status filtering. - Line 29:
searchJsonbViaNativeQuery
is similar tosearchActiveJsonbViaNativeQuery
without status filtering. - Line 32:
searchJsonViaNativeQuery
uses native query with the->>
operator that retrieves a JSON object field by key and returns the result as text (string
). - Line 35:
searchActiveJsonViaJPQL
similar tosearchActiveJsonbViaJPQL
but via thejson_extract_path_text
function.
4.2 Dynamic Query Repository
In this step, I will create a JsonbDynamicQueryRepo.java
class that annotates with @Repostory
annotation. It creates a dynamic query via the Criteria API to search based on JSON
and status
.
JsonbDynamicQueryRepo.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | package com.zheng.demo.jsonb.repo; import java.util.List; import org.springframework.stereotype.Repository; import com.zheng.demo.jsonb.entity.DemoEntityWithJsonFields; import com.zheng.demo.jsonb.entity.TestStatus; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.Expression; import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; @Repository public class JsonbDynamicQueryRepo { @PersistenceContext private EntityManager em; public List<DemoEntityWithJsonFields> searchByJsonbKeyAndStatus( final String jsonKey, final String jsonValue, final TestStatus status) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<DemoEntityWithJsonFields> query = cb.createQuery(DemoEntityWithJsonFields. class ); Root<DemoEntityWithJsonFields> demoEntityRoot = query.from(DemoEntityWithJsonFields. class ); Expression<String> jsonPath = cb.function( "jsonb_extract_path_text" , String. class , demoEntityRoot.get( "jsonbField" ), cb.literal(jsonKey)); Predicate byJson = cb.equal(jsonPath, jsonValue); if (status != null ) { Predicate byStatus = cb.equal(demoEntityRoot.get( "testStatus" ), status); query.select(demoEntityRoot).where(cb.and(byJson, byStatus)); } else { query.select(demoEntityRoot).where(byJson); } return em.createQuery(query).getResultList(); } public List<DemoEntityWithJsonFields> searchByJsonKeyAndStatus( final String jsonKey, final String jsonValue, final TestStatus status) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<DemoEntityWithJsonFields> query = cb.createQuery(DemoEntityWithJsonFields. class ); Root<DemoEntityWithJsonFields> demoEntityRoot = query.from(DemoEntityWithJsonFields. class ); Expression<String> jsonPath = cb.function( "json_extract_path_text" , String. class , demoEntityRoot.get( "jsonData" ), cb.literal(jsonKey)); Predicate byJson = cb.equal(jsonPath, jsonValue); if (status != null ) { Predicate byStatus = cb.equal(demoEntityRoot.get( "testStatus" ), status); query.select(demoEntityRoot).where(cb.and(byJson, byStatus)); } else { query.select(demoEntityRoot).where(byJson); } return em.createQuery(query).getResultList(); } } |
- Line 32, 33: extract the
Json
key field from PostgreSQLjsonb_extract_path_text
function. - Line 39: dynamic query with 2 predicates.
- Line 41: dynamic query with only 1 predicate.
- Line 56, 57: extract the
Json
key field from PostgreSQLjson_extract_path_text
function.
4.3 Repository Test
In this step, I will create a DemoEntityWithJsonFieldRepoTest.java
test class to verify searching methods.
DemoEntityWithJsonFieldRepoTest.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | package com.zheng.demo.jsonb.repo; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.transaction.annotation.Transactional; import com.zheng.demo.jsonb.entity.DemoEntityWithJsonFields; @SpringBootTest @Transactional class DemoEntityWithJsonFieldRepoTest extends BaseTest { @Autowired private DemoEntityWithJsonFieldRepo testClass; @Test void test_searchJsonbViaNativeQuery() { List<DemoEntityWithJsonFields> found = testClass.searchJsonbViaNativeQuery(SEARCH_KEY, SEARCH_VALUE); assertEquals( 1 , found.size()); } @Test void test_searchJsonbViaJPQL() { List<DemoEntityWithJsonFields> found = testClass.searchJsonbViaJPQL(SEARCH_KEY, SEARCH_VALUE); assertEquals( 1 , found.size()); } @Test void test_searchActiveJsonbViaNativeQuery() { List<DemoEntityWithJsonFields> found = testClass.searchActiveJsonbViaNativeQuery(SEARCH_KEY, SEARCH_VALUE); assertEquals( 1 , found.size()); } @Test void test_searchActiveJsonbViaJPQL() { List<DemoEntityWithJsonFields> found = testClass.searchActiveJsonbViaJPQL(SEARCH_KEY, SEARCH_VALUE); assertEquals( 1 , found.size()); } @Test void test_searchJsonViaNativeQuery() { List<DemoEntityWithJsonFields> found = testClass.searchJsonViaNativeQuery(SEARCH_KEY, SEARCH_VALUE); assertEquals( 1 , found.size()); } } |
4.4 Dynamic Query Repository Test
In this step, I will create a JsonbDynamicQueryRepoTest.java
class that verifies the dynamic queries.
JsonbDynamicQueryRepoTest.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | package com.zheng.demo.jsonb.repo; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.transaction.annotation.Transactional; import com.zheng.demo.jsonb.entity.DemoEntityWithJsonFields; import com.zheng.demo.jsonb.entity.TestStatus; @SpringBootTest @Transactional class JsonbDynamicQueryRepoTest extends BaseTest { @Autowired private JsonbDynamicQueryRepo testClass; @Test void test_dynamicSearchActiveByJsonbKey() { List<DemoEntityWithJsonFields> found = testClass.searchByJsonbKeyAndStatus(SEARCH_KEY, SEARCH_VALUE, TestStatus.ACTIVE); assertEquals( 1 , found.size()); } @Test void test_dynamicSearchByJsonbKey() { List<DemoEntityWithJsonFields> found = testClass.searchByJsonbKeyAndStatus(SEARCH_KEY, SEARCH_VALUE, null ); assertEquals( 1 , found.size()); } @Test void test_dynamicSearchActiveByJsonKey() { List<DemoEntityWithJsonFields> found = testClass.searchByJsonKeyAndStatus(SEARCH_KEY, SEARCH_VALUE, TestStatus.ACTIVE); assertEquals( 1 , found.size()); } @Test void test_dynamicSearchByJsonKey() { List<DemoEntityWithJsonFields> found = testClass.searchByJsonKeyAndStatus(SEARCH_KEY, SEARCH_VALUE, null ); assertEquals( 1 , found.size()); } } |
4.5 Test Base
In this step, I will create a BaseTest.java
class that creates and deletes testing data before each test.
BaseTest.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | package com.zheng.demo.jsonb.repo; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; import com.zheng.demo.jsonb.entity.DemoEntityWithJsonFields; import com.zheng.demo.jsonb.entity.TestStatus; public abstract class BaseTest { protected static final int TEST_ENTITY_COUNT = 10 ; protected static final String SEARCH_VALUE = "value1" ; protected static final String SEARCH_KEY = "key1" ; @Autowired protected DemoEntityWithJsonFieldRepo entityRepo; public BaseTest() { super (); } private DemoEntityWithJsonFields buildDemoEntity(String key, String value) { DemoEntityWithJsonFields demoEntity = new DemoEntityWithJsonFields(); Map<String, Object> medadata = new HashMap<>(); medadata.put(key, value); demoEntity.setJsonbField(medadata); demoEntity.setJsonData(medadata); demoEntity.setTestStatus(TestStatus.ACTIVE); return demoEntity; } private void saveEntities() { for ( int i = 1 ; i < TEST_ENTITY_COUNT + 1 ; i++) { entityRepo.save(buildDemoEntity( "key" + i, "value" + i)); } } @BeforeEach void setup( @Autowired DemoEntityWithJsonFieldRepo entityRepo) { saveEntities(); } @AfterEach void cleanup() { entityRepo.deleteAll(); } } |
- Line 15: defines the test entity count. You can change the value and run the tests.
4.6 Test Results
We will run the test with 10 entities and capture the test result in Figure 2.
5. DemoRestController
In this step, I will create a DemoRestController.java
that includes the create, update, and search operations for the JSON data.
DemoRestController.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | package com.zheng.demo.jsonb.rest; import java.util.List; import java.util.UUID; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.zheng.demo.jsonb.entity.DemoEntityWithJsonFields; import com.zheng.demo.jsonb.entity.TestStatus; import com.zheng.demo.jsonb.repo.DemoEntityWithJsonFieldRepo; import com.zheng.demo.jsonb.repo.JsonbDynamicQueryRepo; @RequestMapping ( "/demos" ) @RestController public class DemoRestController { private final DemoEntityWithJsonFieldRepo demoRepo; private final JsonbDynamicQueryRepo jsonRepo; public DemoRestController(DemoEntityWithJsonFieldRepo demoRepo, JsonbDynamicQueryRepo jsonRepo) { super (); this .demoRepo = demoRepo; this .jsonRepo = jsonRepo; } @PostMapping ( "/" ) public DemoEntityWithJsonFields create( @RequestBody RequestPayload request) { DemoEntityWithJsonFields newItem = new DemoEntityWithJsonFields(); newItem.setTestStatus(TestStatus.ACTIVE); newItem.setJsonbField(request.getMetadata()); newItem.setJsonData(request.getMetadata()); return demoRepo.save(newItem); } @GetMapping ( "/metadata" ) public List<DemoEntityWithJsonFields> search( @RequestParam (name = "key" , required = true ) String key, @RequestParam (name = "value" , required = false ) String value) { return jsonRepo.searchByJsonbKeyAndStatus(key, value, TestStatus.ACTIVE); } @PutMapping ( "/{id}" ) public DemoEntityWithJsonFields updateDemo( @PathVariable ( "id" ) UUID id, @RequestBody RequestPayload request) { DemoEntityWithJsonFields found = demoRepo.getReferenceById(id); found.setJsonbField(request.getMetadata()); found.setJsonData(request.getMetadata()); return demoRepo.save(found); } } |
- Line 38, 39, 52, 53: the PostgreSQL JSON and JSONB both mapped to the
Map
object. - Line 46: search JSON via the
searchByJsonbKeyAndStatus
method. - Line 40, 54: Spring Data JPA saves entities with JSON or JSONB.
5.1 RequestPayload
In this step, I will create a RequestPayload.java
that holds JSON data via the Map
object.
RequestPayload.java
01 02 03 04 05 06 07 08 09 10 11 12 | package com.zheng.demo.jsonb.rest; import java.util.Map; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor public class RequestPayload { private Map<String, Object> metadata; } |
5.2 Test the Rest APIs
In this step, I will start the spring boot application at default port 8080. Then test the create, search, and update APIs. First, we can see the server log for the table creation.
Server Log – DDL
1 2 3 4 5 6 7 8 | Hibernate: create table demo_entity_with_json_fields ( status_id smallint check (status_id between 0 and 1), id uuid not null, json_data json, jsonb_field jsonb, primary key (id) ) |
- Line 5 for JSON type
- Line 6 for JSONB type
5.2.1 Create API
Test the Post API via Thunderclient and capture the curl command.
POST http://localhost:8080/demos/
01 02 03 04 05 06 07 08 09 10 | curl -X POST \ --header 'Accept: */*' \ --header 'User-Agent: Thunder Client (https://www.thunderclient.com)' \ --header 'Content-Type: application/json' \ --data-raw ' { "metadata":{ "key2": "value2" } }' |
Create Response.
POST API Response Example
01 02 03 04 05 06 07 08 09 10 | { "id": "549aed74-fd23-4ee6-8fda-47783efa3e96", "jsonbField": { "key2": "value2" }, "jsonData": { "key2": "value2" }, "testStatus": "ACTIVE" } |
Hibernate Log for Insert SQL.
Hibernate SQL – Insert
01 02 03 04 05 06 07 08 09 10 11 | Hibernate: insert into demo_entity_with_json_fields (json_data, jsonb_field, status_id, id) values (?, ?, ?, ?) 2025-02-21T12:24:24.581-06:00 TRACE 35980 --- [postgresJsonDemoJackson] [nio-8080-exec-1] org.hibernate.orm.jdbc.bind : binding parameter (1:JSON) <- [{key2=value2}] 2025-02-21T12:24:24.581-06:00 TRACE 35980 --- [postgresJsonDemoJackson] [nio-8080-exec-1] org.hibernate.orm.jdbc.bind : binding parameter (2:JSON) <- [{key2=value2}] 2025-02-21T12:24:24.581-06:00 TRACE 35980 --- [postgresJsonDemoJackson] [nio-8080-exec-1] org.hibernate.orm.jdbc.bind : binding parameter (3:TINYINT) <- [ACTIVE] 2025-02-21T12:24:24.581-06:00 TRACE 35980 --- [postgresJsonDemoJackson] [nio-8080-exec-1] org.hibernate.orm.jdbc.bind : binding parameter (4:UUID) <- [549aed74-fd23-4ee6-8fda-47783efa3e96] |
- Line 8 and 9: both binding parament shows JSON details.
5.2.2 PUT API
Test PUT API via Thunderclient and capture the curl command.
PUT http://localhost:8080/demos/880b08e8-4edf-49cc-aaad-8f38c135a6a6
01 02 03 04 05 06 07 08 09 10 | curl -X PUT \ --header 'Accept: */*' \ --header 'User-Agent: Thunder Client (https://www.thunderclient.com)' \ --header 'Content-Type: application/json' \ --data-raw ' { "metadata":{ "key2": "value2" } }' |
Update Response.
PUT API Response
01 02 03 04 05 06 07 08 09 10 | { "id": "880b08e8-4edf-49cc-aaad-8f38c135a6a6", "jsonbField": { "key2": "value2" }, "jsonData": { "key2": "value2" }, "testStatus": "ACTIVE" } |
5.2.2 Search JSON Key
Test search JSON via Thunderclient and capture the curl command.
GET http://localhost:8080/demos/metadata?key=key1&value=value1
1 2 3 4 | curl -X GET \ --header 'Accept: */*' \ --header 'User-Agent: Thunder Client (https://www.thunderclient.com)' |
Search Response.
GET API By JSON Key/Value
01 02 03 04 05 06 07 08 09 10 11 12 | [ { "id": "9c45301a-3c70-4e50-8314-a6eddc4562b9", "jsonbField": { "key1": "value1" }, "jsonData": { "key1": "value1" }, "testStatus": "ACTIVE" } ] |
Hibernate Query for the select query.
Hibernate Query – Select
01 02 03 04 05 06 07 08 09 10 11 12 13 | Hibernate: select dewjf1_0.id, dewjf1_0.json_data, dewjf1_0.jsonb_field, dewjf1_0.status_id from demo_entity_with_json_fields dewjf1_0 where jsonb_extract_path_text(dewjf1_0.jsonb_field, 'key1')=? and dewjf1_0.status_id=? 2025-02-21T12:28:12.992-06:00 TRACE 35980 --- [postgresJsonDemoJackson] [nio-8080-exec-4] org.hibernate.orm.jdbc.bind : binding parameter (1:VARCHAR) <- [value1] 2025-02-21T12:28:13.007-06:00 TRACE 35980 --- [postgresJsonDemoJackson] [nio-8080-exec-4] org.hibernate.orm.jdbc.bind : binding parameter (2:TINYINT) <- [ACTIVE] |
- Line 10: Query the JSON via PostgreSQL function
jsonb_extract_path_text
.
6. Conclusion
In this example, I created a PostgreSQL Hibernate 6 JSON data example that uses Hibernate 6 @JdbcTypeCode(SqlTypes.JSON) to map PostgreSQL JSONB and JSON data types. The repository package includes several methods to query J
SON data via native SQL, JPQL, HQL, and Criteria APIs with Hibernate JSON support. The Rest controller included three APIs to create, update, and search JSON and JSONB data.
7. Download
This was an example of a gradle project that included PostgreSQL Hibernate 6 JSON data example.
You can download the full source code of this example here: PostgreSQL Hibernate 6 JSON Example