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。
    [sql]  view plain  copy
    1. public enum EStatus {  
    2.   
    3.     disable("0"), enable("1"), deleted("2"),  
    4.     init("10"), start("11"), wait("12"), end("13");  
    5.   
    6. }  
    上面是一个简单的枚举。它包含了7个状态值,当我们没有设置枚举处理器时,mybatis默认使用EnumTypeHandler作为缺省处理器,当需要将枚举存入数据库时它调用的是枚举的name()方法从而获取的是像:disable,enable这样的值存入了数据库,并不是我们想要的(value)value存入数据库,可以看看源码:
    [sql]  view plain  copy
    1. //  
    2. // Source code recreated from a .class file by IntelliJ IDEA  
    3. // (powered by Fernflower decompiler)  
    4. //  
    5.   
    6. package org.apache.ibatis.type;  
    7.   
    8. import java.sql.CallableStatement;  
    9. import java.sql.PreparedStatement;  
    10. import java.sql.ResultSet;  
    11. import java.sql.SQLException;  
    12.   
    13. public class EnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {  
    14.     private Class<E> type;  
    15.   
    16.     public EnumTypeHandler(Class<E> type) {  
    17.         if(type == null) {  
    18.             throw new IllegalArgumentException("Type argument cannot be null");  
    19.         } else {  
    20.             this.type = type;  
    21.         }  
    22.     }  
    23.   
    24.     public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {  
    25.         if(jdbcType == null) {  
    26.             ps.setString(i, parameter.name());  
    27.         } else {  
    28.             ps.setObject(i, parameter.name(), jdbcType.TYPE_CODE);  
    29.         }  
    30.   
    31.     }  
    32.   
    33.     public E getNullableResult(ResultSet rs, String columnName) throws SQLException {  
    34.         String s = rs.getString(columnName);  
    35.         return s == null?null:Enum.valueOf(this.type, s);  
    36.     }  
    37.   
    38.     public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {  
    39.         String s = rs.getString(columnIndex);  
    40.         return s == null?null:Enum.valueOf(this.type, s);  
    41.     }  
    42.   
    43.     public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {  
    44.         String s = cs.getString(columnIndex);  
    45.         return s == null?null:Enum.valueOf(this.type, s);  
    46.     }  
    47. }  
    大家可以看到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,并实现抽象方法。
    [sql]  view plain  copy
    1. package com.cmc.base.handler;  
    2.   
    3. import com.cmc.base.enums.EStatus;  
    4. import com.cmc.base.utils.EnumUtil;  
    5. import org.apache.ibatis.type.BaseTypeHandler;  
    6. import org.apache.ibatis.type.JdbcType;  
    7. import org.apache.ibatis.type.MappedTypes;  
    8.   
    9. import java.sql.CallableStatement;  
    10. import java.sql.PreparedStatement;  
    11. import java.sql.ResultSet;  
    12. import java.sql.SQLException;  
    13.   
    14. public class BaseEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {  
    15.   
    16.     private Class<E> type;  
    17.   
    18.     public BaseEnumTypeHandler() {}  
    19.       
    20.     public BaseEnumTypeHandler(Class<E> type) {  
    21.         this.type = type;  
    22.     }  
    23.   
    24.     @Override  
    25.     public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {  
    26.         if (jdbcType == null) {  
    27.             ps.setString(i, parameter.toString());  
    28.         } else {  
    29.             ps.setObject(i, parameter.name(), jdbcType.TYPE_CODE);  
    30.         }  
    31.     }  
    32.   
    33.     @Override  
    34.     public E getNullableResult(ResultSet rs, String columnName) throws SQLException {  
    35.         return get(rs.getString(columnName));  
    36.     }  
    37.   
    38.     @Override  
    39.     public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {  
    40.         return get(rs.getString(columnIndex));  
    41.     }  
    42.   
    43.     @Override  
    44.     public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {  
    45.         return get(cs.getString(columnIndex));  
    46.     }  
    47.   
    48.     private <E extends Enum<E>> E get(String v) {  
    49.         if (v == nullreturn null;  
    50.         if (StringUtils.isNumeric(v)) {  
    51.             return get(type, Integer.parseInt(v));  
    52.         } else {  
    53.             return Enum.valueOf(type, v);  
    54.         }  
    55.     }  
    56.   
    57.     private <E extends Enum<E>> E get(Class<E> type, int v) {  
    58.         Method method = null;  
    59.         E result = null;  
    60.         try {  
    61.             method = type.getMethod("get"int.class);  
    62.             result = (E)method.invoke(type, v);  
    63.         } catch (NoSuchMethodException e) {  
    64.             result = Enum.valueOf(type, String.valueOf(v));  
    65.             e.printStackTrace();  
    66.         } catch (IllegalAccessException e) {  
    67.             e.printStackTrace();  
    68.         } catch (InvocationTargetException e) {  
    69.             e.printStackTrace();  
    70.         }  
    71.         return result;  
    72.     }  
    73. }  
    在上面的方法setNonNullParameter中大家可以看到我使用parameter.toString()替换了原来的parameter.name()方法,其实做到能统一处理枚举的关键就是这里,因为toString方法是Object的方法,所以不管传入的类型是什么都适用。而从数据库中取出的数据转Bean我使用的是和默认的处理器一致的方法get()。

    所以我们需要在枚举中定义toString和get方法
    [sql]  view plain  copy
    1. package com.cmc.base.enums;  
    2.   
    3. /**  
    4.  * 状态枚举  
    5.  * @author chenmc  
    6.  *  
    7.  */  
    8. public enum EStatus {  
    9.   
    10.     disable("0"), enable("1"), deleted("2"),  
    11.     init("10"), start("11"), wait("12"), end("13");  
    12.   
    13.     private final String value;  
    14.   
    15.     private EStatus(String v) {  
    16.         this.value = v;  
    17.     }  
    18.   
    19.     public String toString() {  
    20.         return this.value;  
    21.     }  
    22.   
    23.     public static EStatus get(int v) {  
    24.         String str = String.valueOf(v);  
    25.         return get(str);  
    26.     }  
    27.   
    28.     public static EStatus get(String str) {  
    29.         for (EStatus e : values()) {  
    30.             if(e.toString().equals(str)) {  
    31.                 return e;  
    32.             }  
    33.         }  
    34.         return null;  
    35.     }  
    36.   
    37.     /*public static String getName(EStatus e, Locale locale) {  
    38.         return I18N.getEnumName(e, locale);  
    39.     }*/  
    40. }  
    大家可看到toString方法返回的是当前对象的value值,这样就是我们想要的了。
    既然方法都写好了,那下面给大家讲讲如何在springboot中使用。
    1.在application.properties或者application.yml中添加
    [sql]  view plain  copy
    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。
    [sql]  view plain  copy
    1. package com.cmc.schedule.model.handler;  
    2.   
    3. import com.cmc.base.enums.EStatus;  
    4. import com.cmc.base.handler.BaseEnumTypeHandler;  
    5. import com.cmc.schedule.model.enums.EFeedback;  
    6. import org.apache.ibatis.type.MappedTypes;  
    7.   
    8. /**  
    9.  * @author chenmc  
    10.  */  
    11.   
    12. @MappedTypes(value = {  EFeedback.class, EStatus.class})  
    13. public class EnumTypeHandler<E extends Enum<E>> extends BaseEnumTypeHandler<E> {  
    14.     public EnumTypeHandler(Class<E> type) {  
    15.         super(type);  
    16.     }  
    17. }  
    这样有多少个enum需要转换就可以在@MappedTypes注解中添加。是不是很方便了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值