SpringBoot Mybatis EnumTypeHandler自定义统一处理器
- 需求
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。public enum EStatus { disable("0"), enable("1"), deleted("2"), init("10"), start("11"), wait("12"), end("13"); }
上面是一个简单的枚举。它包含了7个状态值,当我们没有设置枚举处理器时,mybatis默认使用EnumTypeHandler作为缺省处理器,当需要将枚举存入数据库时它调用的是枚举的name()方法从而获取的是像:disable,enable这样的值存入了数据库,并不是我们想要的(value)value存入数据库,可以看看源码:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.apache.ibatis.type; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class EnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> { private Class<E> type; public EnumTypeHandler(Class<E> type) { if(type == null) { throw new IllegalArgumentException("Type argument cannot be null"); } else { this.type = type; } } public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException { if(jdbcType == null) { ps.setString(i, parameter.name()); } else { ps.setObject(i, parameter.name(), jdbcType.TYPE_CODE); } } public E getNullableResult(ResultSet rs, String columnName) throws SQLException { String s = rs.getString(columnName); return s == null?null:Enum.valueOf(this.type, s); } public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException { String s = rs.getString(columnIndex); return s == null?null:Enum.valueOf(this.type, s); } public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { String s = cs.getString(columnIndex); return s == null?null:Enum.valueOf(this.type, s); } }
大家可以看到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,并实现抽象方法。
package com.cmc.base.handler; import com.cmc.base.enums.EStatus; import com.cmc.base.utils.EnumUtil; import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.MappedTypes; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class BaseEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> { private Class<E> type; public BaseEnumTypeHandler() {} public BaseEnumTypeHandler(Class<E> type) { this.type = type; } @Override public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException { if (jdbcType == null) { ps.setString(i, parameter.toString()); } else { ps.setObject(i, parameter.name(), jdbcType.TYPE_CODE); } } @Override public E getNullableResult(ResultSet rs, String columnName) throws SQLException { return get(rs.getString(columnName)); } @Override public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return get(rs.getString(columnIndex)); } @Override public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return get(cs.getString(columnIndex)); } private <E extends Enum<E>> E get(String v) { if (v == null) return null; if (StringUtils.isNumeric(v)) { return get(type, Integer.parseInt(v)); } else { return Enum.valueOf(type, v); } } private <E extends Enum<E>> E get(Class<E> type, int v) { Method method = null; E result = null; try { method = type.getMethod("get", int.class); result = (E)method.invoke(type, v); } catch (NoSuchMethodException e) { result = Enum.valueOf(type, String.valueOf(v)); e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } return result; } }
在上面的方法setNonNullParameter中大家可以看到我使用parameter.toString()替换了原来的parameter.name()方法,其实做到能统一处理枚举的关键就是这里,因为toString方法是Object的方法,所以不管传入的类型是什么都适用。而从数据库中取出的数据转Bean我使用的是和默认的处理器一致的方法get()。
所以我们需要在枚举中定义toString和get方法
package com.cmc.base.enums; /** * 状态枚举 * @author chenmc * */ public enum EStatus { disable("0"), enable("1"), deleted("2"), init("10"), start("11"), wait("12"), end("13"); private final String value; private EStatus(String v) { this.value = v; } public String toString() { return this.value; } public static EStatus get(int v) { String str = String.valueOf(v); return get(str); } public static EStatus get(String str) { for (EStatus e : values()) { if(e.toString().equals(str)) { return e; } } return null; } /*public static String getName(EStatus e, Locale locale) { return I18N.getEnumName(e, locale); }*/ }
大家可看到toString方法返回的是当前对象的value值,这样就是我们想要的了。
既然方法都写好了,那下面给大家讲讲如何在springboot中使用。
1.在application.properties或者application.yml中添加
mybatis: mapperLocations: classpath:mapper/*.xml typeAliasesPackage: com.cmc.schedule.model.entity typeHandlersPackage: com.cmc.schedule.model.handler
typeHandlersPackage就是你自定义handler的包名,上面我们定义了一个BaseEnumTypeHandler,往往我们不太会在基类中修改或添加,因为基类可能是多个模块公用的,这时在我们的项目中定义一个EnumTypeHandler去继承我们的BaseEnumTypeHandler。
package com.cmc.schedule.model.handler; import com.cmc.base.enums.EStatus; import com.cmc.base.handler.BaseEnumTypeHandler; import com.cmc.schedule.model.enums.EFeedback; import org.apache.ibatis.type.MappedTypes; /** * @author chenmc */ @MappedTypes(value = { EFeedback.class, EStatus.class}) public class EnumTypeHandler<E extends Enum<E>> extends BaseEnumTypeHandler<E> { public EnumTypeHandler(Class<E> type) { super(type); } }
这样有多少个enum需要转换就可以在@MappedTypes注解中添加。是不是很方便了。
- 结语
希望可以帮助到大家,如果你有任何疑问都可以在下面留言,我会每天回复你。
SpringBoot Mybatis EnumTypeHandler自定义统一处理器的更多相关文章
- SpringBoot Mybatis 执行自定义SQL
1.XML中执行自定义SQL. https://blog.csdn.net/u012427355/article/details/80654806 2.注解执行自定义SQL @Select(" ...
- 学习Spring Boot:(十二)Mybatis 中自定义枚举转换器
前言 在 Spring Boot 中使用 Mybatis 中遇到了字段为枚举类型,数据库存储的是枚举的值,发现它不能自动装载. 解决 内置枚举转换器 MyBatis内置了两个枚举转换器分别是:org. ...
- SpringBoot系列——自定义统一异常处理
前言 springboot内置的/error错误页面并不一定适用我们的项目,这时候就需要进行自定义统一异常处理,本文记录springboot进行自定义统一异常处理. 1.使用@ControllerAd ...
- 自定义统一api返回json格式(app后台框架搭建三)
在统一json自定义格式的方式有多种:1,直接重写@reposeBody的实现,2,自定义一个注解,自己去解析对象成为json字符串进行返回 第一种方式,我就不推荐,想弄得的话,可以自己去研究一下源码 ...
- SpringBoot第十一篇:SpringBoot+MyBatis+Thymelaf实现CRUD
作者:追梦1819 原文:https://www.cnblogs.com/yanfei1819/p/10936304.html 版权声明:本文为博主原创文章,转载请附上博文链接! 引言 总结前面几 ...
- MyBatis从入门到精通(十四):在MyBatis中使用类型处理器
最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 本篇博客主要讲解在MyBatis中如何 ...
- SpringBoot+MyBatis Plus对Map中Date格式转换的处理
在 SpringBoot 项目中, 如何统一 JSON 格式化中的日期格式 问题 现在的关系型数据库例如PostgreSQL/MySQL, 都已经对 JSON 类型提供相当丰富的功能, 项目中对于不需 ...
- springboot+mybatis+redis实现分布式缓存
大家都知道springboot项目都是微服务部署,A服务和B服务分开部署,那么它们如何更新或者获取共有模块的缓存数据,或者给A服务做分布式集群负载,如何确保A服务的所有集群都能同步公共模块的缓存数据, ...
- Java逆向工程SpringBoot + Mybatis Generator + MySQL
Java逆向工程SpringBoot+ Mybatis Generator + MySQL Meven pop.xml文件添加引用: <dependency> <groupId> ...
随机推荐
- Canal使用小结
Canal使用小结 之前公司存在mysql数据同步mongo的需求,可以有多种实现方式,比如硬编码,发送消息等.公司选择的是Canal中间件,最近有空来研究下他的使用方式,对于mysql数据变更监听有 ...
- Redis数据结构之HperLogLog
一.HyperLogLog HyperLogLog是用来做基数统计的. 其可以非常省内存的去统计各种计数,比如注册ip数.每日访问IP数.页面实时UV(PV肯定字符串就搞定了).在线用户数等在对准确性 ...
- 2018-2019-2 20165235 《网络对抗技术》 Exp6 信息搜集与漏洞扫描
2018-2019-2 20165235 <网络对抗技术> Exp6 信息搜集与漏洞扫描 1.实践目标 掌握信息搜集的最基础技能与常用工具的使用方法. 2.实践内容 (1)各种搜索技巧的应 ...
- python代码规范与标准库参考
python代码规范与标准库参考 python代码规范参考文献: http://www.runoob.com/w3cnote/google-python-styleguide.html https:/ ...
- JIRA
https://www.jianshu.com/p/8c14b52ce692 JIRA这个工具接触有好几年了,在多个海外项目上都用过这个工具.去年又在项目上深度使用后就有点爱不释手了,回国后也在找机会 ...
- 2019-3-26WinForm窗体间如何传值的几种方法
窗体间传递数据,无论是父窗体操作子窗体,还是子窗体操作符窗体,有以下几种方式: 公共静态变量: 使用共有属性: 使用委托与事件: 通过构造函数把主窗体传递到从窗体中: 一.通过静态变量 特点:传值是双 ...
- centos7 安装 oh my zsh
和在ubuntu 下安装十分相似(基本没区别) 安装zsh yum install zsh 改变系统bash chsh -s /bin/zsh git clone oh my zsh 项目: git@ ...
- javascript 数据类型 -- 检测
一.前言 在上一篇博文中 Javascript 数据类型 -- 分类 中,我们梳理了 javascript 的基本类型和引用类型,并提到了一些冷知识.大概的知识框架如下: 这篇博文就讲一下在写代码的过 ...
- lua_table 学习
lua table (表) Table 的常用操作 local fruits = {“aaa”,”bbb”,”ccc”,”ddd”,”eee”,”fff”,”ggg”} table.co ...
- Class 和 普通构造函数区别
1. Class 在语法上更加贴合面向对象的写法 2. Class在实现继承上更加易读.易理解 3. 更易于写java等后端语言 4.本质还是语法糖,使用prototype