美文网首页springboot
自定义Mybatis枚举类处理器

自定义Mybatis枚举类处理器

作者: 求心丶 | 来源:发表于2021-07-31 23:57 被阅读0次

前言

在实现业务代码时,根据业务场景会抽象出类似于状态、阶段等枚举类。依据枚举值表示特定阶段,定义枚举值时,一般会定义code和value两个属性,用code表示枚举的标识,value表示对枚举值的描述,虽然使用code值或者value值也可以作为阶段的标识(例如0表示未提交,1表示已提交),但是直接使用code或者value容易出错且不便于统一修改,而使用枚举值可以使代码更加清晰明确,并且更加好维护。
但是使用枚举值进行入库和读库时要注意,mybatis默认的枚举类转换器EnumTypeHandler,即按照枚举值进行处理的,不一定符合实际业务场景的需求。因此,需要实现自定义的mybatis枚举类转换器,对业务中使用的枚举类进行处理。

BaseTypeHandler

BaseTypeHandler是mybatis提供的一个支持泛型的抽象类型处理器,这个类声明如下:

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {

  /**
   * @deprecated Since 3.5.0 - See https://github.com/mybatis/mybatis-3/issues/1203. This field will remove future.
   */
  @Deprecated
  protected Configuration configuration;

  /**
   * @deprecated Since 3.5.0 - See https://github.com/mybatis/mybatis-3/issues/1203. This property will remove future.
   */
  @Deprecated
  public void setConfiguration(Configuration c) {
    this.configuration = c;
  }

  @Override
  public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
    if (parameter == null) {
      if (jdbcType == null) {
        throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
      }
      try {
        ps.setNull(i, jdbcType.TYPE_CODE);
      } catch (SQLException e) {
        throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "
              + "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "
              + "Cause: " + e, e);
      }
    } else {
      try {
        setNonNullParameter(ps, i, parameter, jdbcType);
      } catch (Exception e) {
        throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . "
              + "Try setting a different JdbcType for this parameter or a different configuration property. "
              + "Cause: " + e, e);
      }
    }
  }

  @Override
  public T getResult(ResultSet rs, String columnName) throws SQLException {
    try {
      return getNullableResult(rs, columnName);
    } catch (Exception e) {
      throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + e, e);
    }
  }

  @Override
  public T getResult(ResultSet rs, int columnIndex) throws SQLException {
    try {
      return getNullableResult(rs, columnIndex);
    } catch (Exception e) {
      throw new ResultMapException("Error attempting to get column #" + columnIndex + " from result set.  Cause: " + e, e);
    }
  }

  @Override
  public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
    try {
      return getNullableResult(cs, columnIndex);
    } catch (Exception e) {
      throw new ResultMapException("Error attempting to get column #" + columnIndex + " from callable statement.  Cause: " + e, e);
    }
  }

  public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;

  public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;

  public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;

}

其中声明了四个抽象方法,用于在入库时设置参数以及在读取时读取结果。我们只需要继承BaseTypeHandler并且实现抽象方法即可。

声明枚举类父级接口

由于java的枚举类不支持继承,而我们又想要对业务枚举类提供一个父级模板,因此只能通过实现接口的方式为这些枚举类使用同一个模板。提供父级模板的优势在于通过自定义枚举类转换器对模板类型进行处理即可,只要枚举类实现了该模板即可使用此自定义枚举类转换器,无需提供多种自定义枚举类转换器。

package com.cube.share.base.templates;

import org.springframework.lang.NonNull;

import java.io.Serializable;

/**
 * @author poker.li
 * @date 2021/7/30 11:05
 * <p>
 * 枚举类父级接口
 */
public interface IEnum<C extends Serializable, V extends Serializable> {

    /**
     * 获取枚举的code
     *
     * @return code
     */
    C getCode();

    /**
     * 获取枚举的value
     *
     * @return value
     */
    V getValue();

    /**
     * 根据code从指定枚举类中获取枚举值
     *
     * @param clazz 枚举类
     * @param code  code
     * @param <E>   枚举的类型
     * @return 枚举值
     */
    @NonNull
    static <E extends Enum<?> & IEnum, C> E formCode(@NonNull Class<E> clazz, @NonNull C code) {
        E[] elements = clazz.getEnumConstants();
        for (E element : elements) {
            if (element.getCode().equals(code)) {
                return element;
            }
        }
        throw new IllegalArgumentException("不合法的枚举code");
    }

}

自定义枚举类转换器

package com.cube.share.handler.config;

import com.cube.share.base.templates.IEnum;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.io.Serializable;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @author poker.li
 * @date 2021/7/30 11:52
 * <p>
 * 自定义枚举类处理器
 */
public class IEnumTypeHandler<C extends Serializable, V extends Serializable, E extends Enum<?> & IEnum<C, V>> extends BaseTypeHandler<IEnum<C, V>> {

    private final Class<E> type;

    public IEnumTypeHandler(Class<E> type) {
        if (type == null) {
            throw new IllegalArgumentException("Enum type must be not null!");
        }
        this.type = type;
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, IEnum parameter, JdbcType jdbcType) throws SQLException {
        ps.setObject(i, parameter.getCode());
    }

    @Override
    public IEnum<C, V> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        @SuppressWarnings("unchecked") C code = (C) rs.getObject(columnName);
        return rs.wasNull() ? null : IEnum.formCode(type, code);
    }

    @Override
    public IEnum<C, V> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return rs.wasNull() ? null : IEnum.formCode(type, columnIndex);
    }

    @Override
    public IEnum<C, V> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        @SuppressWarnings("unchecked") C code = (C) cs.getObject(columnIndex);
        return cs.wasNull() ? null : IEnum.formCode(type, code);
    }
}

代码比较简单,就不详细介绍了。

自适应枚举类转换器

在一些情况下,让mybatis根据枚举的声明顺序获取枚举值可能也是有用的,因此,可以根据枚举的类型自主选择枚举类处理器,同时兼容EnumOrdinalTypeHandler

package com.cube.share.handler.config;

import com.cube.share.base.templates.IEnum;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.EnumOrdinalTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @author poker.li
 * @date 2021/7/30 15:00
 * <p>
 * 自适应枚举类处理器
 */
public class AutoEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {

    private final BaseTypeHandler<E> typeHandler;

    public AutoEnumTypeHandler(Class<E> type) {
        if (type == null) {
            throw new IllegalArgumentException("Type must be not null!");
        }
        if (IEnum.class.isAssignableFrom(type)) {
            //noinspection unchecked
            typeHandler = new IEnumTypeHandler(type);
        } else {
            typeHandler = new EnumOrdinalTypeHandler<>(type);
        }
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
        typeHandler.setNonNullParameter(ps, i, parameter, jdbcType);
    }

    @Override
    public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return typeHandler.getNullableResult(rs, columnName);
    }

    @Override
    public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return typeHandler.getNullableResult(rs, columnIndex);
    }

    @Override
    public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return typeHandler.getNullableResult(cs, columnIndex);
    }
}

这里也可以看出声明业务枚举父级接口模板的重要性,可以将业务枚举归类统一处理,而其他未实现父级接口模板的枚举类则采用EnumOrdinalTypeHandler进行处理。

使自适应枚举类转换器生效

在yml文件中增加如下配置即可

mybatis:
  configuration:
    default-enum-type-handler: com.cube.share.handler.config.AutoEnumTypeHandler

注意,如果是在配置类中指定,要按照以下的方式进行声明

    @Bean
    @DependsOn("dataSource")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource, MybatisProperties mybatisProperties) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*Mapper.xml"));
        org.apache.ibatis.session.Configuration configuration = mybatisProperties.getConfiguration();
        configuration.getTypeHandlerRegistry().setDefaultEnumTypeHandler(AutoEnumTypeHandler.class);
        sqlSessionFactoryBean.setConfiguration(configuration);
        return sqlSessionFactoryBean.getObject();
    }

在配置类中进行指定时,有以下常见错误写法,会导致自定义枚举类转换器无法生效

    @Bean
    @DependsOn("dataSource")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource, MybatisProperties mybatisProperties) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*Mapper.xml"));
        //获取SqlSessionFactory实例
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBean.getObject();
        //SqlSessionFactory实例时依据Configuration创建,由于上一步已经生成了SqlSessionFactory,虽然下面通过Configuration获取注册器设置了默认的枚举类转换器
        //实际上对已经创建了的SqlSessionFactory实例并不会生效
        TypeHandlerRegistry typeHandlerRegistry = sqlSessionFactory.getConfiguration().getTypeHandlerRegistry();
        typeHandlerRegistry.setDefaultEnumTypeHandler(AutoEnumTypeHandler.class);
        return sqlSessionFactory;
    }

这种写法是在创建了SqlSessionFactory实例后再设置默认的枚举类转换器,实际上并不会生效。

测试

数据库脚本

CREATE TABLE `sys_task` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `description` varchar(1000) DEFAULT NULL,
  `submit_status` tinyint NOT NULL,
  `phase_status` tinyint DEFAULT NULL,
  `chinese_number` char(10) DEFAULT NULL,
  `ordinal_type` int NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

业务枚举类

package com.cube.share.handler.config;

import com.cube.share.base.templates.IEnum;

/**
 * @author poker.li
 * @date 2021/7/31 9:42
 * <p>
 * 阶段状态枚举类
 */
public enum PhaseStatus implements IEnum<Integer, String> {

    /**
     * 未开始
     */
    NOT_BEGUN(1, "未开始"),
    /**
     * 进行中
     */
    ONGOING(2, "进行中"),
    /**
     * 已结束
     */
    FINISHED(3, "已结束");

    private final Integer code;

    private final String desc;

    PhaseStatus(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    @Override
    public Integer getCode() {
        return this.code;
    }

    @Override
    public String getValue() {
        return this.desc;
    }
}
package com.cube.share.handler.config;

import com.cube.share.base.templates.IEnum;

/**
 * @author cube.li
 * @date 2021/7/31 22:22
 * @description 大写数字枚举类
 */
public enum ChineseNumber implements IEnum<String, Integer> {

    ONE("壹", 1),
    TWO("贰", 2),
    THREE("弎", 3);

    private final String code;

    private final Integer value;

    ChineseNumber(String code, Integer value) {
        this.code = code;
        this.value = value;
    }

    @Override
    public String getCode() {
        return this.code;
    }

    @Override
    public Integer getValue() {
        return this.value;
    }
}

普通枚举类

package com.cube.share.handler.config;

/**
 * @author cube.li
 * @date 2021/7/31 22:59
 * @description 顺序枚举类
 */
public enum OrdinalType {

    THREE,
    ONE,
    TWO
}

实体类

package com.cube.share.handler.entity;

import com.cube.share.handler.config.ChineseNumber;
import com.cube.share.handler.config.OrdinalType;
import com.cube.share.handler.config.PhaseStatus;
import com.cube.share.handler.config.SubmitStatus;
import lombok.Data;

/**
 * @author poker.li
 * @date 2021/7/30 15:56
 * <p>
 * 任务实体
 */
@Data
public class SysTask {

    private Long id;

    private String name;

    private String description;

    private SubmitStatus submitStatus;

    private PhaseStatus phaseStatus;

    private ChineseNumber chineseNumber;

    private OrdinalType ordinalType;
}

单元测试

package com.cube.share.handler.service;

import com.cube.share.handler.config.ChineseNumber;
import com.cube.share.handler.config.PhaseStatus;
import com.cube.share.handler.config.SubmitStatus;
import com.cube.share.handler.entity.SysTask;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

/**
 * @author poker.li
 * @date 2021/7/30 16:27
 */
@SpringBootTest
class SysTaskServiceTest {

    @Resource
    SysTaskService sysTaskService;

    @Test
    void save() {
        SysTask sysTask = new SysTask();
        sysTask.setName("任务3");
        sysTask.setDescription("任务三的描述");
        sysTask.setSubmitStatus(SubmitStatus.SUBMITTED);
        sysTask.setPhaseStatus(PhaseStatus.FINISHED);
        sysTask.setChineseNumber(ChineseNumber.THREE);
        sysTaskService.save(sysTask);
    }

    @Test
    void getOne() {
        SysTask sysTask = sysTaskService.getOne(11L);
        System.out.println(sysTask);
    }
}
微信截图_20210731235921.png 微信截图_20210731235229.png
可以看出读库入库均能正确执行,对于业务枚举类采用自定义枚举类处理器IEnumTypeHandler处理,对于普通枚举类采用EnumOrdinalTypeHandler处理。

示例代码: https://gitee.com/li-cube/share/tree/master/enum-handler

相关文章

  • SpringBoot和mybatis项目中使用枚举

    枚举基类 枚举定义 mybatis类型处理器 mybatis.xml中配置 mybatis mapper文件中re...

  • 自定义Mybatis枚举类处理器

    前言 在实现业务代码时,根据业务场景会抽象出类似于状态、阶段等枚举类。依据枚举值表示特定阶段,定义枚举值时,一般会...

  • 枚举--java24(02/17/2016)

    如何自定义枚举类如何使用enum定义枚举类、枚举类的主要方法实现接口的枚举类 JDK1.5之前需要自定义枚举类JD...

  • Chapter 8 . 枚举

    阅读原文 Chapter 8 . 枚举 8.1 枚举类 主要内容: 如何自定义枚举类 如何使用enum定义枚举类 ...

  • 8.枚举和注解

    一、枚举 枚举的二种实现方式: 自定义类实现枚举 使用 enum 关键字实现枚举 自定义类实现枚举: 不需要提供s...

  • Java篇-枚举的使用

    一 : 自定义枚举类 枚举类调用 二 : 使用enum关键字定义枚举类 让枚举类实现接口: 可以让不同的枚举类的对...

  • 枚举类与注解

    1.自定义枚举类: 2.使用enum关键字定义枚举类:image.png 其余步骤与自定义枚举类一致

  • java enum实现原理

    一、分析自定义枚举类 普通的枚举类和抽象枚举类相似,故直接分析抽象枚举类。 1. 编写一个抽象枚举类 2. 编译 ...

  • Java高级-枚举类与注解

    10.1.枚举类的使用: 入门 类的对象只有有限个,确定的 自定义枚举类 一.枚举类的使用1.枚举类的理解: 类的...

  • 枚举2--java25(02/18/2016)

    一、枚举类1.如何自定义枚举类2.如何使用enum关键字定义枚举类:将枚举类对象相同部分删掉,同时中间用逗号分隔。...

网友评论

    本文标题:自定义Mybatis枚举类处理器

    本文链接:https://www.haomeiwen.com/subject/jqvhvltx.html