MyBatis(⼋):MybatisJavaAPI枚举类型转化的⽤法
最近⼯作中⽤到了mybatis的Java API⽅式进⾏开发,顺便也整理下该功能的⽤法,接下来会针对基本部分进⾏学习:
;
Mybatis官⽹给了具体的⽂档,但是并没有对以上⽤法具体介绍,因此在这⾥整理下,以便以后⼯作⽤到时,可以参考。
本章主要使⽤Mybatis中使⽤typeHandlers进⾏对Enum进⾏转化的⽤法(本章将结合Spring⾃动注⼊《》)
本章将不再对maven项⽬的引⼊包,以及配置⽂件:jdbc.properties、l、l重复进⾏介绍,详情请参考上篇⽂件构建项⽬过程:《》。
enum函数简介:
在开发过程中,我们往往会使⽤到枚举类型,因为使⽤枚举更可以穷举、开发起来⽅便、把所有可选值都定义在⼀起(⽐起使⽤数字代表更能避免出现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 st.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 st.mapper.sqlprovider.LogSqlProvider;
import st.model.Log;
import ums.ModuleType;
import ums.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 st.mapper.sqlprovider;
import org.apache.ibatis.jdbc.SQL;
import st.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 st.model;
import java.util.Date;
import ums.ModuleType;
import ums.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)的⽤法
在不修改l和l配置⽂件(基于上⼀篇⽂章⽽⾔)的情况下,mybatis内部typeHandlers采⽤默认配置是:EnumTypeHandler,因此enum对应存储字段需要存储为varchar类型。
定义enum类型:ModuleType.java/OperateType.java
操作模块枚举MoudleType.java
package ums;
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 ums;
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=3DEFAULT CHARSET=utf8
测试类st.LogTest.java:
package st;
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 st.context.ContextConfiguration;
import st.context.junit4.SpringJUnit4ClassRunner;
import st.mapper.LogMapper;
import st.model.Log;
import ums.ModuleType;
import ums.OperateType;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "l" })
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=ById(logId);
System.out.println(log);
Long dbLogId=(log!=Id():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值,也不是dinal属性。
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 '操作类型';
此时执⾏测试类st.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 st.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.anslate(AbstractFallbackSQLExceptionTranslator.java:89)
at org.springframework.jdbc.anslate(AbstractFallbackSQLExceptionTranslator.java:81)
at org.springframework.jdbc.anslate(AbstractFallbackSQLExceptionTranslator.java:81)
batis.anslateExceptionIfPossible(MyBatisExceptionTranslator.java:88)
batis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:440)
at com.sun.proxy.$Proxy24.insert(Unknown Source)
batis.spring.SqlSessionTemplate.insert(SqlSessionTemplate.java:271)
at org.apache.ibatis.ute(MapperMethod.java:58)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59)
at com.sun.proxy.$Proxy36.insert(Unknown Source)
at stInsert(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');
此时执⾏抛出以下异常:
Error attempting to get column 'module_type' from result set.
Cause: java.lang.IllegalArgumentException: No enum constant ums.ModuleType.2
batis.anslateExceptionIfPossible(MyBatisExceptionTranslator.java:92)
batis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:440)
at com.sun.proxy.$Proxy24.selectOne(Unknown Source)
batis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate.java:159)
at org.apache.ibatis.ute(MapperMethod.java:83)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59)
at com.sun.proxy.$ById(Unknown Source)
at stGetById(LogTest.java:44)
。。。
测试我们可以发现:当typeHandlers的值为EnumTypeHandler时,数据存储类型必须是字符型(varchar等)不能是整数(smallint、int(4/11/20)、tinyint)。
为什么是这样⼦呢?其实我们可以从EnumTypeHandler的源代码去分析问题:
EnumTypeHandler源码分析:
package org.pe;
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");
}
}
@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.pe.TypeHandler<T>
-->org.pe.BaseTypeHandler.BaseTypeHandler()
----> org.pe.ArrayTypeHandler.ArrayTypeHandler()
-
---> org.pe.BigDecimalTypeHandler
----> org.pe.BigIntegerTypeHandler
----> org.pe.BlobByteObjectArrayTypeHandler
----> org.pe.BlobInputStreamTypeHandler
----> org.pe.BlobTypeHandler
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论