#spring-boot #jpa #java-8 #jackson-databind
#spring-boot #jpa #java-8 #jackson-databind
Вопрос:
У меня есть класс сущности с атрибутом столбца, тип которого является абстрактным классом. Я хочу сериализовать (объект в строку JSON) при сохранении его в столбце базы данных и десериализовать его в абстрактный класс (который, в свою очередь, преобразует строку в соответствующий конкретный класс), когда она извлекается из базы данных.
Вот как я это сделал:
ProductEntity.java
@Entity
@Table(name="PRODUCT")
@Data
public class ProductEntity{
@Id
@Column(name = "ID", insertable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private BigInteger id;
@Column(name = "DESCRIPTION")
private String description;
@Column(name = "NAME")
private String name;
@Column(name = "PRODUCT_TYPE")
private String productType;
@Column(name = "PRODUCT_SPECS")
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property =
"productType") // -------------------> Map to concrete class based on productType value
@Convert(converter = ObjectConverter.class) // ------------> custom converter
private ProductSpecification productSpec;
}
ПРИМЕЧАНИЕ: столбец базы данных «PRODUCT_SPECS» имеет тип JSON.
ProductSpecification.java
@NoArgsConstructor
@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS,
include = JsonTypeInfo.As.WRAPPER_OBJECT,
@JsonSubTypes({
@JsonSubTypes.Type(value = ComputerSpecification.class, name = "computer"),
@JsonSubTypes.Type(value = SpeakerSpecification.class, name = "speaker")
})
public abstract class ProductSpecification{ }
ComputerSpecification.java
@Getter
@Setter
@NoArgsConstructor
@JsonTypeName("computer")
public class ComputerSpecification extends ProductSpecification {
String memory;
String displaySize;
String processor;
@JsonCreator
public ComputerSpecification (@JsonProperty("memory") String memory,
@JsonProperty("displaysize") String displaySize,
@JsonProperty("processor") String processor){
super();
this.memory = memory;
this.displaySize = displaySize;
this.processor = processor;
}
}
SpeakerSpecification.java
@Getter
@Setter
@NoArgsConstructor
@JsonTypeName("computer")
public class SpeakerSpecification extends ProductSpecification {
String dimension;
String sensitivity;
String bassPrinciple;
String amplifierPower;
@JsonCreator
public SpeakerSpecification (@JsonProperty("sensitivity") String sensitivity,
@JsonProperty("dimension") String dimension,
@JsonProperty("bassPrinciple") String bassPrinciple,
@JsonProperty("amplifierPower") String amplifierPower){
super();
this.sensitivity = sensitivity;
this.dimension = dimension;
this.bassPrinciple = bassPrinciple;
this.amplifierPower = amplifierPower;
}
}
ObjectConverter.java
ПРИМЕЧАНИЕ: я использую Jackson ObjectMapper для сериализации и десериализации.
public class ObjectConverter implements AttributeConverter<Object, String>{
private final static Logger LOGGER = LoggerFactory.getLogger(ObjectConverter.class);
private static final ObjectMapper mapper;
static {
mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL);
}
@Override
public String convertToDatabaseColumn(Object attributeObject) {
if (attributeObject == null) {
return "";
}
try {
return mapper.writeValueAsString(attributeObject);
} catch (JsonProcessingException e) {
LOGGER.error("Could not convert to database column", e);
return null;
}
}
@Override
public Object convertToEntityAttribute(String dbColumnValue) {
try {
if (StringUtils.isBlank(dbColumnValue)) {
return null;
}
return mapper.readValue(dbColumnValue, ProductSpecification.class); // ----> mapped to
abstract class
} catch (Exception e) {
LOGGER.error("Could not convert to entity attribute", e);
return null;
}
}
}
Тело запроса 1:
{
"name" : "Bose Bass Module 700 - Black- Wireless, Compact Subwoofer",
"description" : "This wireless, compact subwoofer is designed to be paired with the Bose sound
bar 700 to bring music, movies, and TV to life with Deep, dramatic bass. ",
"productSpec" : {
"sensitivity" : "90 dB",
"bassPrinciple" : "reflex",
"amplifierPower" : "700 watts",
"dimension" : "14-5/16inW x 42-13/16inH x 16-5/16inD"
}
}
Этот запрос сохраняется в столбце базы данных «PRODUCT_SPECS» как :
{".SpeakerSpecification ":{"sensitivity" : "90 dB","bassPrinciple" : "reflex", "amplifierPower" :"700
watts", "dimension" : "14-5/16inW x 42-13/16inH x 16-5/16inD" }}
Теперь это решение работает отлично. Ключ «SpeakerSpecification» не появляется ни в ответе на вызов GET API, ни в документе swagger. Но необходимость хранить информацию о типе в базе данных действительно беспокоит меня.
Есть ли лучший подход к этой проблеме, при котором я мог бы избежать указания typeinfo («.SpeakerSpecification «) в значении столбца?