Core Java

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 both jsonbField and json Data. This annotation simplifies the mapping of JSON columns. By setting the SQL type to SqlTypes.JSON, Hibernate handles serialization and deserialization of JSON data.
  • Line 30, 34: both JSON and JSONB columns map to the Map 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.

PostgreSQL Hibernate 6 JSON Example
Figure 1. Table demo_entity_with_json_fields

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 function jsonb_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 as Text.
  • searchJsonbViaJPQL – this is similar to searchActiveJsonbViaJPQL, but without filtering status.
  • searchJsonbViaNativeQuery – this is similar to searchActiveJsonbViaNativeQuery, but without filtering status.
  • searchJsonViaNativeQuery – this is similar to searchJsonbViaNativeQuery.
  • searchActiveJsonbViaHQL – uses HQL and jsonb_extract_path_text to search JSONB element key and value.
  • searchActiveJsonViaJPQL – creates a JPQL Query along with PostgreSQL function json_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 function jsonb_extract_path_text. It includes an Enum status check. The jsonb_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 function jsonb_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 to searchActiveJsonbViaJPQL but without status filtering.
  • Line 29: searchJsonbViaNativeQuery is similar to searchActiveJsonbViaNativeQuery 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 to searchActiveJsonbViaJPQL but via the json_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 PostgreSQL jsonb_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 PostgreSQL json_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.

PostgreSQL Hibernate 6 JSON Data Example
Figure 2. Test Result

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 JSON 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.

Download
You can download the full source code of this example here: PostgreSQL Hibernate 6 JSON Example

Mary Zheng

Mary graduated from the Mechanical Engineering department at ShangHai JiaoTong University. She also holds a Master degree in Computer Science from Webster University. During her studies she has been involved with a large number of projects ranging from programming and software engineering. She worked as a lead Software Engineer where she led and worked with others to design, implement, and monitor the software solution.
Subscribe
Notify of
guest


This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button