Home > Net >  Hibernate mapping to store custom object as a String
Hibernate mapping to store custom object as a String

Time:07-01

I'm trying to create mapping that will store my custom object as a String to a single column in a database table.

Article.java

@Entity
public class Article {

    @Getter @Setter
    @Embedded
    @Column(name = "name", columnDefinition = "TEXT")
    @Convert(converter = LocalizedFieldConverter.class,
        attributeName = "name")
    private LocalizedField name = new LocalizedField();

    @Getter @Setter
    @Embedded
    @Column(name = "name", columnDefinition = "TEXT")
    @Convert(converter = LocalizedFieldConverter.class,
        attributeName = "description")
    private LocalizedField description = new LocalizedField();

}

LocalizedField.java

@Embeddable @Getter @Setter
public class LocalizedField {

    // map key is "en-US", value is a translation
    private Map<String, String> data = new HashMap<>();

}

LocalizedFieldConverter.java

@Converter
public class LocalizedFieldConverter
    implements AttributeConverter<LocalizedField, String>, Serializable
{

    @Override
    public String convertToDatabaseColumn(LocalizedField attribute) {
        if (attribute == null) { return null; }
        try {
            return new ObjectMapper().writeValueAsString(attribute);
        }
        catch (JsonProcessingException e) { return null; }
    }

    @Override
    public LocalizedField convertToEntityAttribute(String dbData) {
        if (dbData == null) { return null; }
        try {
            return new ObjectMapper().readValue(dbData, typeReference());
        }
        catch (JsonProcessingException e) { return null; }
    }

    private static final TypeReference<LocalizedField> typeReference() {
        return new TypeReference<>() { /* */ };
    }

}

When Hibernate tries to create the database, I get this one:

Caused by: org.hibernate.MappingException: Could not determine type for: java.util.Map, at table: article, for columns: [org.hibernate.mapping.Column(data)]

I don't want to use @ElementCollection as this would create additional table in my database.


Next try was to change mapping to this:

Article.java

@Getter @Setter
private LocalizedField name = new LocalizedField();

LocalizedField.java

@Convert(converter = LocalizedFieldMapConverter.class)
private Map<String, String> data = new HashMap<>();

LocalizedFieldMapConverter.java

@Converter
public class LocalizedFieldMapConverter
    implements AttributeConverter<Map<String, String>, String>, Serializable {

    @Override
    public String convertToDatabaseColumn(Map<String, String> attribute) {
        if (attribute == null) { return null; }
        try {
            return new ObjectMapper().writeValueAsString(attribute);
        }
        catch (JsonProcessingException e) { return null; }
    }

    @Override
    public Map<String, String> convertToEntityAttribute(String dbData) {
        if (dbData == null) { return null; }
        try {
            return new ObjectMapper().readValue(dbData, typeReference());
        }
        catch (JsonProcessingException e) { return null; }
    }

    private static final TypeReference<Map<String, String>> typeReference() {
        return new TypeReference<>() { /* */ };
    }

}

In second case I had to remove description property from entity because database column gets a name data which is a name of Map inside LocalizedField class.

In the final I would like to have more than one LocalizedField property handled by the same @Converter (of course, if possible).

But in cotrary to first attempt, at least I get column stored as expected, in JSON format but it's named data instead of name:

{
    "hr-HR" : "Sončnica & vitamin E šampon za kosu i tijelo 1000 ml",
    "en-US" : "Sunflower & Vitamin E Hair & Body Shampoo 1000 ml",
    "sr-RS" : "Сончница & витамин Е шампон за косу и тело 1000 мл"
}

What did I do wrong?

CodePudding user response:

Just drop @Embedded, @Embeddable, and attributeName from the original solution:

@Entity
public class Article {

    @Getter @Setter
    @Column(columnDefinition = "TEXT")
    @Convert(converter = LocalizedFieldConverter.class)
    private LocalizedField name = new LocalizedField();

    @Getter @Setter
    @Column(columnDefinition = "TEXT")
    @Convert(converter = LocalizedFieldConverter.class)
    private LocalizedField description = new LocalizedField();

}

@Getter @Setter
public class LocalizedField {

    private Map<String, String> data = new HashMap<>();

}

@Embedded and @AttributeConverter are mutually exclusive. You're getting a MappingException because Hibernate cannot figure out how the hell it's supposed to map LocalizedField as an embeddable, since data itself doesn't have an attribute converter.

CodePudding user response:

The solution that works with more than one LocalizedField properties needs use of @AttributeOverride.

Changes are:

Article.java

@Getter @Setter
@Embedded
@AttributeOverride(
    name = "data", // refers to Map name inside LocalizedField
    column = @Column(
        name = "name", // column name to use for converted LocalizedField
        columnDefinition = "TEXT"
    )
)
private LocalizedField name = new LocalizedField();

@Getter @Setter
@Embedded
@AttributeOverride(
    name = "data", // refers to Map name inside LocalizedField
    column = @Column(
        name = "description", // column name to use for converted LocalizedField
        columnDefinition = "TEXT"
    )
)
private LocalizedField description = new LocalizedField();

LocalizedField.java

@Convert(converter = LocalizedFieldMapConverter.class)
private Map<String, String> data = new HashMap<>();
  • Related