• 需求

    mybatis目前已经内嵌入了springboot中了,这说明其目前在数据访问层的绝对优势。而我们在开发的过程中,往往会在程序中使用枚举(enum) 来表示一些状态或选项,而在数据库中使用数字来存储。这样做的好处是在程序中使用enum更直观的可以知道每个值代表的状态及含义,还可以做国际化的功能。那么这样会带来一个问题那就是:程序中的枚举 与 数据库中的数字 转换问题。
  • 介绍

    抱歉,最近因为实在太忙,所以写一半就停了。等有空继续。不将就哈。停了大概一周的时间,在周一的早上继续来完成这篇文章。

    无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成 Java 类型。Mybatis默认为我们实现了许多TypeHandler, 当我们没有配置指定TypeHandler时,Mybatis会根据参数或者返回结果的不同,默认为我们选择合适的TypeHandler处理。

    对于enum而言,在mybatis中已经存在了EnumTypeHandler和EnumOrdinalTypeHandler两大处理器,他们都是继承自BaseTypeHandler处理器,那为何不能使用mybatis自定义的enum处理器而是要自己再定义枚举处理器呢?下面为您一步一步剖析。
  • 分析

    首先说说EnumTypeHandler与EnumOrdinalTypeHandler两者之间的区别吧:
    EnumTypeHandler存入数据库的是枚举的name,EnumOrdinalTypeHandler存入数据库的是枚举的位置。例如下方的枚举,当我们有一个枚举值是EStatus.init时,这时我们使用mybatis EnumTypeHandler存入数据库的是"init"字符串;而EnumOrdinalTypeHandler存入的是3,因为init是第四个值,第一个值disable的index是0。
    1. public enum EStatus {
    2. disable("0"), enable("1"), deleted("2"),
    3. init("10"), start("11"), wait("12"), end("13");
    4. }

    上面是一个简单的枚举。它包含了7个状态值,当我们没有设置枚举处理器时,mybatis默认使用EnumTypeHandler作为缺省处理器,当需要将枚举存入数据库时它调用的是枚举的name()方法从而获取的是像:disable,enable这样的值存入了数据库,并不是我们想要的(value)value存入数据库,可以看看源码:

    1. //
    2. // Source code recreated from a .class file by IntelliJ IDEA
    3. // (powered by Fernflower decompiler)
    4. //
    5. package org.apache.ibatis.type;
    6. import java.sql.CallableStatement;
    7. import java.sql.PreparedStatement;
    8. import java.sql.ResultSet;
    9. import java.sql.SQLException;
    10. public class EnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
    11. private Class<E> type;
    12. public EnumTypeHandler(Class<E> type) {
    13. if(type == null) {
    14. throw new IllegalArgumentException("Type argument cannot be null");
    15. } else {
    16. this.type = type;
    17. }
    18. }
    19. public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
    20. if(jdbcType == null) {
    21. ps.setString(i, parameter.name());
    22. } else {
    23. ps.setObject(i, parameter.name(), jdbcType.TYPE_CODE);
    24. }
    25. }
    26. public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
    27. String s = rs.getString(columnName);
    28. return s == null?null:Enum.valueOf(this.type, s);
    29. }
    30. public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    31. String s = rs.getString(columnIndex);
    32. return s == null?null:Enum.valueOf(this.type, s);
    33. }
    34. public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    35. String s = cs.getString(columnIndex);
    36. return s == null?null:Enum.valueOf(this.type, s);
    37. }
    38. }

    大家可以看到setNonNullParameter方法中ps.setString(i, parameter.name());调用的确实是name()方法。

    而在将数据库字段转java Bean字段的时候使用的方法getNullableResult中都是调用Enum.valueOf(this.type, s),而这个方法底层调用的是get(name)方法。

    所以EnumTypeHandler对我们来说并不适用,因为它在java Bean转数据库数据时获取的是枚举的name而不是value。

    而EnumOrdinalTypeHandler存入数据库的是参数的位置,所以这两个处理器都不是我们想要的。但是我们可以根据EnumTypeHandler的设计思想转变一下,变成符合我们想要的统一处理器。

  • 教程

    首先我们定义一个BaseEnumTypeHandler继承BaseTypeHandler,并实现抽象方法。
    1. package com.cmc.base.handler;
    2. import com.cmc.base.enums.EStatus;
    3. import com.cmc.base.utils.EnumUtil;
    4. import org.apache.ibatis.type.BaseTypeHandler;
    5. import org.apache.ibatis.type.JdbcType;
    6. import org.apache.ibatis.type.MappedTypes;
    7. import java.sql.CallableStatement;
    8. import java.sql.PreparedStatement;
    9. import java.sql.ResultSet;
    10. import java.sql.SQLException;
    11. public class BaseEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
    12. private Class<E> type;
    13. public BaseEnumTypeHandler() {}
    14. public BaseEnumTypeHandler(Class<E> type) {
    15. this.type = type;
    16. }
    17. @Override
    18. public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
    19. if (jdbcType == null) {
    20. ps.setString(i, parameter.toString());
    21. } else {
    22. ps.setObject(i, parameter.name(), jdbcType.TYPE_CODE);
    23. }
    24. }
    25. @Override
    26. public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
    27. return get(rs.getString(columnName));
    28. }
    29. @Override
    30. public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    31. return get(rs.getString(columnIndex));
    32. }
    33. @Override
    34. public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    35. return get(cs.getString(columnIndex));
    36. }
    37. private <E extends Enum<E>> E get(String v) {
    38. if (v == null) return null;
    39. if (StringUtils.isNumeric(v)) {
    40. return get(type, Integer.parseInt(v));
    41. } else {
    42. return Enum.valueOf(type, v);
    43. }
    44. }
    45. private <E extends Enum<E>> E get(Class<E> type, int v) {
    46. Method method = null;
    47. E result = null;
    48. try {
    49. method = type.getMethod("get", int.class);
    50. result = (E)method.invoke(type, v);
    51. } catch (NoSuchMethodException e) {
    52. result = Enum.valueOf(type, String.valueOf(v));
    53. e.printStackTrace();
    54. } catch (IllegalAccessException e) {
    55. e.printStackTrace();
    56. } catch (InvocationTargetException e) {
    57. e.printStackTrace();
    58. }
    59. return result;
    60. }
    61. }

    在上面的方法setNonNullParameter中大家可以看到我使用parameter.toString()替换了原来的parameter.name()方法,其实做到能统一处理枚举的关键就是这里,因为toString方法是Object的方法,所以不管传入的类型是什么都适用。而从数据库中取出的数据转Bean我使用的是和默认的处理器一致的方法get()。

    所以我们需要在枚举中定义toString和get方法

    1. package com.cmc.base.enums;
    2. /**
    3. * 状态枚举
    4. * @author chenmc
    5. *
    6. */
    7. public enum EStatus {
    8. disable("0"), enable("1"), deleted("2"),
    9. init("10"), start("11"), wait("12"), end("13");
    10. private final String value;
    11. private EStatus(String v) {
    12. this.value = v;
    13. }
    14. public String toString() {
    15. return this.value;
    16. }
    17. public static EStatus get(int v) {
    18. String str = String.valueOf(v);
    19. return get(str);
    20. }
    21. public static EStatus get(String str) {
    22. for (EStatus e : values()) {
    23. if(e.toString().equals(str)) {
    24. return e;
    25. }
    26. }
    27. return null;
    28. }
    29. /*public static String getName(EStatus e, Locale locale) {
    30. return I18N.getEnumName(e, locale);
    31. }*/
    32. }

    大家可看到toString方法返回的是当前对象的value值,这样就是我们想要的了。

    既然方法都写好了,那下面给大家讲讲如何在springboot中使用。

    1.在application.properties或者application.yml中添加

    1. mybatis:
    2. mapperLocations: classpath:mapper/*.xml
    3. typeAliasesPackage: com.cmc.schedule.model.entity
    4. typeHandlersPackage: com.cmc.schedule.model.handler

    typeHandlersPackage就是你自定义handler的包名,上面我们定义了一个BaseEnumTypeHandler,往往我们不太会在基类中修改或添加,因为基类可能是多个模块公用的,这时在我们的项目中定义一个EnumTypeHandler去继承我们的BaseEnumTypeHandler。

    1. package com.cmc.schedule.model.handler;
    2. import com.cmc.base.enums.EStatus;
    3. import com.cmc.base.handler.BaseEnumTypeHandler;
    4. import com.cmc.schedule.model.enums.EFeedback;
    5. import org.apache.ibatis.type.MappedTypes;
    6. /**
    7. * @author chenmc
    8. */
    9. @MappedTypes(value = { EFeedback.class, EStatus.class})
    10. public class EnumTypeHandler<E extends Enum<E>> extends BaseEnumTypeHandler<E> {
    11. public EnumTypeHandler(Class<E> type) {
    12. super(type);
    13. }
    14. }

    这样有多少个enum需要转换就可以在@MappedTypes注解中添加。是不是很方便了。

  • 结语

    希望可以帮助到大家,如果你有任何疑问都可以在下面留言,我会每天回复你。

SpringBoot Mybatis EnumTypeHandler自定义统一处理器的更多相关文章

  1. SpringBoot Mybatis 执行自定义SQL

    1.XML中执行自定义SQL. https://blog.csdn.net/u012427355/article/details/80654806 2.注解执行自定义SQL @Select(" ...

  2. 学习Spring Boot:(十二)Mybatis 中自定义枚举转换器

    前言 在 Spring Boot 中使用 Mybatis 中遇到了字段为枚举类型,数据库存储的是枚举的值,发现它不能自动装载. 解决 内置枚举转换器 MyBatis内置了两个枚举转换器分别是:org. ...

  3. SpringBoot系列——自定义统一异常处理

    前言 springboot内置的/error错误页面并不一定适用我们的项目,这时候就需要进行自定义统一异常处理,本文记录springboot进行自定义统一异常处理. 1.使用@ControllerAd ...

  4. 自定义统一api返回json格式(app后台框架搭建三)

    在统一json自定义格式的方式有多种:1,直接重写@reposeBody的实现,2,自定义一个注解,自己去解析对象成为json字符串进行返回 第一种方式,我就不推荐,想弄得的话,可以自己去研究一下源码 ...

  5. SpringBoot第十一篇:SpringBoot+MyBatis+Thymelaf实现CRUD

    作者:追梦1819 原文:https://www.cnblogs.com/yanfei1819/p/10936304.html 版权声明:本文为博主原创文章,转载请附上博文链接! 引言   总结前面几 ...

  6. MyBatis从入门到精通(十四):在MyBatis中使用类型处理器

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 本篇博客主要讲解在MyBatis中如何 ...

  7. SpringBoot+MyBatis Plus对Map中Date格式转换的处理

    在 SpringBoot 项目中, 如何统一 JSON 格式化中的日期格式 问题 现在的关系型数据库例如PostgreSQL/MySQL, 都已经对 JSON 类型提供相当丰富的功能, 项目中对于不需 ...

  8. springboot+mybatis+redis实现分布式缓存

    大家都知道springboot项目都是微服务部署,A服务和B服务分开部署,那么它们如何更新或者获取共有模块的缓存数据,或者给A服务做分布式集群负载,如何确保A服务的所有集群都能同步公共模块的缓存数据, ...

  9. Java逆向工程SpringBoot + Mybatis Generator + MySQL

    Java逆向工程SpringBoot+ Mybatis Generator + MySQL Meven pop.xml文件添加引用: <dependency> <groupId> ...

随机推荐

  1. get_k_data 接口文档 全新的免费行情数据接口

    get_k_data 接口文档 全新的免费行情数据接口 原创: Jimmy 挖地兔 2016-11-06 前言在tushareAPI里,曾经被用户喜欢和作为典范使用的API get_hist_data ...

  2. oracle中可能使用到的命令

    借鉴他人 1.su – oracle 不是必需,适合于没有DBA密码时使用,可以不用密码来进入sqlplus界面.2.sqlplus /nolog 或sqlplus system/manager 或. ...

  3. 1.3 正则表达式和python语言-1.3.4使用 match()方法匹配字符串

    1.3.4使用 match()方法匹配字符串(第一次写博客,格式,述语有不当之处还请见谅)2018-05-08 Python 代码是以Jupyter Notebook编写的,主要写的是python3的 ...

  4. CentOS7安装及简单配置(一)

    CentOS7是RHEL的社区版,摘抄维基百科的一段话如下: CentOS(Community Enterprise Operating System)是Linux发行版之一,它是来自于Red Hat ...

  5. VS2017无法发现单元测试,不能运行单元测试的解决方案

    问题: 在VS2017中新建空的单元测试后,无法运行测试,即右键菜单的"运行测试"和"调试测试" 不能运行,在测试资源管理中也无法列出这个测试. 解决方案: 1 ...

  6. vue 实现图片上传与预览,以及清除图片

    vue写原生的上传图片并预览以及清除图片的效果,下面是demo,其中里面有vue获取input框value值的方法以及vue中函数之间的调用 <!DOCTYPE html> <htm ...

  7. 限制输入字数JS

    <tr> <th><b>说明内容:</b><span id="content">(500字以内)</span> ...

  8. 【PHP版】火星坐标系 (GCJ-02) 与百度坐标系 (BD-09ll)转换算法

    首先感谢java版作者@宋宋宋伟,java版我是看http://blog.csdn.net/coolypf/article/details/8569813 然后根据java代码修改成了php代码. & ...

  9. dedecms 文章根据 权重排序

    原文链接 一: dede:list   标签 找到 /include/arc.listview.class.php if($orderby=="senddate" || $orde ...

  10. ES6 浏览器兼容性 和 Transpilation

     浏览器兼容性 和 Transpilation 你的 web 浏览器可能每隔几个月就会提示你去更新,你知道为什么吗,主要是一些安全漏洞,新特性,以及支持新的 HTML.CSS 和 JavaScript ...