How can solve JSON column in H2

前端 未结 10 1841
隐瞒了意图╮
隐瞒了意图╮ 2020-12-24 13:39

I use in application MySQL 5.7 and I have JSON columns. When I try running my integration tests don\'t work because the H2 database can\'t create the table. This is the erro

相关标签:
10条回答
  • 2020-12-24 13:51

    I am in the same situation as @madz, where we use Postgres in production and H2 for unit tests. In my case i found a bit more simple solution, i think. We use Liquibase for database migrations, so here i made a conditional migration only to be run on H2, where i change the column type to H2's "other" type.

    With the other type, H2 just stores it in the database and doesn't think twice about how the data is formatted etc. This does require however that you are not doing anything with the JSON directly in the database, and only in your application.

    My migration looks like this:

      # Use other type in H2, as jsonb is not supported
      - changeSet:
          id: 42
          author: Elias Jørgensen
          dbms: h2
          changes:
            - modifyDataType:
                tableName: myTableName
                columnName: config
                newDataType: other
    

    Along with this, i added the following to my test datasource:

    INIT=create domain if not exists jsonb as text;
    
    0 讨论(0)
  • 2020-12-24 13:51

    Example with Kotlin + Spring + Hibernate + Postgres + jsonb column

    Create the entity:

    @Entity
    @TypeDef(name = "jsonb", typeClass = JsonBinaryType::class)
    class MyEntity(
        @Type(type = "jsonb")
        @Column(columnDefinition = "jsonb")
        val myConfig: String,
    
        @Id
        @GeneratedValue
        val id: Long = 0,
    )
    

    JsonBinaryType.class comes from https://github.com/vladmihalcea/hibernate-types

    <dependency>
        <groupId>com.vladmihalcea</groupId>
        <artifactId>hibernate-types-52</artifactId>
        <version>2.9.13</version>
    </dependency>
    

    Configure your H2 database in spring profile. The key line is this: INIT=create domain if not exists jsonb as other

    spring:
        profiles: h2
    
        datasource:
            driver-class-name: org.h2.Driver
            url: jdbc:h2:mem:testdb;INIT=create domain if not exists jsonb as other;MODE=PostgreSQL;DB_CLOSE_DELAY=-1
            username: sa
            password: sa
    
    spring.jpa.hibernate.ddl-auto: create
    

    Write the test:

    // Postgres test
    @SpringBootTest
    class ExampleJsonbPostgres(@Autowired private val myEntityRepository: MyEntityRepository) {
        @Test
        fun `verify we can write and read jsonb`() {
            val r = myEntityRepository.save(MyEntity("""{"hello": "world"}"""))
            assertThat(myEntityRepository.findById(r.id).get().config).isEqualTo("""{"hello": "world"}""")
        }
    }
    
    // H2 test
    @ActiveProfiles("h2")
    @SpringBootTest
    class ExampleJsonbH2(@Autowired private val myEntityRepository: MyEntityRepository) {
        @Test
        fun `verify we can write and read jsonb`() {
            val r = myEntityRepository.save(MyEntity("""{"hello": "world"}"""))
            assertThat(myEntityRepository.findById(r.id).get().config).isEqualTo("""{"hello": "world"}""")
        }
    }
    

    Alternatively you can try to define custom type per database in hibernate XML as described here: https://stackoverflow.com/a/59753980/10714479

    0 讨论(0)
  • 2020-12-24 13:51

    H2 does not have the JSON data type.

    In MySQL the JSON type is just an alias for the LONGTEXT data type so the actual data type for the column will be LONGTEXT.

    0 讨论(0)
  • 2020-12-24 13:53

    I have solved the problem using TEXT type in H2. One must create a separate database script to create schema in H2 for tests and replace the JSON type by TEXT.

    It is still a problem since if you use Json function in queries, you will not be able to test those while with H2.

    0 讨论(0)
  • 2020-12-24 13:54

    I just came across this problem working with the JSONB column type - the binary version of the JSON type, which doesn't map to TEXT.

    For future reference, you can define a custom type in H2 using CREATE DOMAIN, as follows:

    CREATE domain IF NOT EXISTS jsonb AS other;
    

    This seemed to work for me, and allowed me to successfully test my code against the entity.

    Source: https://objectpartners.com/2015/05/26/grails-postgresql-9-4-and-jsonb/

    0 讨论(0)
  • 2020-12-24 13:55

    My problem was with JSONB since H2 does not support it as was already mentioned.

    One more problem is that when you insert a json, H2 transforms it into a json object string which makes jackson serialization fail. ex: "{\"key\": 3}" instead of {"key": 3} . One solution is to use FORMAT JSON when inserting the json, but then you need to have duplicate insert files if you are using flyway, for example.

    Inspired by the @madz answer I came across with this solution:

    Create a custom JsonbType (on production - e.g. main/java/com/app/types/JsonbType.java)

    import com.vladmihalcea.hibernate.type.json.JsonBinaryType;
    
    public class JsonbType extends JsonBinaryType {
      private static final long serialVersionUID = 1L;
    }
    

    Create a custom JsonbType (on tests - e.g. test/java/com/app/types/JsonbType.java)

    import com.vladmihalcea.hibernate.type.json.JsonStringType;
    
    public class JsonbType extends JsonStringType {
      private static final long serialVersionUID = 1L;
      @Override
      public String getName() {
          return "jsonb";
      }
    }
    

    Create an alias type from JSONB to JSON only on tests (h2):

    -- only on H2 database
    CREATE TYPE "JSONB" AS TEXT;
    

    note: I'm using flyway which make it easy to do but you can follow @jchrbrt suggestion

    Finally you declare the type on your entity model, as follows:

    import com.app.types.JsonbType;
    
    @TypeDef(name = "jsonb", typeClass = JsonbType.class)
    @Entity(name = "Translation")
    @Table(name = "Translation")
    @Data
    public class Translation {
      @Type(type = "jsonb")
      @Column(name="translations")
         private MySerializableCustomType translations; 
      }
    }
    

    That's it. I hope it helps someone.

    0 讨论(0)
提交回复
热议问题