springboot使用枚举整合性别等

使用背景

在项目中需要将数据库中的gender(1男,0女),读取时做一个转换,转换为文字 男、女。同时,有另一个字段 info_tpye(三个不同的类型,数据库存储的是 1,2,3,但是需要展示对应的文字信息)。于是,使用bean对象集成枚举,在读取、插入数据时进行转换。

《阿里巴巴Java开发手册》将接口中枚举的使用分为两类,即 接口参数和接口返回值,并规定: 接口参数可以使用枚举类型,但接口返回值不可以使用枚举类型(包括含枚举类型的POJO对象)。
小小讨论:
Java中出现的任何元素,在Gosling的角度都会有背后的思考和逻辑(尽管并非绝对完美,但Java的顶层抽象已经是天才级了),比如:接口、抽象类、注解、和本文提到的枚举。枚举有好处,类型安全,清晰直接,还可以使用等号来判断,也可以用在switch中。它的劣势也是明显的,就是不要扩展。可是为什么在返回值和参数进行了区分呢,如果不兼容,那么两个都有问题,怎么允许参数可以有枚举。当时的考虑,如果参数也不能用,那么枚举几乎无用武之地了。参数输出,毕竟是本地决定的,你本地有的,传送过去,向前兼容是不会有问题的。但如果是接口返回,就比较恶心了,因为解析回来的这个枚举值,可能本地还没有,这时就会抛出序列化异常。

比如:你的本地枚举类,有一个天气Enum:SUNNY, RAINY, CLOUDY,如果根据天气计算心情的方法:guess(WeatcherEnum xx),传入这三个值都是可以的。返回值:Weather guess(参数),那么对方运算后,返回一个SNOWY,本地枚举里没有这个值,傻眼了

定义接口 和 枚举类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public interface ValueEnum<T> {
T value();
}
public interface DescriptionEnum {
String description();
}
public enum Gender implements ValueEnum<Integer>,DescriptionEnum {
/**
* 性别男
*/
MALE(1,"男"),
/**
* 性别女
*/
FEMALE(2,"女");
private Integer value;
private String description;
Gender(Integer value,String description) {
this.value = value;
this.description = description;
}
@Override
public String description() {
return description;
}
@Override
public Integer value() {
return value;
}

}

有个使用Gender的pojo类User(@Data为lombok注解)

1
2
3
4
5
6
7
@Data
public class User {
private Long id;
private String name;
private Gender gender;
private String email;
}

使用枚举作为接口参数

 
Spring 默认使用Bean接收枚举参数时支持 字面量,这也是我们常见的做法。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Data
public class UserCommand {
private String name;
private Gender gender;
private String email;
}
@ApiOperation("添加用户")
@PostMapping("/users")
public User users(User command){
User user = new User();
BeanUtils.copyProperties(command,user);
return user;
}

VJR8PJ.png

注意这种方式不支持枚举的ordinal值

使用Json接收枚举参数

Json数据都放在请求体中,后台使用注解 @RequestBody+command bean接收(也可以从HttpServletRequest的getInputStream获取)

1
2
3
4
5
6
7
@ApiOperation("添加用户")
@PostMapping("/users")
public User users(@RequestBody UserCommand userCommand) {
User user = new User();
BeanUtils.copyProperties(userCommand,user);
return user;
}

这种方式支持字面量,ordinary  

自定义@RequestBody 和@ResponseBody处理枚举参数

单独使用@JsonValue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public enum Gender implements ValueEnum<Integer>,DescriptionEnum{
/**
* 性别男
*/
MALE(10,"男"),
/**
* 性别女
*/
FEMALE(20,"女");
private Integer value;
private String description;
Gender(Integer value,String description) {
this.value = value;
this.description = description;
}
@Override
public String description() {
return description;
}
@JsonValue
@Override
public Integer value() {
return value;
}
}

@JsonValue 决定了序列化的字段,表明该枚举类型只能使用该字段值传值。它可标注在字段和getter方法上,推荐标注在getter方法上。因为标注在字段上,swagger参数列表只显示字面值,但实际不能使用字面值传值,这样会给使用该接口的开发人员造成误解。
标注value字段上:
VJR7zn.png

标注在value方法上
VJRjdU.png

这种方案虽然简单,但是只能单独使用某个字段传值。

使用@JsonValue+@JsonCreator,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public enum Gender implements ValueEnum<Integer>,DescriptionEnum{
/**
* 性别男
*/
MALE(10,"男"),
/**
* 性别女
*/
FEMALE(20,"女");
private Integer value;
private String description;
Gender(Integer value,String description) {
this.value = value;
this.description = description;
}
@JsonValue
@Override
public String description() {
return description;
}
@Override
public Integer value() {
return value;
}
@JsonCreator
public static Gender create(String value){
try{
return Gender.valueOf(value);
}catch (IllegalArgumentException e){
for (Gender gender : Gender.values()) {
try {
if (gender.value.equals(Integer.parseInt(value))) {
return gender;
}
}catch (NumberFormatException n) {
if (gender.description.equals(value)) {
return gender;
}
}
}
throw new IllegalArgumentException("No element matches "+value);
}
}
}

@JsonValue 是可选的,标注在getter方法上或者字段上,但是标注字段上Swagger显示参数不起作用,它可决定枚举反序列化的字段。如下
VJWPQ1.png

@JsonCreator 标注在静态方法上,表明使用该方法序列化和反序列化,方法内部是序列化的逻辑

上面的示例代码可使用三种方式传值。枚举类型的字面值,value属性或description属性,。这种方案就比较灵活可以任意决定一个或多个字段传值


示例

配置文件

1
2
3
# application.properties中
mybatis-plus.type-handlers-package=com.qingtengcloud.utils.enumHandler
mybatis-plus.type-enums-package=com.qingtengcloud.utils.enums

bean对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class BTenant {
/**
* 主键
*/
private Long id;
/**
* 租客名称
*/
private String name;
/**
* 手机号码
*/
private String mobile;
/**
* 性别:1男 2女
*/
private GenderEnum gender;
/**
* 0 无类型 1-有效未推送、2-蛋壳无效、3-其他
*/
private BTenantInfoTypeEnum infoType;

****get、set方法

枚举类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public enum GenderEnum {
OTHER("0", "未知"),
MAN("1", "男"),
FEMALE("2", "女");
private String key;
private String value;
private static Map<String, GenderEnum> genderEnumMap = new HashMap<>();
static {
for (GenderEnum sexEnum : GenderEnum.values()) {
genderEnumMap.put(sexEnum.getKey(), sexEnum);
}
}
/**
* 私有化构造函数
*
* @param key
* @param value
*/
private GenderEnum(String key, String value) {
this.key = key;
this.value = value;
}
/**
* @param key
* @return
* @Title: getGenderEnumByKey
* @Description: 依据key获取枚举
*/
public static GenderEnum getGenderEnumByKey(String key) {
return genderEnumMap.get(key);
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
@JsonValue
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@JsonCreator
public static GenderEnum create(String value){
try{
return GenderEnum.valueOf(value);
}catch (IllegalArgumentException e){
for (GenderEnum gender : GenderEnum.values()) {
try {
if (gender.value.equals(Integer.parseInt(value))) {
return gender;
}
}catch (NumberFormatException n) {
if (gender.value.equals(value)) {
return gender;
}
}
}
throw new IllegalArgumentException("No element matches "+value);
}
}
}

枚举控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class GenderEnumHandler extends BaseTypeHandler<GenderEnum> {
/**
* 用于定义设置参数时,该如何把Java类型的参数转换为对应的数据库类型
*/
@Override
public void setNonNullParameter(PreparedStatement ps, int i, GenderEnum parameter, JdbcType jdbcType) throws SQLException {
// baseTypeHandler已经帮我们做了parameter的null判断
// 第二个参数 : 存入到数据库中的值
ps.setString(i, parameter.getKey());
}
/**
* 用于定义通过字段名称获取字段数据时,如何把数据库类型转换为对应的Java类型
*/
@Override
public GenderEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {
// 根据数据库存储类型决定获取类型,本例子中数据库中存放String类型
String key = rs.getString(columnName);
if (rs.wasNull()) {
return null;
} else {
// 根据数据库中的key值,定位GenderEnum子类
return GenderEnum.getGenderEnumByKey(key);
}
}
/**
* 用于定义通过字段索引获取字段数据时,如何把数据库类型转换为对应的Java类型
*/
@Override
public GenderEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
// 根据数据库存储类型决定获取类型,本例子中数据库中存放String类型
String key = rs.getString(columnIndex);
if (rs.wasNull()) {
return null;
} else {
// 根据数据库中的key值,定位GenderEnum子类
return GenderEnum.getGenderEnumByKey(key);
}
}
/**
* 用定义调用存储过程后,如何把数据库类型转换为对应的Java类型
*/
@Override
public GenderEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
// 根据数据库存储类型决定获取类型,本例子中数据库中存放String类型
String key = cs.getString(columnIndex);
if (cs.wasNull()) {
return null;
} else {
// 根据数据库中的key值,定位GenderEnum子类
return GenderEnum.getGenderEnumByKey(key);
}
}
}