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<>();