web-dev-qa-db-ja.com

マップJSON列をJava JPAのオブジェクトにマップする方法

多数の列を持つ大きなテーブルがあります。 MySQL Clusterに移動した後、次の理由でテーブルを作成できません。

エラー1118(42000):行サイズが大きすぎます。 BLOBをカウントしない使用済みテーブルタイプの最大行サイズは14000です。これにはストレージオーバーヘッドが含まれます。マニュアルを確認してください。一部の列をTEXTまたはBLOBに変更する必要があります

例として:

@Entity @Table (name = "appconfigs", schema = "myproject")
public class AppConfig implements Serializable
{
    @Id @Column (name = "id", nullable = false)
    @GeneratedValue (strategy = GenerationType.IDENTITY)
    private int id;

    @OneToOne @JoinColumn (name = "app_id")
    private App app;

    @Column(name = "param_a")
    private ParamA parama;

    @Column(name = "param_b")
    private ParamB paramb;
}

これは、構成パラメーターを保存するためのテーブルです。いくつかの列を1つにまとめてJSONオブジェクトとして保存し、それをJavaオブジェクトに変換できると考えていました。

例えば:

@Entity @Table (name = "appconfigs", schema = "myproject")
public class AppConfig implements Serializable
{
    @Id @Column (name = "id", nullable = false)
    @GeneratedValue (strategy = GenerationType.IDENTITY)
    private int id;

    @OneToOne @JoinColumn (name = "app_id")
    private App app;

    @Column(name = "params")
    //How to specify that this should be mapped to JSON object?
    private Params params;
}

定義した場所:

public class Params implements Serializable
{
    private ParamA parama;
    private ParamB paramb;
}

これを使用することで、すべての列を1つにまとめてテーブルを作成できます。または、テーブル全体を複数のテーブルに分割することもできます。個人的に私は最初の解決策を好みます。

とにかく私の質問は、テキストであり、JavaオブジェクトのJSON文字列を含むParams列をマップする方法ですか?

16
Rad

JPAコンバーターを使用して、エンティティーをデータベースにマップできます。 paramsフィールドに次のような注釈を追加するだけです。

@Convert(converter = JpaConverterJson.class)

次に、同様の方法でクラスを作成します(これにより、汎用オブジェクトが変換されます。特殊化することもできます)。

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

  private final static ObjectMapper objectMapper = new ObjectMapper();

  @Override
  public String convertToDatabaseColumn(Object meta) {
    try {
      return objectMapper.writeValueAsString(meta);
    } catch (JsonProcessingException ex) {
      return null;
      // or throw an error
    }
  }

  @Override
  public Object convertToEntityAttribute(String dbData) {
    try {
      return objectMapper.readValue(dbData, Object.class);
    } catch (IOException ex) {
      // logger.error("Unexpected IOEx decoding json from database: " + dbData);
      return null;
    }
  }

}

それだけです。このクラスを使用して、オブジェクトをテーブル内のjsonにシリアル化できます。

41

この記事 で説明したように、JPA AttributeConverterはJSONオブジェクトタイプをマップするにはあまりにも制限されているため、特にJSONバイナリとして保存する場合はそうです。

これらすべてのタイプを手動で作成する必要はありません。次の依存関係を使用してMaven Centralから簡単に取得できます。

<dependency>
    <groupId>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-52</artifactId>
    <version>${hibernate-types.version}</version> 
</dependency> 

詳細については、 hibernate-typesオープンソースプロジェクト をご覧ください。

それでは、すべての仕組みを説明します。

記事 PostgreSQLとMySQLの両方でJSONオブジェクトをマップする方法について書きました。

PostgreSQLの場合、JSONオブジェクトをバイナリ形式で送信する必要があります。

public class JsonBinaryType
    extends AbstractSingleColumnStandardBasicType<Object> 
    implements DynamicParameterizedType {

    public JsonBinaryType() {
        super( 
            JsonBinarySqlTypeDescriptor.INSTANCE, 
            new JsonTypeDescriptor()
        );
    }

    public String getName() {
        return "jsonb";
    }

    @Override
    public void setParameterValues(Properties parameters) {
        ((JsonTypeDescriptor) getJavaTypeDescriptor())
            .setParameterValues(parameters);
    }

}

JsonBinarySqlTypeDescriptorは次のようになります。

public class JsonBinarySqlTypeDescriptor
    extends AbstractJsonSqlTypeDescriptor {

    public static final JsonBinarySqlTypeDescriptor INSTANCE = 
        new JsonBinarySqlTypeDescriptor();

    @Override
    public <X> ValueBinder<X> getBinder(
        final JavaTypeDescriptor<X> javaTypeDescriptor) {
        return new BasicBinder<X>(javaTypeDescriptor, this) {
            @Override
            protected void doBind(
                PreparedStatement st, 
                X value, 
                int index, 
                WrapperOptions options) throws SQLException {
                st.setObject(index, 
                    javaTypeDescriptor.unwrap(
                        value, JsonNode.class, options), getSqlType()
                );
            }

            @Override
            protected void doBind(
                CallableStatement st, 
                X value, 
                String name, 
                WrapperOptions options)
                    throws SQLException {
                st.setObject(name, 
                    javaTypeDescriptor.unwrap(
                        value, JsonNode.class, options), getSqlType()
                );
            }
        };
    }
}

JsonTypeDescriptorは次のようになります。

public class JsonTypeDescriptor
        extends AbstractTypeDescriptor<Object> 
        implements DynamicParameterizedType {

    private Class<?> jsonObjectClass;

    @Override
    public void setParameterValues(Properties parameters) {
        jsonObjectClass = ( (ParameterType) parameters.get( PARAMETER_TYPE ) )
            .getReturnedClass();

    }

    public JsonTypeDescriptor() {
        super( Object.class, new MutableMutabilityPlan<Object>() {
            @Override
            protected Object deepCopyNotNull(Object value) {
                return JacksonUtil.clone(value);
            }
        });
    }

    @Override
    public boolean areEqual(Object one, Object another) {
        if ( one == another ) {
            return true;
        }
        if ( one == null || another == null ) {
            return false;
        }
        return JacksonUtil.toJsonNode(JacksonUtil.toString(one)).equals(
                JacksonUtil.toJsonNode(JacksonUtil.toString(another)));
    }

    @Override
    public String toString(Object value) {
        return JacksonUtil.toString(value);
    }

    @Override
    public Object fromString(String string) {
        return JacksonUtil.fromString(string, jsonObjectClass);
    }

    @SuppressWarnings({ "unchecked" })
    @Override
    public <X> X unwrap(Object value, Class<X> type, WrapperOptions options) {
        if ( value == null ) {
            return null;
        }
        if ( String.class.isAssignableFrom( type ) ) {
            return (X) toString(value);
        }
        if ( Object.class.isAssignableFrom( type ) ) {
            return (X) JacksonUtil.toJsonNode(toString(value));
        }
        throw unknownUnwrap( type );
    }

    @Override
    public <X> Object wrap(X value, WrapperOptions options) {
        if ( value == null ) {
            return null;
        }
        return fromString(value.toString());
    }

}

次に、クラスレベルまたはpackage-info.Javaパッケージレベル記述子で新しい型を宣言する必要があります。

@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)

エンティティマッピングは次のようになります。

@Type(type = "jsonb")
@Column(columnDefinition = "json")
private Location location;

Hibernate 5以降を使用している場合、JSONタイプは Postgre92Dialectによって自動的に登録されます です。

それ以外の場合は、自分で登録する必要があります。

public class PostgreSQLDialect extends PostgreSQL91Dialect {

    public PostgreSQLDialect() {
        super();
        this.registerColumnType( Types.Java_OBJECT, "json" );
    }
}
5
Vlad Mihalcea

同様の問題がありましたが、@ ExternalizerアノテーションとJacksonを使用してデータをシリアル化/デシリアライズすることで解決しました(@ExternalizerはOpenJPA固有のアノテーションなので、JPA実装の同様の可能性を確認する必要があります)。

@Persistent
@Column(name = "params")
@Externalizer("toJSON")
private Params params;

Paramsクラスの実装:

public class Params {
    private static final ObjectMapper mapper = new ObjectMapper();

    private Map<String, Object> map;

    public Params () {
        this.map = new HashMap<String, Object>();
    }

    public Params (Params another) {
        this.map = new HashMap<String, Object>();
        this.map.putAll(anotherHolder.map);
    }

    public Params(String string) {
        try {
            TypeReference<Map<String, Object>> typeRef = new TypeReference<Map<String, Object>>() {
            };
            if (string == null) {
                this.map = new HashMap<String, Object>();
            } else {
                this.map = mapper.readValue(string, typeRef);
            }
        } catch (IOException e) {
            throw new PersistenceException(e);
        }
    }

    public String toJSON() throws PersistenceException {
        try {
            return mapper.writeValueAsString(this.map);
        } catch (IOException e) {
            throw new PersistenceException(e);
        }
    }

    public boolean containsKey(String key) {
        return this.map.containsKey(key);
    }

    // Hash map methods
    public Object get(String key) {
        return this.map.get(key);
    }

    public Object put(String key, Object value) {
        return this.map.put(key, value);
    }

    public void remove(String key) {
        this.map.remove(key);
    }

    public Object size() {
        return map.size();
    }
}

HTH

0
Magic Wand