• MyBatis(八):Mybatis Java API枚举类型转化的用法


    最近工作中用到了mybatis的Java API方式进行开发,顺便也整理下该功能的用法,接下来会针对基本部分进行学习:

    1)Java API处理一对多、多对一的用法

    2)增、删、改、查的用法;

    3)涉及到类型转化的用法;

    4)批量修改、批量查询、批量新增的用法。

    Mybatis官网给了具体的文档,但是并没有对以上用法具体介绍,因此在这里整理下,以便以后工作用到时,可以参考。

    本章主要使用Mybatis中使用typeHandlers进行对Enum进行转化的用法(本章将结合Spring自动注入《Spring(二十三):Spring自动注入的实现方式》)

    本章将不再对maven项目的引入包,以及配置文件:jdbc.properties、mybatis-config.xml、spring-config.xml重复进行介绍,详情请参考上篇文件构建项目过程:《MyBatis(七):mybatis Java API编程实现增、删、改、查的用法》。

    简介:

    在开发过程中,我们往往会使用到枚举类型,因为使用枚举更可以穷举、开发起来方便、把所有可选值都定义在一起(比起使用数字代表更能避免出现BUG:数字标记规定一旦数字记错或者数字代表意义变化都会导致n多问题:带来bug、不易维护)。
    因此枚举类型的出现给开发带来了不少好处:

    • 1)将一系列的枚举项统一定义到一个enum文件中,统一管理(比起使用数字,导出都是数字和备注);
    • 2)而且增加一个枚举时只要已定义值不变动,不会影响到其他已有枚举项;
    • 3)另外,如果调整枚举值是,只需要修改enum文件中定义枚举项值(使用数字,需要使用到地方一个一个的修改,很容易出错),以及设计到持久化的数据调整。

    什么时候使用枚举?

    • 定义用户的性别:可以定义为male,female,other,nolimit;
    • 标记记录的状态:0-living,1-published,2-deleted。

    在实际开发中,入库时我们可以选择enum的code(int/smallint.tinyint)入库,也可以选择enum的name(varchar)入库。实际上往往code存入库的话,按照int来存储;name入库的话,按照varchar存储。读取的时候再进行转化按照库中的int值转化为enum,或者按照库中的varchar值转化为enum.

    Enum的属性中包含两个字段:

    • 1)name(String类型,存储enum元素的字符串)
    • 2)ordinal(int类型,存储enum元素的顺序,从0开始)

    弄清这点对后边分析一些现象会有帮助。

    Mybatis中默认提供了两种Enum类型的handler:EnumTypeHandler和EnumOrdinalTypeHandler。

    • EnumTypeHandler:将enum按照String存入库,存储为varchar类型;
    • EnumOrdinalTypeHandler:将enum按照Integer处理,存储为int(smallint、tinyint也可以)。

    maven项目公用类如下:

    maven项目中mapper类LogMapper.java

    package com.dx.test.mapper;
    
    import org.apache.ibatis.annotations.InsertProvider;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Options;
    import org.apache.ibatis.annotations.Param;
    import org.apache.ibatis.annotations.Result;
    import org.apache.ibatis.annotations.Results;
    import org.apache.ibatis.annotations.Select;
    
    import com.dx.test.mapper.sqlprovider.LogSqlProvider;
    import com.dx.test.model.Log;
    import com.dx.test.model.enums.ModuleType;
    import com.dx.test.model.enums.OperateType;
    
    @Mapper
    public interface LogMapper {
        /**
         * 入库日志
         * 
         * @param log 待入库实体
         * @return 影响条数
         */
        @Options(useCache = true, flushCache = Options.FlushCachePolicy.TRUE, useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
        @InsertProvider(type = LogSqlProvider.class, method = "insert")
        public int insert(Log log);
    
        /**
         * 根据文章id,查询日志详情
         * 
         * @param id 日志id
         * @return 返回查询到的日志详情
         */
        @Options(useCache = true, flushCache = Options.FlushCachePolicy.FALSE, timeout = 60000)
        @Results(id = "logResult", value = { 
                @Result(property = "id", column = "id", id = true),
                @Result(property = "title", column = "title"), 
                @Result(property = "content", column = "content"),
                @Result(property = "moduleType", column = "module_type", javaType = ModuleType.class),
                @Result(property = "operateType", column = "operate_type", javaType = OperateType.class),
                @Result(property = "dataId", column = "data_id"),
                @Result(property = "createUser", column = "create_user"),
                @Result(property = "createUserId", column = "create_user_id"),
                @Result(property = "createTime", column = "create_time")
                })
        @Select({ "select * from `log` where `id`=#{id}" })
        Log getById(@Param("id") Long id);
    }

    LogMapper生成sql的代理类LogSqlProvider.java

    package com.dx.test.mapper.sqlprovider;
    
    import org.apache.ibatis.jdbc.SQL;
    
    import com.dx.test.model.Log;
    
    public class LogSqlProvider {
        /**
         * 生成插入日志SQL
         * @param log 日志实体
         * @return 插入日志SQL
         * */
        public String insert(Log log) {
            return new SQL() {
                {
                    INSERT_INTO("log");
                    INTO_COLUMNS("title", "module_type", "operate_type","data_id", "content", "create_time","create_user","create_user_id");
                    INTO_VALUES("#{title}", "#{moduleType}", "#{operateType}","#{dataId}", "#{content}", "now()","#{createUser}","#{createUserId}");
                }
            }.toString();
        }
    }

    Log实体类Log.java

    package com.dx.test.model;
    
    import java.util.Date;
    
    import com.dx.test.model.enums.ModuleType;
    import com.dx.test.model.enums.OperateType;
     
    public class Log {
        private Long id; // 自增id
        private String title;// 日志msg
        private ModuleType moduleType;// 日志归属模块
        private OperateType operateType; // 日志操作类型
        private String dataId; // 操作数据id
        private String content; // 日志内容简介
        private Date createTime; // 新增时间
        private String createUser; // 新增人
        private String createUserId; // 新增人id
    
        。。。// getter setter
    
        @Override
        public String toString() {
            return "Log [id=" + id + ", title=" + title + ", moduleType=" + moduleType + ", operateType=" + operateType
                    + ", dataId=" + dataId + ", content=" + content + ", createTime=" + createTime + ", createUser="
                    + createUser + ", createUserId=" + createUserId + "]";
        }
    
    }

    下面展开对Mybatis Java API中使用Enun的用法:

    typeHandlers之EnumTypeHandler(默认)的用法:

    1)db使用varchar存储enum(enum的类型为:String、Integer)的用法

    在不修改mybatis-config.xml和spring-config.xml配置文件(基于上一篇文章而言)的情况下,mybatis内部typeHandlers采用默认配置是:EnumTypeHandler,因此enum对应存储字段需要存储为varchar类型。

    定义enum类型:ModuleType.java/OperateType.java

    操作模块枚举MoudleType.java

    package com.dx.test.model.enums;
    
    public enum ModuleType {
        Unkown("0:Unkown"),
        /**
         * 文章模块
         */
        Article_Module("1:Article_Module"),
        /**
         * 文章分类模块
         **/
        Article_Category_Module("2:Article_Category_Module"),
        /**
         * 配置模块
         */
        Settings_Module("3:Settings_Module");
        
        private String value;
    
        ModuleType(String value) {
            this.value = value;
        }
    
        public String getValue() {
            return this.value;
        }
    }

    操作类型枚举类OperateType.java

    package com.dx.test.model.enums;
    
    public enum OperateType {
        /**
         * 如果0未占位,可能会出现错误。
         * */
        Unkown(0),
        /**
         * 新增
         */
        Create(1),
        /**
         * 修改
         */
        Modify(2),
        /**
         * 删除
         */
        Delete(3),
        /**
         * 查看
         */
        View(4),
        /**
         * 作废
         */
        UnUsed(5);
    
        private int value;
    
        OperateType(int value) {
            this.value = value;
        }
    
        public int getValue() {
            return this.value;
        }
    }

    mydb中新建log表:

    CREATE TABLE `log` (
      `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '自增id',
      `title` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '日志标题',
      `content` text CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT '日志内容',
      `module_type` varchar(32) NOT NULL COMMENT '记录模块类型',
      `operate_type` varchar(32) NOT NULL COMMENT '操作类型',
      `data_id` varchar(64) NOT NULL COMMENT '操作数据记录id',
      `create_time` datetime NOT NULL COMMENT '日志记录时间',
      `create_user` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '操作人',
      `create_user_id` varchar(64) NOT NULL COMMENT '操作人id',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8

    测试类com.dx.test.LogTest.java:

    package com.dx.test;
    
    import java.util.Date;
    
    import org.junit.After;
    import org.junit.Assert;
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    import com.dx.test.mapper.LogMapper;
    import com.dx.test.model.Log;
    import com.dx.test.model.enums.ModuleType;
    import com.dx.test.model.enums.OperateType;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration({ "classpath:spring-config.xml" })
    public class LogTest {
        @Autowired
        private LogMapper logMapper;
        
        @Test
        public void testInsert() {
            Log log=new Log();
            log.setTitle("test log title");
            log.setContent("test log content");
            log.setModuleType(ModuleType.Article_Module);
            log.setOperateType(OperateType.Modify);
            log.setDataId(String.valueOf(1L));
            log.setCreateTime(new Date());
            log.setCreateUser("create user");
            log.setCreateUserId("user-0001000");
            
            int result=this.logMapper.insert(log);
            Assert.assertEquals(result, 1);
        }
        
        @Test
        public void testGetById() {
            Long logId=1L;
            Log log=this.logMapper.getById(logId);
            System.out.println(log);
            Long dbLogId=(log!=null?log.getId():0L);
            Assert.assertEquals(dbLogId, logId);
        }
    }

    执行testInsert()测试函数的执行结果如下:

    Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
    Creating a new SqlSession
    SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1e4d3ce5] was not registered for synchronization because synchronization is not active
    JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@1b8a29df] will not be managed by Spring
    ==>  Preparing: INSERT INTO log (title, module_type, operate_type, data_id, content, create_time, create_user, create_user_id) VALUES (?, ?, ?, ?, ?, now(), ?, ?) 
    ==> Parameters: test log title(String), Article_Module(String), Modify(String), 1(String), test log content(String), create user(String), user-0001000(String)
    <==    Updates: 1
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1e4d3ce5]

    执行testSelectById()测试函数的执行结果如下:

    Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
    Creating a new SqlSession
    SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@404bbcbd] was not registered for synchronization because synchronization is not active
    JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@275bf9b3] will not be managed by Spring
    ==>  Preparing: select * from `log` where `id`=? 
    ==> Parameters: 1(Long)
    <==    Columns: id, title, content, module_type, operate_type, data_id, create_time, create_user, create_user_id
    <==        Row: 1, test log title, <<BLOB>>, Article_Module, Modify, 1, 2019-11-18 21:00:08, create user, user-0001000
    <==      Total: 1
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@404bbcbd]
    Log [id=1, title=test log title, moduleType=Article_Module, operateType=Modify, dataId=1, content=test log content, createTime=Mon Nov 18 21:00:08 CST 2019, createUser=create user, createUserId=user-0001000]

    此时查询数据库中数据如下:

    上边的执行结果可以总结出:在typeHandlers为EnumTypeHandler时,enum中存储到数据的是Enum.name属性,而不是enum定义的value值,也不是Enum.ordinal属性。

    2)db使用int存储enum(enum的类型为:String、Integer)的用法

    测试存储enum字段为int(4):

    修改测试log表的module_type、operate_type为int(4):

    truncate table `log`;
    alter table `log` modify column `module_type` int(4) not null comment '模块类型'; alter table `log` modify column `operate_type` int(4) not null comment '操作类型';

    此时执行测试类com.dx.test.LogTest.java

    执行testInsert(),抛出以下异常:

    org.springframework.jdbc.UncategorizedSQLException: 
    ### Error updating database.  Cause: java.sql.SQLException: Incorrect integer value: 'Article_Module' for column 'module_type' at row 1
    ### The error may involve com.dx.test.mapper.LogMapper.insert-Inline
    ### The error occurred while setting parameters
    ### SQL: INSERT INTO log  (title, module_type, operate_type, data_id, content, create_time, create_user, create_user_id) VALUES (?, ?, ?, ?, ?, now(), ?, ?)
    ### Cause: java.sql.SQLException: Incorrect integer value: 'Article_Module' for column 'module_type' at row 1
    ; uncategorized SQLException; SQL state [HY000]; error code [1366]; Incorrect integer value: 'Article_Module' for column 'module_type' at row 1; 
    nested exception is java.sql.SQLException: Incorrect integer value: 'Article_Module' for column 'module_type' at row 1 at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:89) at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81) at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81) at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:88) at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:440) at com.sun.proxy.$Proxy24.insert(Unknown Source) at org.mybatis.spring.SqlSessionTemplate.insert(SqlSessionTemplate.java:271) at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:58) at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59) at com.sun.proxy.$Proxy36.insert(Unknown Source) at com.dx.test.LogTest.testInsert(LogTest.java:37) 。。。

    执行testGetById()测试函数,需要先插入一条,否则空数据测试无意义:

    insert into log 
    (title,content,module_type,operate_type,data_id,create_time,create_user,create_user_id)
    values('test title','test content',2,2,'1',now(),'test create user','test create user id');

    此时执行抛出以下异常:

    org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.executor.result.ResultMapException: 
    Error attempting to get column 'module_type' from result set.
    Cause: java.lang.IllegalArgumentException: No enum constant com.dx.test.model.enums.ModuleType.2 at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:92) at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:440) at com.sun.proxy.$Proxy24.selectOne(Unknown Source) at org.mybatis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate.java:159) at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:83) at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59) at com.sun.proxy.$Proxy36.getById(Unknown Source) at com.dx.test.LogTest.testGetById(LogTest.java:44) 。。。

    测试我们可以发现:当typeHandlers的值为EnumTypeHandler时,数据存储类型必须是字符型(varchar等)不能是整数(smallint、int(4/11/20)、tinyint)。

    总结下,上边我们对mybatis默认对enum进行转化处理EnumTypeHandler的使用得出经验:

    • 1)EnumTypeHandler是属于mybatis的默认enum转化器,在对enum进行转化时,不需要在mybatis-config.xml中添加配置,也不需要在spring-config.xml中进行添加配置;
    • 2)EnumTypeHandler在处理数据时,可以需要数据存储字段类型为varchar类型,不能是int类型,否则会抛出异常:java.sql.SQLException
    • 3)EnumTypeHandler处理的enum是存储到数据中的是enum项的String字段,并不存储其code值,其code可以为String/Integer类型。

    为什么是这样子呢?其实我们可以从EnumTypeHandler的源代码去分析问题:

    EnumTypeHandler源码分析:

    package org.apache.ibatis.type;
    
    import java.sql.CallableStatement;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    /**
     * @author Clinton Begin
     */
    public class EnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
    
      private final Class<E> type;
    
      public EnumTypeHandler(Class<E> type) {
        if (type == null) {
          throw new IllegalArgumentException("Type argument cannot be null");
        }
        this.type = type;
      }
    
      @Override
      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); // see r3589
        }
      }
    
      @Override
      public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String s = rs.getString(columnName);
        return s == null ? null : Enum.valueOf(type, s);
      }
    
      @Override
      public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String s = rs.getString(columnIndex);
        return s == null ? null : Enum.valueOf(type, s);
      }
    
      @Override
      public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String s = cs.getString(columnIndex);
        return s == null ? null : Enum.valueOf(type, s);
      }
    }
    View Code

    1)数据存储时,在设置数据库操作参数时,会调用:

      @Override
      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); // see r3589
        }
      }

    方法,从该方法就可以看出,数据库中存储的是enum的name值,而enum又是String类型,这也说明了为什么我们库中存储的是enum中enum项的字符串,而不是其值,另外这也说明了在做数据存储时,必须是使用字符类型的数据库类型来做存储(比如:varchar)。

    2)数据获取转化为Enum的过程会调用getNullableResult方法:对数据库中的值进行判断,如果为空则返回null,否则使用 Enum.valueOf(type, s)函数将‘数据库中存储的值’转化为对应的enum类型。

    3)其实我们会typeHandlers可以配置的值还包含很多:

    org.apache.ibatis.type.TypeHandler<T>
    -->org.apache.ibatis.type.BaseTypeHandler.BaseTypeHandler()
    ----> org.apache.ibatis.type.ArrayTypeHandler.ArrayTypeHandler()
    ----> org.apache.ibatis.type.BigDecimalTypeHandler
    ----> org.apache.ibatis.type.BigIntegerTypeHandler
    ----> org.apache.ibatis.type.BlobByteObjectArrayTypeHandler
    ----> org.apache.ibatis.type.BlobInputStreamTypeHandler
    ----> org.apache.ibatis.type.BlobTypeHandler
    ----> org.apache.ibatis.type.BooleanTypeHandler
    ----> org.apache.ibatis.type.ByteArrayTypeHandler
    ----> org.apache.ibatis.type.ByteObjectArrayTypeHandler
    ----> org.apache.ibatis.type.ByteTypeHandler
    ----> org.apache.ibatis.type.CharacterTypeHandler
    ----> org.apache.ibatis.type.ClobReaderTypeHandler
    ----> org.apache.ibatis.type.ClobTypeHandler
    ----> org.apache.ibatis.type.DateOnlyTypeHandler
    ----> org.apache.ibatis.type.DateTypeHandler
    ----> org.apache.ibatis.type.DoubleTypeHandler
    ----> org.apache.ibatis.type.EnumOrdinalTypeHandler.EnumOrdinalTypeHandler(Class<E>)
    ----> org.apache.ibatis.type.EnumTypeHandler.EnumTypeHandler(Class<E>)
    ----> org.apache.ibatis.type.FloatTypeHandler
    ----> org.apache.ibatis.type.InstantTypeHandler
    ----> org.apache.ibatis.type.IntegerTypeHandler
    ----> org.apache.ibatis.type.JapaneseDateTypeHandler
    ----> org.apache.ibatis.type.LocalDateTimeTypeHandler
    ----> org.apache.ibatis.type.LocalDateTypeHandler
    ----> org.apache.ibatis.type.LocalTimeTypeHandler
    ----> org.apache.ibatis.type.LongTypeHandler
    ----> org.apache.ibatis.type.MonthTypeHandler
    ----> org.apache.ibatis.type.NClobTypeHandler
    ----> org.apache.ibatis.type.NStringTypeHandler
    ----> org.apache.ibatis.type.ObjectTypeHandler
    ----> org.apache.ibatis.type.OffsetDateTimeTypeHandler
    ----> org.apache.ibatis.type.OffsetTimeTypeHandler
    ----> org.apache.ibatis.type.ShortTypeHandler
    ----> org.apache.ibatis.type.SqlDateTypeHandler
    ----> org.apache.ibatis.type.SqlTimestampTypeHandler
    ----> org.apache.ibatis.type.SqlTimeTypeHandler
    ----> org.apache.ibatis.type.StringTypeHandler
    ----> org.apache.ibatis.type.TimeOnlyTypeHandler
    ----> org.apache.ibatis.type.UnknownTypeHandler.UnknownTypeHandler(TypeHandlerRegistry)
    ----> org.apache.ibatis.type.YearMonthTypeHandler
    ----> org.apache.ibatis.type.YearTypeHandler
    ----> org.apache.ibatis.type.ZonedDateTimeTypeHandler

    这些类都维护在org.apache.ibatis.type.TypeHandlerRegistry:

    /**
     *    Copyright 2009-2018 the original author or authors.
     *
     *    Licensed under the Apache License, Version 2.0 (the "License");
     *    you may not use this file except in compliance with the License.
     *    You may obtain a copy of the License at
     *
     *       http://www.apache.org/licenses/LICENSE-2.0
     *
     *    Unless required by applicable law or agreed to in writing, software
     *    distributed under the License is distributed on an "AS IS" BASIS,
     *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     *    See the License for the specific language governing permissions and
     *    limitations under the License.
     */
    package org.apache.ibatis.type;
    
    import java.io.InputStream;
    import java.io.Reader;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Modifier;
    import java.lang.reflect.Type;
    import java.math.BigDecimal;
    import java.math.BigInteger;
    import java.time.Instant;
    import java.time.LocalDate;
    import java.time.LocalDateTime;
    import java.time.LocalTime;
    import java.time.Month;
    import java.time.OffsetDateTime;
    import java.time.OffsetTime;
    import java.time.Year;
    import java.time.YearMonth;
    import java.time.ZonedDateTime;
    import java.time.chrono.JapaneseDate;
    import java.util.Collection;
    import java.util.Collections;
    import java.util.Date;
    import java.util.EnumMap;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.Set;
    import java.util.concurrent.ConcurrentHashMap;
    
    import org.apache.ibatis.binding.MapperMethod.ParamMap;
    import org.apache.ibatis.io.ResolverUtil;
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.reflection.Jdk;
    
    /**
     * @author Clinton Begin
     * @author Kazuki Shimizu
     */
    public final class TypeHandlerRegistry {
    
      private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
      private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();
      private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);
      private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();
    
      private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
    
      private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
    
      public TypeHandlerRegistry() {
        register(Boolean.class, new BooleanTypeHandler());
        register(boolean.class, new BooleanTypeHandler());
        register(JdbcType.BOOLEAN, new BooleanTypeHandler());
        register(JdbcType.BIT, new BooleanTypeHandler());
    
        register(Byte.class, new ByteTypeHandler());
        register(byte.class, new ByteTypeHandler());
        register(JdbcType.TINYINT, new ByteTypeHandler());
    
        register(Short.class, new ShortTypeHandler());
        register(short.class, new ShortTypeHandler());
        register(JdbcType.SMALLINT, new ShortTypeHandler());
    
        register(Integer.class, new IntegerTypeHandler());
        register(int.class, new IntegerTypeHandler());
        register(JdbcType.INTEGER, new IntegerTypeHandler());
    
        register(Long.class, new LongTypeHandler());
        register(long.class, new LongTypeHandler());
    
        register(Float.class, new FloatTypeHandler());
        register(float.class, new FloatTypeHandler());
        register(JdbcType.FLOAT, new FloatTypeHandler());
    
        register(Double.class, new DoubleTypeHandler());
        register(double.class, new DoubleTypeHandler());
        register(JdbcType.DOUBLE, new DoubleTypeHandler());
    
        register(Reader.class, new ClobReaderTypeHandler());
        register(String.class, new StringTypeHandler());
        register(String.class, JdbcType.CHAR, new StringTypeHandler());
        register(String.class, JdbcType.CLOB, new ClobTypeHandler());
        register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
        register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
        register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
        register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
        register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
        register(JdbcType.CHAR, new StringTypeHandler());
        register(JdbcType.VARCHAR, new StringTypeHandler());
        register(JdbcType.CLOB, new ClobTypeHandler());
        register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
        register(JdbcType.NVARCHAR, new NStringTypeHandler());
        register(JdbcType.NCHAR, new NStringTypeHandler());
        register(JdbcType.NCLOB, new NClobTypeHandler());
    
        register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
        register(JdbcType.ARRAY, new ArrayTypeHandler());
    
        register(BigInteger.class, new BigIntegerTypeHandler());
        register(JdbcType.BIGINT, new LongTypeHandler());
    
        register(BigDecimal.class, new BigDecimalTypeHandler());
        register(JdbcType.REAL, new BigDecimalTypeHandler());
        register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
        register(JdbcType.NUMERIC, new BigDecimalTypeHandler());
    
        register(InputStream.class, new BlobInputStreamTypeHandler());
        register(Byte[].class, new ByteObjectArrayTypeHandler());
        register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
        register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
        register(byte[].class, new ByteArrayTypeHandler());
        register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
        register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
        register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
        register(JdbcType.BLOB, new BlobTypeHandler());
    
        register(Object.class, UNKNOWN_TYPE_HANDLER);
        register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
        register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
    
        register(Date.class, new DateTypeHandler());
        register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
        register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
        register(JdbcType.TIMESTAMP, new DateTypeHandler());
        register(JdbcType.DATE, new DateOnlyTypeHandler());
        register(JdbcType.TIME, new TimeOnlyTypeHandler());
    
        register(java.sql.Date.class, new SqlDateTypeHandler());
        register(java.sql.Time.class, new SqlTimeTypeHandler());
        register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());
    
        // mybatis-typehandlers-jsr310
        if (Jdk.dateAndTimeApiExists) {
          this.register(Instant.class, InstantTypeHandler.class);
          this.register(LocalDateTime.class, LocalDateTimeTypeHandler.class);
          this.register(LocalDate.class, LocalDateTypeHandler.class);
          this.register(LocalTime.class, LocalTimeTypeHandler.class);
          this.register(OffsetDateTime.class, OffsetDateTimeTypeHandler.class);
          this.register(OffsetTime.class, OffsetTimeTypeHandler.class);
          this.register(ZonedDateTime.class, ZonedDateTimeTypeHandler.class);
          this.register(Month.class, MonthTypeHandler.class);
          this.register(Year.class, YearTypeHandler.class);
          this.register(YearMonth.class, YearMonthTypeHandler.class);
          this.register(JapaneseDate.class, JapaneseDateTypeHandler.class);
        }
    
        // issue #273
        register(Character.class, new CharacterTypeHandler());
        register(char.class, new CharacterTypeHandler());
      }
    
      /**
       * Set a default {@link TypeHandler} class for {@link Enum}.
       * A default {@link TypeHandler} is {@link org.apache.ibatis.type.EnumTypeHandler}.
       * @param typeHandler a type handler class for {@link Enum}
       * @since 3.4.5
       */
      public void setDefaultEnumTypeHandler(Class<? extends TypeHandler> typeHandler) {
        this.defaultEnumTypeHandler = typeHandler;
      }
    
      public boolean hasTypeHandler(Class<?> javaType) {
        return hasTypeHandler(javaType, null);
      }
    
      public boolean hasTypeHandler(TypeReference<?> javaTypeReference) {
        return hasTypeHandler(javaTypeReference, null);
      }
    
      public boolean hasTypeHandler(Class<?> javaType, JdbcType jdbcType) {
        return javaType != null && getTypeHandler((Type) javaType, jdbcType) != null;
      }
    
      public boolean hasTypeHandler(TypeReference<?> javaTypeReference, JdbcType jdbcType) {
        return javaTypeReference != null && getTypeHandler(javaTypeReference, jdbcType) != null;
      }
    
      public TypeHandler<?> getMappingTypeHandler(Class<? extends TypeHandler<?>> handlerType) {
        return ALL_TYPE_HANDLERS_MAP.get(handlerType);
      }
    
      public <T> TypeHandler<T> getTypeHandler(Class<T> type) {
        return getTypeHandler((Type) type, null);
      }
    
      public <T> TypeHandler<T> getTypeHandler(TypeReference<T> javaTypeReference) {
        return getTypeHandler(javaTypeReference, null);
      }
    
      public TypeHandler<?> getTypeHandler(JdbcType jdbcType) {
        return JDBC_TYPE_HANDLER_MAP.get(jdbcType);
      }
    
      public <T> TypeHandler<T> getTypeHandler(Class<T> type, JdbcType jdbcType) {
        return getTypeHandler((Type) type, jdbcType);
      }
    
      public <T> TypeHandler<T> getTypeHandler(TypeReference<T> javaTypeReference, JdbcType jdbcType) {
        return getTypeHandler(javaTypeReference.getRawType(), jdbcType);
      }
    
      @SuppressWarnings("unchecked")
      private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
        if (ParamMap.class.equals(type)) {
          return null;
        }
        Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
        TypeHandler<?> handler = null;
        if (jdbcHandlerMap != null) {
          handler = jdbcHandlerMap.get(jdbcType);
          if (handler == null) {
            handler = jdbcHandlerMap.get(null);
          }
          if (handler == null) {
            // #591
            handler = pickSoleHandler(jdbcHandlerMap);
          }
        }
        // type drives generics here
        return (TypeHandler<T>) handler;
      }
    
      private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
        Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(type);
        if (NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap)) {
          return null;
        }
        if (jdbcHandlerMap == null && type instanceof Class) {
          Class<?> clazz = (Class<?>) type;
          if (clazz.isEnum()) {
            jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(clazz, clazz);
            if (jdbcHandlerMap == null) {
              register(clazz, getInstance(clazz, defaultEnumTypeHandler));
              return TYPE_HANDLER_MAP.get(clazz);
            }
          } else {
            jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
          }
        }
        TYPE_HANDLER_MAP.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);
        return jdbcHandlerMap;
      }
    
      private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForEnumInterfaces(Class<?> clazz, Class<?> enumClazz) {
        for (Class<?> iface : clazz.getInterfaces()) {
          Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(iface);
          if (jdbcHandlerMap == null) {
            jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(iface, enumClazz);
          }
          if (jdbcHandlerMap != null) {
            // Found a type handler regsiterd to a super interface
            HashMap<JdbcType, TypeHandler<?>> newMap = new HashMap<JdbcType, TypeHandler<?>>();
            for (Entry<JdbcType, TypeHandler<?>> entry : jdbcHandlerMap.entrySet()) {
              // Create a type handler instance with enum type as a constructor arg
              newMap.put(entry.getKey(), getInstance(enumClazz, entry.getValue().getClass()));
            }
            return newMap;
          }
        }
        return null;
      }
    
      private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForSuperclass(Class<?> clazz) {
        Class<?> superclass =  clazz.getSuperclass();
        if (superclass == null || Object.class.equals(superclass)) {
          return null;
        }
        Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(superclass);
        if (jdbcHandlerMap != null) {
          return jdbcHandlerMap;
        } else {
          return getJdbcHandlerMapForSuperclass(superclass);
        }
      }
    
      private TypeHandler<?> pickSoleHandler(Map<JdbcType, TypeHandler<?>> jdbcHandlerMap) {
        TypeHandler<?> soleHandler = null;
        for (TypeHandler<?> handler : jdbcHandlerMap.values()) {
          if (soleHandler == null) {
            soleHandler = handler;
          } else if (!handler.getClass().equals(soleHandler.getClass())) {
            // More than one type handlers registered.
            return null;
          }
        }
        return soleHandler;
      }
    
      public TypeHandler<Object> getUnknownTypeHandler() {
        return UNKNOWN_TYPE_HANDLER;
      }
    
      public void register(JdbcType jdbcType, TypeHandler<?> handler) {
        JDBC_TYPE_HANDLER_MAP.put(jdbcType, handler);
      }
    
      //
      // REGISTER INSTANCE
      //
    
      // Only handler
    
      @SuppressWarnings("unchecked")
      public <T> void register(TypeHandler<T> typeHandler) {
        boolean mappedTypeFound = false;
        MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
        if (mappedTypes != null) {
          for (Class<?> handledType : mappedTypes.value()) {
            register(handledType, typeHandler);
            mappedTypeFound = true;
          }
        }
        // @since 3.1.0 - try to auto-discover the mapped type
        if (!mappedTypeFound && typeHandler instanceof TypeReference) {
          try {
            TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
            register(typeReference.getRawType(), typeHandler);
            mappedTypeFound = true;
          } catch (Throwable t) {
            // maybe users define the TypeReference with a different type and are not assignable, so just ignore it
          }
        }
        if (!mappedTypeFound) {
          register((Class<T>) null, typeHandler);
        }
      }
    
      // java type + handler
    
      public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) {
        register((Type) javaType, typeHandler);
      }
    
      private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
        MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
        if (mappedJdbcTypes != null) {
          for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
            register(javaType, handledJdbcType, typeHandler);
          }
          if (mappedJdbcTypes.includeNullJdbcType()) {
            register(javaType, null, typeHandler);
          }
        } else {
          register(javaType, null, typeHandler);
        }
      }
    
      public <T> void register(TypeReference<T> javaTypeReference, TypeHandler<? extends T> handler) {
        register(javaTypeReference.getRawType(), handler);
      }
    
      // java type + jdbc type + handler
    
      public <T> void register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler) {
        register((Type) type, jdbcType, handler);
      }
    
      private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
        if (javaType != null) {
          Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
          if (map == null || map == NULL_TYPE_HANDLER_MAP) {
            map = new HashMap<JdbcType, TypeHandler<?>>();
            TYPE_HANDLER_MAP.put(javaType, map);
          }
          map.put(jdbcType, handler);
        }
        ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
      }
    
      //
      // REGISTER CLASS
      //
    
      // Only handler type
    
      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));
        }
      }
    
      // java type + handler type
    
      public void register(String javaTypeClassName, String typeHandlerClassName) throws ClassNotFoundException {
        register(Resources.classForName(javaTypeClassName), Resources.classForName(typeHandlerClassName));
      }
    
      public void register(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
        register(javaTypeClass, getInstance(javaTypeClass, typeHandlerClass));
      }
    
      // java type + jdbc type + handler type
    
      public void register(Class<?> javaTypeClass, JdbcType jdbcType, Class<?> typeHandlerClass) {
        register(javaTypeClass, jdbcType, getInstance(javaTypeClass, typeHandlerClass));
      }
    
      // Construct a handler (used also from Builders)
    
      @SuppressWarnings("unchecked")
      public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
        if (javaTypeClass != null) {
          try {
            Constructor<?> c = typeHandlerClass.getConstructor(Class.class);
            return (TypeHandler<T>) c.newInstance(javaTypeClass);
          } catch (NoSuchMethodException ignored) {
            // ignored
          } catch (Exception e) {
            throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
          }
        }
        try {
          Constructor<?> c = typeHandlerClass.getConstructor();
          return (TypeHandler<T>) c.newInstance();
        } catch (Exception e) {
          throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e);
        }
      }
    
      // scan
    
      public void register(String packageName) {
        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
        resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
        Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
        for (Class<?> type : handlerSet) {
          //Ignore inner classes and interfaces (including package-info.java) and abstract classes
          if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
            register(type);
          }
        }
      }
      
      // get information
      
      /**
       * @since 3.2.2
       */
      public Collection<TypeHandler<?>> getTypeHandlers() {
        return Collections.unmodifiableCollection(ALL_TYPE_HANDLERS_MAP.values());
      }
      
    }
    View Code

    具体在mybaits何时加载和使用TypeHandlerRegistry,等后边讲解Mybatis整体运行原理时再讲解。

    typeHandlers之EnumOrdinalTypeHandler的用法:

    使用该类型时,需要在mybatis-config.xml中添加<typeHandlers>标签,内部指定每个枚举类使用的typeHandler:

        <!-- 
        元素类型为 "configuration" 的内容必须匹配 "
        (properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,
          plugins?,environments?,databaseIdProvider?,mappers?)"。
       --> <typeHandlers> <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.dx.test.model.enums.ModuleType"/> <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.dx.test.model.enums.OperateType"/> </typeHandlers>

    在上边使用EnumTypeHandler时,不需要配置<typeHandlers>的原因是,typeHandlers默认使用的就是EnunTypeHandler。

    我们可以发现EnumOrdinalTypeHandler名称,其中包含了一个Ordinal,这个属性在上边提过Enum也包含一个ordinal属性,而且Enum.ordinal的属性类型为int,可以猜测如果typeHandlers配置为EnumOrdinalTypeHandler时,数据库中存储enum的类型需要是int(也可以是smallint、tinyint)。

    接下来我们先分析EnumOrdinalTypeHandler的源码:

    /**
     *    Copyright 2009-2017 the original author or authors.
     *
     *    Licensed under the Apache License, Version 2.0 (the "License");
     *    you may not use this file except in compliance with the License.
     *    You may obtain a copy of the License at
     *
     *       http://www.apache.org/licenses/LICENSE-2.0
     *
     *    Unless required by applicable law or agreed to in writing, software
     *    distributed under the License is distributed on an "AS IS" BASIS,
     *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     *    See the License for the specific language governing permissions and
     *    limitations under the License.
     */
    package org.apache.ibatis.type;
    
    import java.sql.CallableStatement;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    /**
     * @author Clinton Begin
     */
    public class EnumOrdinalTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
    
      private final Class<E> type;
      private final E[] enums;
    
      public EnumOrdinalTypeHandler(Class<E> type) {
        if (type == null) {
          throw new IllegalArgumentException("Type argument cannot be null");
        }
        this.type = type;
        this.enums = type.getEnumConstants();
        if (this.enums == null) {
          throw new IllegalArgumentException(type.getSimpleName() + " does not represent an enum type.");
        }
      }
    
      @Override
      public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
        ps.setInt(i, parameter.ordinal());
      }
    
      @Override
      public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        int i = rs.getInt(columnName);
        if (rs.wasNull()) {
          return null;
        } else {
          try {
            return enums[i];
          } catch (Exception ex) {
            throw new IllegalArgumentException("Cannot convert " + i + " to " + type.getSimpleName() + " by ordinal value.", ex);
          }
        }
      }
    
      @Override
      public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        int i = rs.getInt(columnIndex);
        if (rs.wasNull()) {
          return null;
        } else {
          try {
            return enums[i];
          } catch (Exception ex) {
            throw new IllegalArgumentException("Cannot convert " + i + " to " + type.getSimpleName() + " by ordinal value.", ex);
          }
        }
      }
    
      @Override
      public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        int i = cs.getInt(columnIndex);
        if (cs.wasNull()) {
          return null;
        } else {
          try {
            return enums[i];
          } catch (Exception ex) {
            throw new IllegalArgumentException("Cannot convert " + i + " to " + type.getSimpleName() + " by ordinal value.", ex);
          }
        }
      }
      
    }
    View Code

    源码分析会发现它还是主要提供了两个函数:

    1)写入数据库中数据时,设置参数setNonNullParameter(...)函数:

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

    从该函数可以看出:

    1.1)数据库中数据必须是int(也可以使smallint、tinyint)类型,因为parameter.ordinal()就是获取Enum.ordinal属性。

    1.2)数据库中存储的是enum的ordinal值,而不是Module_Type或Operate_Type的value属性。

    2)读取数据库中数据时,对数据库中值进行enum转化getNullableResult(...)函数:

      @Override
      public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        int i = cs.getInt(columnIndex);
        if (cs.wasNull()) {
          return null;
        } else {
          try {
            return enums[i];
          } catch (Exception ex) {
            throw new IllegalArgumentException("Cannot convert " + i + " to " + type.getSimpleName() + " by ordinal value.", ex);
          }
        }
      }

    从数据库中读取到数据,是一个int值,然后根据该值作为索引,索引enum元素集合的enum项。

    1)db使用int存储enum(enum的类型为:Integer、String)的用法

    mydb中log标创建语句:

    CREATE TABLE `log` (
      `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '自增id',
      `title` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '日志标题',
      `content` text CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT '日志内容',
      `module_type` int(4) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '模块类型',
      `operate_type` int(4) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '操作类型',
      `data_id` varchar(64) NOT NULL COMMENT '操作数据记录id',
      `create_time` datetime NOT NULL COMMENT '日志记录时间',
      `create_user` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '操作人',
      `create_user_id` varchar(64) NOT NULL COMMENT '操作人id',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8

    ModuleType.java

    package com.dx.test.model.enums;
    
    public enum ModuleType {
        Unkown(0),
        /**
         * 文章模块
         */
        Article_Module(1),
        /**
         * 文章分类模块
         **/
        Article_Category_Module(2),
        /**
         * 配置模块
         */
        Settings_Module(3);
        
        private int value;
    
        ModuleType(int value) {
            this.value = value;
        }
    
        public int getValue() {
            return this.value;
        }
    }

    OperateType.java

    package com.dx.test.model.enums;
    
    public enum OperateType {
        /**
         * 如果0未占位,可能会出现错误。
         * */
        Unkown("0:Unkown"),
        /**
         * 新增
         */
        Create("1:Create"),
        /**
         * 修改
         */
        Modify("2:Modify"),
        /**
         * 删除
         */
        Delete("3:Delete"),
        /**
         * 查看
         */
        View("4:View"),
        /**
         * 作废
         */
        UnUsed("5:UnUsed");
    
        private String value;
    
        OperateType(String value) {
            this.value = value;
        }
    
        public String getValue() {
            return this.value;
        }
    }

    执行com.dx.test.LogTest.java测试类:

    执行testInsert()测试方法:

    Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
    Creating a new SqlSession
    SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@704deff2] was not registered for synchronization because synchronization is not active
    JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@12a94400] will not be managed by Spring
    ==>  Preparing: INSERT INTO log (title, module_type, operate_type, data_id, content, create_time, create_user, create_user_id) VALUES (?, ?, ?, ?, ?, now(), ?, ?) 
    ==> Parameters: test log title(String), 1(Integer), 2(Integer), 1(String), test log content(String), create user(String), user-0001000(String)
    <==    Updates: 1
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@704deff2]

    此时数据库中数据已经成功插入:

    执行testGetById()测试类:

    Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
    Creating a new SqlSession
    SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1acaf3d] was not registered for synchronization because synchronization is not active
    JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@4fbe37eb] will not be managed by Spring
    ==>  Preparing: select * from `log` where `id`=? 
    ==> Parameters: 1(Long)
    <==    Columns: id, title, content, module_type, operate_type, data_id, create_time, create_user, create_user_id
    <==        Row: 1, test log title, <<BLOB>>, 1, 2, 1, 2019-11-18 21:52:34, create user, user-0001000
    <==      Total: 1
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1acaf3d]
    Log [id=1, title=test log title, moduleType=Article_Module, operateType=Modify, dataId=1, content=test log content, createTime=Mon Nov 18 21:52:34 CST 2019, createUser=create user, createUserId=user-0001000]

    通过该测试可以发现:

    1)mybatis中当使用typeHandlers为EnumOrdinalTypeHandler时,存储只是Enum.ordinal,而不是Enum中定义的value类型,而且与Enum的value类型无关:不管value是Integer,还是String都不关心;

    2)在使用时,需要注意此时存储的是Enum的ordinal,是enum项在enum中定义的顺序,从0开始。因此一旦项目定义好,如果修改enum中enum项顺序,会导致查询结果错误问题。

    2)db使用varchar存储enum(enum的类型为:Integer、String)的用法

    修改mydb下log的module_type、operate_type类型为int(4),并插入一条数据作为测试使用:

    truncate table `log`;
    alter table `log` modify column `module_type` varchar(32) not null comment '模块类型';
    alter table `log` modify column `operate_type` varchar(32) not null comment '操作类型';

    执行com.dx.test.LogTest.java测试类:

    执行testInsert()测试方法:

    Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
    Creating a new SqlSession
    SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@704deff2] was not registered for synchronization because synchronization is not active
    JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@12a94400] will not be managed by Spring
    ==>  Preparing: INSERT INTO log (title, module_type, operate_type, data_id, content, create_time, create_user, create_user_id) VALUES (?, ?, ?, ?, ?, now(), ?, ?) 
    ==> Parameters: test log title(String), 1(Integer), 2(Integer), 1(String), test log content(String), create user(String), user-0001000(String)
    <==    Updates: 1
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@704deff2]

    此时查询数据库结果:

    执行testGetById()测试方法:

    Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
    Creating a new SqlSession
    SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1acaf3d] was not registered for synchronization because synchronization is not active
    JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@4fbe37eb] will not be managed by Spring
    ==>  Preparing: select * from `log` where `id`=? 
    ==> Parameters: 1(Long)
    <==    Columns: id, title, content, module_type, operate_type, data_id, create_time, create_user, create_user_id
    <==        Row: 1, test log title, <<BLOB>>, 1, 2, 1, 2019-11-18 22:06:49, create user, user-0001000
    <==      Total: 1
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1acaf3d]
    Log [id=1, title=test log title, moduleType=Article_Module, operateType=Modify, dataId=1, content=test log content, createTime=Mon Nov 18 22:06:49 CST 2019, createUser=create user, createUserId=user-0001000]

    从该测试结果结合“1)db使用int存储enum(enum的类型为:Integer、String)的用法:”可以得出以下结论:

    1)typeHandlers使用EnumOrdinalTypeHandler时,不管数据库总存储字段是int(smallint/tinyint),还是varchar类型,都可以正常使用,数据库中存储的值为Enum.ordinal;

    2)需要注意:一旦数据持久化后,不可以轻易调整enum类型中enum项的顺序,因为数据库总存储时enum的ordinal属性,调整后查询出的数据结果会变动。

  • 相关阅读:
    C#设计模式(8)——桥接模式(Bridge Pattern)
    我的微软最有价值专家(Microsoft MVP)之路
    C#设计模式(7)——适配器模式(Adapter Pattern)
    C#设计模式(6)——原型模式(Prototype Pattern)
    C#设计模式(5)——建造者模式(Builder Pattern)
    C#设计模式(4)——抽象工厂模式
    C#设计模式(3)——工厂方法模式
    C#设计模式(2)——简单工厂模式
    [C# 基础知识系列]C#中易混淆的知识点
    [C# 开发技巧]如何使不符合要求的元素等于离它最近的一个元素
  • 原文地址:https://www.cnblogs.com/yy3b2007com/p/11884898.html
Copyright © 2020-2023  润新知