Каков идеальный способ сериализации и десериализации атрибута полиморфной сущности в spring boot?

#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 «) в значении столбца?