在Spring Boot中使用Java枚举自动序列化参数并自动保存数据库 - Dario


分享在Spring应用程序中使用Java枚举Java Enum映射字段的有效方法。目标:

  • 一个RestController 类,以通过不同的HTTP方法(GET,POST等)获取并使用枚举。
  • 一个服务类,它通常用来做一些种类的业务逻辑的实现
  • 一些DAO类(例如JPA Entity和Spring Data Repository),通过保存或检索数据以非常简单的方式与数据库进行通信

我希望我的Web层(RestController)能够执行以下任务:
  • 自动将请求正文字段中的JSON转换(反序列化)为枚举
  • 自动将枚举转换(序列化)为JSON字段
  • 自动将请求参数或路径变量转换为枚举

在DAO层中,我希望具有以下机制:
  • 自动转换JPA实体的字段,该字段是要作为字符串写入数据库的枚举
  • 检索varchar字段并自动映射到JPA实体的Enum字段,而不是字符串

代码在https://github.com/dariux2016/template-projects

模型:

@Getter 
@Setter 
@ToString
@EqualsAndHashCode
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Item {
    
    private String name;
    private EnItemType type;
    private String explanation;

}

EnItemType是一个枚举它通过类型代码将被解析为正确的数据值,然后在我们的网页和DAO层来管理。目前,仅注意此枚举有一个代码字段,通过它我们可以在整个应用程序中维护引用。

public enum EnItemType {

    TYPE1("CODE_1"),
    TYPE2(
"CODE_2"),
    TYPE3(
"CODE_3");
    
    private String code;
    
    private EnItemType(String code) {
        this.code=code;
    }
    
    @JsonCreator
    public static EnItemType decode(final String code) {
        return Stream.of(EnItemType.values()).filter(targetEnum -> targetEnum.code.equals(code)).findFirst().orElse(null);
    }
    
    @JsonValue
    public String getCode() {
        return code;
    }
}


注意上面代码中:@JsonCreator和@JsonValue批注注解,下面解释。

在 Web层将JSON与枚举序列化和反序列化
对于GET请求,我们必须返回必须以JSON方式进行转换。这就是所谓的JSON序列化操作。
我们可以调用GET方法以按项目类型查找项目,并且可以在path变量中传递Enum代码(而不是Enum名称)。访问:GET http://localhost:8080/item/CODE_1,希望获得下面数据:
{
"name":"A",
"type":"CODE_1",
"explanation":"item A of first type"
}

@JsonCreator注释仅足以将JSON数据解释为枚举,但是如果枚举值是通过路径或请求参数到达的,该怎么办?在这种情况下,我们必须使用从String类型转换为Enum的机制。为此,我实现了以下转换器:

@RequestParameterConverter
public class StringToEnItemTypeConverter implements Converter<String, EnItemType> {
    
    @Override
    public EnItemType convert(String source) {
        return EnItemType.decode(source);
    }
}

这是一个Spring转换器,它使用一个自定义的@RequestParameterConverter批注,我利用该批注利用了组件扫描机制。然后,我实现了注释,如下所示,声明了用该注释注释的方法是Spring Component:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Component
public @interface RequestParameterConverter {

}

为了注册转换器,我使用了一个特定的配置类来实现Spring Web MVC接口。此类检索使用我的自定义@RequestParameterConverter注释注释的所有Spring Bean,然后将转换器注册到FormatterRegistry中,该类是专用于在Spring中格式化数据的类。该类如下:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Autowired
    private ListableBeanFactory beanFactory;
    
    @Override
    public void addFormatters(FormatterRegistry registry) {
        Map<String, Object> components = beanFactory.getBeansWithAnnotation(RequestParameterConverter.class);
        components.values().parallelStream().forEach(c -> {
            if(c instanceof Converter) {
                registry.addConverter((Converter)c);
            }
        });
    }
}

这样,无论您在何处定义了转换器类:如果Spring对其进行了扫描,那么它将被注册并添加到格式化程序集中。
我们已经完成了Web层枚举的使用,现在我们能够:

  • 自动将JSON请求正文字段转换为枚举
  • 自动将枚举转换为JSON字段
  • 自动将请求参数或路径变量转换为枚举

这种转换在RestController中自动进行:


@Api
@RestController
@RequestMapping(
        value = "/item",
        produces = { MediaType.APPLICATION_JSON_VALUE  })
public class ItemController {
    
    @Autowired
    private ItemService itemService;
    
    @GetMapping
    public List<Item> findItems() {
        return itemService.findItems();
    }
    
    @GetMapping(
"/{type}")
    public List<Item> findItemByType(@PathVariable(
"type") EnItemType type) {
        return itemService.findItemByType(type);
    }
    
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Item saveItem(@RequestBody Item item) {
        return itemService.saveItem(item);
    }

}


DAO层转换
ItemEntity:

@Entity
@Table(name="items")
@Getter
@Setter
public class ItemEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    @Column(name=
"type_code")
    private EnItemType type;
    
    private String explanation;
}

在ItemEntity类中,我们直接放置了EnItemType类型的字段,并且希望在插入或更新行时将该字段自动转换为varchar(String)字段,并在执行操作时自动将该字段映射为EnItemType选择操作。
为了实现此目标,我们可以使用JPA提供的名为AttributeConverter的组件。您可以实现此接口,以便在与数据库“对话”时自动将一种数据类型转换为另一种数据类型。我的自定义转换器如下:

/**
 * AttributeConvertEnItemTypeype, String>. Implements the following methods :
 * <ul>
 * <li>convertToDatabaseColumn : (given an Enum returns a String)
 * <li>convertToEntityAttribute : (given a String returns an Enum)
 * </ul>
 */

@Component
@Converter(autoApply = true)
public class EnItemTypeConverter implements AttributeConverter<EnItemType, String> {

    @Override
    public String convertToDatabaseColumn(final EnItemType attribute) {
        return Optional.ofNullable(attribute).map(EnItemType::getCode).orElse(null);
    }

    @Override
    public EnItemType convertToEntityAttribute(final String dbData) {
        return EnItemType.decode(dbData);
    }
}

它必须实现两个方法:

  • convertToDatabaseColumn:将枚举转换为字符串,在插入或更新行时使用
  • convertToEntityAttribute:将来自数据库的数据(仅映射到具有EnItemType枚举的实体中的数据)转换为正确的枚举值

ItemRepository :

public interface ItemRepository extends JpaRepository<ItemEntity, Long>{
    
    public List<ItemEntity> findByType(EnItemType type);

}

点击标题见原文。