MyBatis 学习笔记(五)---MyBatis通用类型处理器的实现与自动注册

简介: 实际项目中,我们经常要处理一些枚举类型的数据。例如:订单的状态就分为已下单,已付款,已发货,订单完成等等很多状态,数据库中我们一般只存储的一个数字表示各种状态。但是,前台显示的话就需要显示名称给用户看,所以这中间就涉及到一个转化。我们见过太多了在前端通过 if, else 写死判断的。这样初期没啥,后期难以扩展及维护。针对这种情况,我们思考下能不能直接将枚举直接返回给前端,让前端显示时取value, 保存时传入key?这样的前端就不用写一堆判断了。

概述及背景

实际项目中,我们经常要处理一些枚举类型的数据。例如:订单的状态就分为已下单,已付款,已发货,订单完成等等很多状态,数据库中我们一般只存储的一个数字表示各种状态。但是,前台显示的话就需要显示名称给用户看,所以这中间就涉及到一个转化。我们见过太多了在前端通过 if, else 写死判断的。这样初期没啥,后期难以扩展及维护。针对这种情况,我们思考下能不能直接将枚举直接返回给前端,让前端显示时取value, 保存时传入key?这样的前端就不用写一堆判断了。

项目结构

项目的结构如上图所示,主要有7个部分,其中 1,4,7 是通过MyBatis操作数据库所必须的。我们只做简要分析,其余如通用的类型处理GeneralEnumHandler 和重写TypeHandlerRegistry类将是我们重点分析的对象。

枚举类基类

public interface BaseEnum<E extends Enum<?>, T> {
    /**
     * 真正与数据库进行映射的值
     * @return
     */
    T getKey();
    /**
     * 显示的信息
     * @return
     */
    String getValue();
}

枚举类基类作为万能的模板,主要定义了获取key和value的方法,供子类实现。然后其传入的泛型<E extends Enum<?>, T>一个是枚举类自身,一个是枚举的key的类型。


通用类型处理器的设计

// 所有的自定义类型处理器都需要实现TypeHandler或者继承BaseTypeHandler类。
public class GeneralEnumHandler<E extends BaseEnum> extends BaseTypeHandler<E> {
    private Class<E> type;
    private E[] enums;
    //设置参数到preparedStatement
    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, E e, JdbcType jdbcType) throws SQLException {
        if (jdbcType == null) {
            preparedStatement.setObject(i, e.getKey());
        } else {
            preparedStatement.setObject(i, e.getKey(), jdbcType.TYPE_CODE);
        }
    }
   //设置参数到preparedStatement
    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, E e, JdbcType jdbcType) throws SQLException {
        if (jdbcType == null) {
            preparedStatement.setObject(i, e.getKey());
        } else {
            preparedStatement.setObject(i, e.getKey(), jdbcType.TYPE_CODE);
        }
    }
    //根据ResultSet返回的key,找到其对应的enum
    @Override
    public E getNullableResult(ResultSet resultSet, String s) throws SQLException {
        if (resultSet.wasNull()) {
            return null;
        }
        Object key = resultSet.getObject(s);
        return locateEnumsStatus(key);
    }
   // 省略部分方法。
    /**
     * @param key
     * @return
     */
    private E locateEnumsStatus(Object key) {
        if (key instanceof Integer) {
            for (E anEnum : enums) {
                if (anEnum.getKey() == key) {
                    return anEnum;
                }
            }
            throw new IllegalArgumentException("未知的枚举类型:" + key + ",请核对" + type.getSimpleName());
        }
        if (key instanceof String) {
            for (E anEnum : enums) {
                if (anEnum.getKey().equals(key)) {
                    return anEnum;
                }
            }
            throw new IllegalArgumentException("未知的枚举类型:" + key + ",请核对" + type.getSimpleName());
        }
        throw new IllegalArgumentException("未知的枚举类型:" + key + ",请核对" + type.getSimpleName());
    }
}

如上处理后,我们就可以在配置文件或者映射文件中配置使用通用的处理器了。

我们只需要在配置文件中配置(1-1配置)

<typeHandlers>
        <typeHandler handler="com.jay.chapter3.handler.GeneralEnumHandler" javaType="com.jay.chapter3.enums.SexEnum"/>
    </typeHandlers>

或者在映射文件中配置

<result property="sexEnum" column="sex"
                typeHandler="com.jay.chapter3.Handler.GeneralEnumHandler"/>

需要注意的是要在用到枚举类的实体中引入枚举类对象。如下,

public class Student {
     /**
     * 性别
     */
    private SexEnum sexEnum;
  //省略getter,setter方法
}

如上,定义一个通用的类型处理器来处理枚举还是比较方便的。只是美中不足的是,如果我们有很多枚举类,那么我们就需要在配置文件中定义很多(1-1配置),着实有点繁琐。那么我思考下如何减少配置呢?哈哈,答案就是对枚举类实现自动扫描。但是,MyBatis 的框架默认是不支持的。下面我们就来看看源码。

TypeHandlerRegistry源码

 

public void register(Class<?> typeHandlerClass) {
    boolean mappedTypeFound = false;
    MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
      for (Class<?> javaTypeClass : mappedTypes.value()) {
        register(javaTypeClass, typeHandlerClass);
        mappedTypeFound = true;
      }
    }
    if (!mappedTypeFound) {
      register(getInstance(null, typeHandlerClass));
    }
  }

如上,我们MyBatis框架是通过register(Class<?> typeHandlerClass)将类型处理器注册到注册机中。但是,我们也知道,MappedTypes 元数据只支持配置一个个具体的Class对象,这显然不符合我们的需求。那么我们的重写点就从MappedTypes元数据开始,使其可以支持包名配置,然后,在通过包扫描其下面的所有枚举类,循环注入即可。


重写MappedTypes元数据

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MappedTypes {
    Class<?>[] value() default {};
  // 增加包名配置
    String[] basePackage() default {};
}

需要注意的是重写框架的类,包名路径要与框架保持完全一致。 然后在通用类型处理器GeneralEnumHandler中类名上添加如下注解即可

// 该包名是枚举类下的包名路径
@MappedTypes(basePackage = {"com.jay.chapter3.enums"})

接着我们来重写下TypeHandlerRegistry的register方法。


重写TypeHandlerRegistry

public void register(Class<?> typeHandlerClass) {
    boolean mappedTypeFound = false;
    MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
      if (mappedTypes.value().length > 0) {
        for (Class<?> javaTypeClass : mappedTypes.value()) {
          register(javaTypeClass, typeHandlerClass);
          mappedTypeFound = true;
        }
      }
      if (mappedTypes.basePackage().length > 0) {
        for (String packageName : mappedTypes.basePackage()) {
//           扫描并注册包下所有继承于superType的类型
          ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
          resolverUtil.find(new ResolverUtil.IsA(Object.class), packageName);
        //获取该包下所有满足条件的class对象
        Set<Class<? extends Class<?>>> mTypes = resolverUtil.getClasses();
          for (Class<? extends Class<?>> javaTypeClass : mTypes) {
            // 注册枚举类以及其所使用的类型处理器
     register(javaTypeClass,typeHandlerClass);
            mappedTypeFound = true;
          }
        }
      }
    }
    if (!mappedTypeFound) {
      register(getInstance(null, typeHandlerClass));
    }
  }

如上,包扫描那个分支的主要流程有三个:

1.扫描出包下所有继承于superType(一般是Object.class)类型的class 对象

2.获取该包下所有满足条件的class对象

3.循环注册枚举类以及其所使用的类型处理器。

重写完成之后,接着我们来看看文件中的配置:

<typeHandlers>
        <package name="com.jay.chapter3.handler"/>
    </typeHandlers>

下面我们就来测试一下:测试代码如下:

@Test
    public void testSelectId() {
        String resource = "chapter3/mybatis-cfg.xml";
        try {
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession session = factory.openSession();
            Student3Mapper mapper = session.getMapper(Student3Mapper.class);
            Student student = mapper.selectStudentById(1);
            System.out.println("------->student={}"+student);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

测试结果如下:

总结

本文通过一个小小的demo 示范了如果如何自定义类型处理器,然后,通过重写MyBatis中的MappedTypes和TypeHandlerRegistry两个类,实现了对枚举类所在包的扫描已经注册。希望对读者朋友们有所帮助。

源代码

https://github.com/XWxiaowei/MyBatisLearn/tree/master/mybatisDemo

相关文章
|
7月前
|
SQL Java 数据库连接
Mybatis之核心配置文件详解、默认类型别名、Mybatis获取参数值的两种方式
【1月更文挑战第3天】 一、核心配置文件详解 二、默认的类型别名 三、MyBatis的增删改查 四、MyBatis获取参数值的两种方式 1、单个字面量类型的参数 2、多个字面量类型的参数 3、map集合类型的参数 4、实体类类型的参数 5、使用@Param标识参数
133 2
Mybatis之核心配置文件详解、默认类型别名、Mybatis获取参数值的两种方式
|
1月前
|
SQL Java 数据库连接
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
MyBatis-Plus是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。本文讲解了最新版MP的使用教程,包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段等核心功能。
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
|
2月前
|
SQL Java 数据库连接
mybatis使用四:dao接口参数与mapper 接口中SQL的对应和对应方式的总结,MyBatis的parameterType传入参数类型
这篇文章是关于MyBatis中DAO接口参数与Mapper接口中SQL的对应关系,以及如何使用parameterType传入参数类型的详细总结。
51 10
|
2月前
|
SQL Java 数据库连接
Mybatis中传入不同类型的值处理方案
这篇文章讲述了在Mybatis中如何处理传入不同类型参数的情况,包括单个值、列表及Map等,并提供了相应的XML映射和Java代码示例。
93 0
|
7月前
|
前端开发 Java 数据库连接
【Java实战篇】SpringBoot+MyBatis快速实现登录注册
【1月更文挑战第19天】【Java实战篇】SpringBoot+MyBatis快速实现登录注册
|
7月前
|
存储 XML Java
mybatis使用内部类处理一对多类型数据2
mybatis使用内部类处理一对多类型数据2
79 0
|
7月前
|
SQL Java 关系型数据库
MyBatisPlus学习笔记(SpringBoot版)
MyBatisPlus学习笔记(SpringBoot版)
510 0
|
7月前
|
SQL Java 数据库连接
Mybatis查询的时候BigDecimal类型的值查询失效的解决办法
Mybatis查询的时候BigDecimal类型的值查询失效的解决办法
|
7月前
|
存储 JSON Java
深入理解 MyBatis-Plus 中的 JSON 处理器及案例演示
深入理解 MyBatis-Plus 中的 JSON 处理器及案例演示
860 0
|
7月前
|
SQL 存储 测试技术
深入了解MyBatis-Plus中的枚举处理器及实例演示
深入了解MyBatis-Plus中的枚举处理器及实例演示
368 0
下一篇
DataWorks