最近工作中用到了mybatis的Java API方式進行開發,順便也整理下該功能的用法,接下來會針對基本部分進行學習:
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); } }
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()); } }
具體在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); } } } }
源碼分析會發現它還是主要提供了兩個函數:
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屬性,調整后查詢出的數據結果會變動。