如何在H2中解决JSON列

ear*_*des 23 java spring hibernate h2 mysql-5.7

我在应用程序MySQL 5.7中使用,我有JSON列.当我尝试运行时,我的集成测试不起作用,因为H2数据库无法创建表.这是错误:

2016-09-21 16:35:29.729 ERROR 10981 --- [           main] org.hibernate.tool.hbm2ddl.SchemaExport  : HHH000389: Unsuccessful: create table payment_transaction (id bigint generated by default as identity, creation_date timestamp not null, payload json, period integer, public_id varchar(255) not null, state varchar(255) not null, subscription_id_zuora varchar(255), type varchar(255) not null, user_id bigint not null, primary key (id))
2016-09-21 16:35:29.730 ERROR 10981 --- [           main] org.hibernate.tool.hbm2ddl.SchemaExport  : Unknown data type: "JSON"; SQL statement:
Run Code Online (Sandbox Code Playgroud)

这是实体类.

@Table(name = "payment_transaction")
public class PaymentTransaction extends DomainObject implements Serializable {

    @Convert(converter = JpaPayloadConverter.class)
    @Column(name = "payload", insertable = true, updatable = true, nullable = true, columnDefinition = "json")
    private Payload payload;

    public Payload getPayload() {
        return payload;
    }

    public void setPayload(Payload payload) {
        this.payload = payload;
    }
}
Run Code Online (Sandbox Code Playgroud)

而子类:

public class Payload implements Serializable {

    private Long userId;
    private SubscriptionType type;
    private String paymentId;
    private List<String> ratePlanId;
    private Integer period;

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public SubscriptionType getType() {
        return type;
    }

    public void setType(SubscriptionType type) {
        this.type = type;
    }

    public String getPaymentId() {
        return paymentId;
    }

    public void setPaymentId(String paymentId) {
        this.paymentId = paymentId;
    }

    public List<String> getRatePlanId() {
        return ratePlanId;
    }

    public void setRatePlanId(List<String> ratePlanId) {
        this.ratePlanId = ratePlanId;
    }

    public Integer getPeriod() {
        return period;
    }

    public void setPeriod(Integer period) {
        this.period = period;
    }

}
Run Code Online (Sandbox Code Playgroud)

并且此转换器用于插入数据库:

public class JpaPayloadConverter implements AttributeConverter<Payload, String> {

    // ObjectMapper is thread safe
    private final static ObjectMapper objectMapper = new ObjectMapper();

    private Logger log = LoggerFactory.getLogger(getClass());

    @Override
    public String convertToDatabaseColumn(Payload attribute) {
        String jsonString = "";
        try {
            log.debug("Start convertToDatabaseColumn");

            // convert list of POJO to json
            jsonString = objectMapper.writeValueAsString(attribute);
            log.debug("convertToDatabaseColumn" + jsonString);

        } catch (JsonProcessingException ex) {
            log.error(ex.getMessage());
        }
        return jsonString;
    }

    @Override
    public Payload convertToEntityAttribute(String dbData) {

        Payload payload = new Payload();
        try {
            log.debug("Start convertToEntityAttribute");

            // convert json to list of POJO
            payload = objectMapper.readValue(dbData, Payload.class);
            log.debug("JsonDocumentsConverter.convertToDatabaseColumn" + payload);

        } catch (IOException ex) {
            log.error(ex.getMessage());
        }
        return payload;

    }
}
Run Code Online (Sandbox Code Playgroud)

n00*_*dle 23

我刚刚遇到了使用JSONB列类型的问题 - 类型的二进制版本JSON,它没有映射到TEXT.

为了将来参考,您可以使用CREATE DOMAIN如下定义H2中的自定义类型:

CREATE domain IF NOT EXISTS jsonb AS other;
Run Code Online (Sandbox Code Playgroud)

这似乎对我有用,并允许我成功测试我的代码对实体.

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


jch*_*brt 12

一种解决方法实际上是在 H2 中为 jsonb 类型创建自定义列数据类型,并将查询放在数据源 url 中,如下所示:

spring.datasource.url=jdbc:h2:mem:testdb;INIT=create domain if not exists jsonb as text;MODE=PostgreSQL"
Run Code Online (Sandbox Code Playgroud)

现在特别是对于测试和集成测试,最好通过TestContainers使用与您的应用程序相同的数据库

  • 支持 TestContainers 参考。如果这不是我们在生产中使用的,我们应该停止使用内存数据库进行集成测试。 (2认同)
  • `MODE=PostgreSQL` 有什么作用? (2认同)

too*_*ger 8

H2没有JSON数据类型.

JSON本质上只是一个非常长的字符串,因此您可以使用大多数数据库中可用的CLOB.

只有当您需要对其进行操作的SQL函数时,才需要行级的JSON类型,并且只有当数据库坚持其JSON函数在JSON类型而不是CLOB上运行时才需要.

  • 更新:从 1.4.200 开始,H2 确实有原生 JSON 类型:http://www.h2database.com/html/changelog.html (4认同)
  • @Type(type = "jsonb") @Column(columnDefinition = "jsonb") h2 数据库不支持此功能 (2认同)

zjo*_*jor 8

这就是我在 Spring 上下文中解决它的方法:

  1. 创建 /src/test/resources/init.sql
CREATE TYPE "JSONB" AS json;
Run Code Online (Sandbox Code Playgroud)
  1. 配置H2数据源如下 /src/test/resources/application-test.yml
spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;INIT=RUNSCRIPT FROM 'classpath:init.sql'
    username: sa
    password: sa
Run Code Online (Sandbox Code Playgroud)

来源文章


Vla*_*cea 8

香槟时间!

从 version 开始2.11.0,Hibernate Types 项目现在提供了一个JsonType可以自动神奇地工作的泛型:

  • 甲骨文,
  • SQL 服务器,
  • PostgreSQL,
  • MySQL,和
  • H2。

甲骨文

@Entity(name = "Book")
@Table(name = "book")
@TypeDef(name = "json", typeClass = JsonType.class)
public class Book {

    @Id
    @GeneratedValue
    private Long id;

    @NaturalId
    @Column(length = 15)
    private String isbn;

    @Type(type = "json")
    @Column(columnDefinition = "VARCHAR2(1000) CONSTRAINT IS_VALID_JSON CHECK (properties IS JSON)")
    private Map<String, String> properties = new HashMap<>();
}
Run Code Online (Sandbox Code Playgroud)

数据库服务器

@Entity(name = "Book")
@Table(name = "book")
@TypeDef(name = "json", typeClass = JsonType.class)
public class Book {

    @Id
    @GeneratedValue
    private Long id;

    @NaturalId
    @Column(length = 15)
    private String isbn;

    @Type(type = "json")
    @Column(columnDefinition = "NVARCHAR(1000) CHECK(ISJSON(properties) = 1)")
    private Map<String, String> properties = new HashMap<>();
}
Run Code Online (Sandbox Code Playgroud)

PostgreSQL

@Entity(name = "Book")
@Table(name = "book")
@TypeDef(name = "json", typeClass = JsonType.class)
public class Book {

    @Id
    @GeneratedValue
    private Long id;

    @NaturalId
    @Column(length = 15)
    private String isbn;

    @Type(type = "json")
    @Column(columnDefinition = "jsonb")
    private Map<String, String> properties = new HashMap<>();
}
Run Code Online (Sandbox Code Playgroud)

MySQL

@Entity(name = "Book")
@Table(name = "book")
@TypeDef(name = "json", typeClass = JsonType.class)
public class Book {

    @Id
    @GeneratedValue
    private Long id;

    @NaturalId
    @Column(length = 15)
    private String isbn;

    @Type(type = "json")
    @Column(columnDefinition = "json")
    private Map<String, String> properties = new HashMap<>();
}
Run Code Online (Sandbox Code Playgroud)

H2

@Entity(name = "Book")
@Table(name = "book")
@TypeDef(name = "json", typeClass = JsonType.class)
public class Book {

    @Id
    @GeneratedValue
    private Long id;

    @NaturalId
    @Column(length = 15)
    private String isbn;

    @Type(type = "json")
    @Column(columnDefinition = "json")
    private Map<String, String> properties = new HashMap<>();
}
Run Code Online (Sandbox Code Playgroud)

奇迹般有效!

因此,没有更多的黑客和变通方法,JsonType无论您使用什么数据库都可以使用。

如果您想看到它的实际效果,请查看GitHub 上的这个测试文件夹

  • 如果我们只在测试环境中使用 H2,是否仍然可以使用这个解决方案?我在生产中使用 Postgres (`columnDefinition = "jsonb`) 并在一些测试用例中使用 H2 (`columnDefinition = "json"`)。 (3认同)
  • 准备好感到惊讶吧。去尝试一下。 (2认同)

mad*_*adz 6

就我而言,我们正在处理PostgreSQL jsonb生产和H2测试中的类型。

我无法测试@ n00dle的解决方案,因为显然春不支持执行SQL脚本之前Hibernateddl-auto=update对于我们的测试,所以我用另一种方式来解决这个问题。

这是它的要点

总体思路是创建两个package-info文件。一个用于生产,另一个用于测试和注册不同的类型(JsonBinaryType.class用于生产TextType.class用于测试),以不同的方式处理它们的PostgreSQLH2


whi*_*rue 6

我的问题是 JSONB,因为 H2 不支持它,如前所述。

还有一个问题是,当你插入一个 json 时,H2 将它转换成一个json 对象字符串,这使得 jackson 序列化失败。例如: "{\"key\": 3}" 而不是 {"key": 3} 。一种解决方案是在插入 json 时使用 FORMAT JSON,但是例如,如果您使用的是 flyway,则您需要有重复的插入文件。

受到@madz回答的启发,我遇到了这个解决方案:

创建自定义 JsonbType(在生产中 - 例如 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;
}
Run Code Online (Sandbox Code Playgroud)

创建自定义 JsonbType(在测试中 - 例如 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";
  }
}
Run Code Online (Sandbox Code Playgroud)

仅在测试 (h2) 上创建从 JSONB 到 JSON 的别名类型:

-- only on H2 database
CREATE TYPE "JSONB" AS TEXT;
Run Code Online (Sandbox Code Playgroud)

注意:我正在使用 flyway 这使得它很容易做到,但您可以遵循@jchrbrt建议

最后,您在实体模型上声明类型,如下所示:

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; 
  }
}
Run Code Online (Sandbox Code Playgroud)

就是这样。我希望它可以帮助某人。

  • 我非常喜欢这个解决方案,我很高兴你在两个小时前想出了这个解决方案。根据此线程上的活动,似乎每个人都在 2020 年 3 月在 Postgres 中编写有关 jsonb 类型的单元测试。 (2认同)