自学内容网 自学内容网

Mybatis自定义TypeHandler,直接存储枚举类对象

这篇文章中,我们已经知道如何使用枚举类直接接受前端的数字类型参数,省去了麻烦的转换。如果数据库需要保存枚举类的code,一般做法也是代码中手动转换,那么能不能通过某种机制,省去转换,达到代码中直接保存枚举对象,但是数据库中保存的却是code值呢。即我们的整体目标如下

数字
枚举类
数字
前端
后端-枚举类接收
Mybatis-枚举类接收
数据库保存数字类型

实际上Mybatis对枚举类有一定的支持,在官网中看到对枚举类的支持有两种:EnumTypeHandler和EnumOrdinalTypeHandler。前者是保存枚举的name,后置是保存枚举的ordinal值。这两个都不满足我们的需求,仿照它们,我们洗一个自己的TypeHandler。

自定义枚举TypeHandler

还是使用原先的数据对象

public class Product {
    private Status status;
    private String name;
    // getter and setter
}

// BaseEnumDeserial 见https://blog.csdn.net/weixin_41535316/article/details/142426433
@JsonDeserialize(using = BaseEnumDeserial.class)
public interface BaseEnum {
    Integer getCode();
}
pu
blic enum Status implements BaseEnum {
    ON_LINE(1000, "在线"),
    OFF_LINE(2000, "下线")
    ;
    private int code;
    private String desc;
    Status(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public static Status getByCode(int code) {
        final Status[] values = Status.values();
        for (int i = 0; i < values.length; i++) {
            if (values[i].code == code) {
                return values[i];
            }
        }
        throw new RuntimeException("不合法的code值");
    }
    @Override
    public Integer getCode() {
        return code;
    }
}

配置数据库连接

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/stu?serverTimezone=GMT&useSSL=false
    username: root
    password: root
mybatis:
  # 自定义的typeHandler所在包位置
  type-handlers-package: com.example.mybatis.typehandle
  mapper-locations: classpath:mapper/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

创建表

    CREATE TABLE `product`  (
        `name` varchar(255) NULL,
        `status` integer NULL
        );

创建Mapper


@Mapper
public interface ProductMapper {
    int insert(Product product);
    Product getByName(String name);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mybatis.mapper.ProductMapper">

    <resultMap id="product" type="com.example.mybatis.entity.Product">
        <result property="status" jdbcType="INTEGER" column="status"/>
        <result property="name" jdbcType="VARCHAR" column="name"/>
    </resultMap>

    <insert id="insert" parameterType="com.example.mybatis.entity.Product">
        insert into product(name, status) values(#{name}, #{status})
    </insert>

    <select id="getByName" parameterType="string" resultType="com.example.mybatis.entity.Product">
        select name, status from product where name = #{name}
    </select>
</mapper>

定义接口

    @Autowired
    ProductMapper productMapper;


    @PostMapping("/insertProduct")
    @ResponseBody
    public void insertProduct(@RequestBody Product product) {
        System.out.println(product.getStatus());
        System.out.println(product.getName());
        final int insert = productMapper.insert(product);
        System.out.println(insert);
        System.out.println("ok");
    }

    @GetMapping("/getProduct")
    @ResponseBody
    public void getProduct(@RequestParam String name) {
        final Product product = productMapper.getByName(name);
        System.out.println(product.getStatus());
        System.out.println(product.getName());
        System.out.println("ok");
    }

定义通用TypeHandler

通用TypeHandler同样面临在运行时怎么确定要转成哪种具体枚举类的问题,不同于jackson的运行时创建反序列化器,Mybatis是在项目启动时创建了所有的TypeHandler,且对于枚举类,会根据具体对象创建出不同的TypeHandler。
通用TypeHandler如下:

@MappedTypes(BaseEnum.class)
@MappedJdbcTypes(value = {JdbcType.SMALLINT,JdbcType.TINYINT,JdbcType.INTEGER}, includeNullJdbcType = true)
public class BaseEnumTypeHandler<T extends BaseEnum> extends BaseTypeHandler<BaseEnum> {

    private final Class<T> type;
    private final T[] enums;

    /**
     * 对于枚举,会优先使用Constructor<?> c = typeHandlerClass.getConstructor(Class.class);
     * 获取构造函数,如果没有,会获取午无参构造函数,这样就可以为同一个接口下的不同实现类创建不同的TypeHandler了
     */
    public BaseEnumTypeHandler(Class<T> clazz) {
        this.type = clazz;
        enums = type.getEnumConstants();
    }

    /**
     * 这里时设置参数 i是参数位置,parameter是外层传入的真实值,可以处理后再存入数据库
     */
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, BaseEnum parameter, JdbcType jdbcType) throws SQLException {
        ps.setInt(i, parameter.getCode());
    }

    /**
     * rs.getInt(columnName)拿到了数据库中保存的值,处理后得到返回给上层的类型 T
     */
    @Override
    public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
        final int code = rs.getInt(columnName);
        for (int i = 0; i < enums.length; i++) {
            if (enums[i].getCode() == code) {
                return enums[i];
            }
        }
        return null;
    }

    @Override
    public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        final int code = rs.getInt(columnIndex);
        for (int i = 0; i < enums.length; i++) {
            if (enums[i].getCode() == code) {
                return enums[i];
            }
        }
        return null;
    }

    @Override
    public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        final int code = cs.getInt(columnIndex);
        for (int i = 0; i < enums.length; i++) {
            if (enums[i].getCode() == code) {
                return enums[i];
            }
        }
        return null;
    }
}

例如我们有两个枚举类Status和GenderEnum都继实现了BaseEnum接口,Mybatis会创建两个BaseEnumTypeHandler。
在这里插入图片描述
另外需要注意的是,如果数据库中的枚举是NULL,那么ResultSet 的getInt()方法会返回0,而不是NULL。所以我们的枚举类最好不要使用0作为一个有意义的code值。


原文地址:https://blog.csdn.net/weixin_41535316/article/details/142433135

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!