如何使用SpringBoot + JPA存储PostgreSQL jsonb?

时间:2018-07-11 03:13:42

标签: postgresql spring-boot spring-data-jpa jsonb

我正在开发一个迁移软件,它将使用REST服务中的未知数据。

我已经考虑过使用MongoDB,但我决定不使用它,而使用PostgreSQL。

阅读this之后,我试图使用Spring JPA在SpringBoot应用程序中实现它,但是我不知道在实体中映射jsonb

尝试过this,但一无所知!

我在这里:

@Repository
@Transactional
public interface DnitRepository extends JpaRepository<Dnit, Long> {

    @Query(value = "insert into dnit(id,data) VALUES (:id,:data)", nativeQuery = true)
    void insertdata( @Param("id")Integer id,@Param("data") String data );

}

和...

@RestController
public class TestController {

    @Autowired
    DnitRepository dnitRepository;  

    @RequestMapping(value = "/dnit", method = RequestMethod.GET)
    public String testBig() {
        dnitRepository.insertdata(2, someJsonDataAsString );
    }

}

和表格:

CREATE TABLE public.dnit
(
    id integer NOT NULL,
    data jsonb,
    CONSTRAINT dnit_pkey PRIMARY KEY (id)
)

我该怎么做?

注意:我不希望/不需要实体。我的JSON将始终为String,但我需要jsonb来查询数据库

5 个答案:

答案 0 :(得分:11)

  

尝试过this,但一无所知!

要与带有Vlad Mihalcea的hibernate-types lib的 Spring Data JPA (休眠)项目中的 jsonb 一起使用,您应该执行以下操作:

1)将此库添加到您的项目中:

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

2)然后在您的实体中使用其类型,例如:

@Data
@NoArgsConstructor
@Entity
@Table(name = "parents")
@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
public class Parent implements Serializable {

    @Id
    @GeneratedValue(strategy = SEQUENCE)
    private Integer id;

    @Column(length = 32, nullable = false)
    private String name;

    @Type(type = "jsonb")
    @Column(columnDefinition = "jsonb")
    private List<Child> children;

    @Type(type = "jsonb")
    @Column(columnDefinition = "jsonb")
    private Bio bio;

    public Parent(String name, List children, Bio bio) {
        this.name = name;
        this.children = children;
        this.bio = bio;
    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Child implements Serializable {
    private String name;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Bio implements Serializable {
    private String text;
}

然后,您将能够使用例如简单的JpaRepository来处理对象:

public interface ParentRepo extends JpaRepository<Parent, Integer> {
}
parentRepo.save(new Parent(
                     "parent1", 
                     asList(new Child("child1"), new Child("child2")), 
                     new Bio("bio1")
                )
);
Parent result = parentRepo.findById(1);
List<Child> children = result.getChildren();
Bio bio = result.getBio();

答案 1 :(得分:3)

通过添加Spring Data JPA来执行一个简单的insert语句,使事情变得过于复杂。您没有使用任何JPA功能。而是执行以下

  1. spring-boot-starter-data-jpa替换为spring-boot-starter-jdbc
  2. 删除您的DnitRepository界面
  3. 在您注入JdbcTemplate的位置注入DnitRepository
  4. dnitRepository.insertdata(2, someJsonDataAsString );替换为jdbcTemplate.executeUpdate("insert into dnit(id, data) VALUES (?,to_json(?))", id, data);

您已经在使用普通SQL(以非常复杂的方式),如果您需要普通SQL(并且不需要JPA),则只需使用SQL。

当然,与其将JdbcTemplate直接注入到控制器中,您可能希望将该逻辑/复杂性隐藏在存储库或服务中。

答案 2 :(得分:1)

在这种情况下,我使用上面的定制转换器类,您可以自由地将其添加到您的库中。它正在与EclipseLink JPA Provider一起使用。

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.log4j.Logger;
import org.postgresql.util.PGobject;

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Map;

@Converter
public final class PgJsonbToMapConverter implements AttributeConverter<Map<String, ? extends Object>, PGobject> {

    private static final Logger LOGGER = Logger.getLogger(PgJsonbToMapConverter.class);
    private static final ObjectMapper MAPPER = new ObjectMapper();

    @Override
    public PGobject convertToDatabaseColumn(Map<String, ? extends Object> map) {
        PGobject po = new PGobject();
        po.setType("jsonb");

        try {
            po.setValue(map == null ? null : MAPPER.writeValueAsString(map));
        } catch (SQLException | JsonProcessingException ex) {
            LOGGER.error("Cannot convert JsonObject to PGobject.");
            throw new IllegalStateException(ex);
        }
        return po;
    }

    @Override
    public Map<String, ? extends Object> convertToEntityAttribute(PGobject dbData) {
        if (dbData == null || dbData.getValue() == null) {
            return null;
        }
        try {
            return MAPPER.readValue(dbData.getValue(), new TypeReference<Map<String, Object>>() {
            });
        } catch (IOException ex) {
            LOGGER.error("Cannot convert JsonObject to PGobject.");
            return null;
        }
    }

}

使用示例,用于名为Customer的实体。

@Entity
@Table(schema = "web", name = "customer")
public class Customer implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Convert(converter = PgJsonbToMapConverter.class)
    private Map<String, String> info;

    public Customer() {
        this.id = null;
        this.info = null;
    }

    // Getters and setter omitted.

答案 3 :(得分:0)

如果您使用的是 R2DBC ,则可以使用依赖项io.r2dbc:r2dbc-postgresql,并在实体类的成员属性中使用类型io.r2dbc.postgresql.codec.Json,例如:

public class Rule {
    @Id
    private String client_id;
    private String username;
    private String password;
    private Json publish_acl;
    private Json subscribe_acl;
}

答案 4 :(得分:0)

已经有几个答案,我很确定它们适用于多种情况。我不想使用更多我不知道的依赖项,所以我寻找另一种解决方案。 重要的部分是 AttributeConverter,它将 jsonb 从数据库映射到您的对象,反之亦然。因此,您必须使用 @Convert 注释实体中 jsonb 列的属性并链接您的 AttributeConverter 并添加 @Column(columnDefinition = "jsonb")< /strong> 也是如此,因此 JPA 知道这是数据库中的什么类型。这应该已经可以启动 spring boot 应用程序了。但是,每当您尝试使用 JpaRepository 进行 save() 时,您都会遇到问题。我收到了消息:

<块引用>

PSQLException:错误:“myColumn”列的类型为 jsonb 但 表达式是字符变化类型。

<块引用>

提示:您需要重写或转换表达式。

发生这种情况是因为 postgres 对类型有点认真。 您可以通过更改配置来解决此问题:

<块引用>

datasource.hikari.data-source-properties: stringtype=unspecified

<块引用>

datasource.tomcat.connection-properties: stringtype=unspecified

后来它对我很有用,这是一个最小的例子。 我使用 JpaRepositories:

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface MyEntityRepository extends JpaRepository<MyEntity, Integer> {
}

实体:

import javax.persistence.Column;
import javax.persistence.Convert;

public class MyEntity {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  protected Integer id;

  @Convert(converter = MyConverter.class)
  @Column(columnDefinition = "jsonb")
  private MyJsonObject jsonContent;

}

json 的模型:

public class MyJsonObject {

  protected String name;

  protected int age;

}

转换器,我在这里使用 Gson,但您可以随意映射它:

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

@Converter(autoApply = true)
public class MyConverter implements AttributeConverter<MyJsonObject, String> {

  private final static Gson GSON = new Gson();

  @Override
  public String convertToDatabaseColumn(MyJsonObject mjo) {
    return GSON.toJson(mjo);
  }

  @Override
  public MyJsonObject convertToEntityAttribute(String dbData) {
    return GSON.fromJson(dbData, MyJsonObject.class);
  }
}

SQL:

create table my_entity
(
    id serial primary key,
    json_content jsonb

);

还有我的 application.yml (application.properties)

  datasource:
    hikari:
      data-source-properties: stringtype=unspecified
    tomcat:
      connection-properties: stringtype=unspecified