暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

当Mybatis遇到MySQL中的Tinyint

爪哇优太儿 2020-03-20
1498

先看一个简单的demo:

数据库有一个user表,status类型设置为tinyint(1), 具体的结构如下:

mysql> show create table user \G
*************************** 1. row ***************************
Table: user
Create Table: CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(10) DEFAULT NULL,
`status` tinyint(1) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
mysql> select * from user \G
*************************** 1. row ***************************
id: 1
name: a
status: 2

再看下mybatis的mapper.xml:

<!--这两个查询的区别在于返回类型,一个是返回User对象,一个是返回Map-->
<select id="selectById" resultType="com.mamcharge.techc.action_audit.domain.User">
select * from user
where id=#{id}
</select>
<select id="selectByIdReturnMap" resultType="map">
select * from user
  where id=#{id}
</select>

做一个测试看一下输出结果:

@Test
public void selectByIdTest(){
User u = userMapper.selectById(1L);
System.out.println(u);
//User(id=1, name=a, status=2)
}
@Test
public void selectByIdReturnMapTest(){
Map<String, Object> map = userMapper.selectByIdReturnMap(1L);
System.out.println(map);
//{name=a, id=1, status=true}
}

有木有感觉很意外,status字段在返回对象User的情况下能正确取到值,但是在返回Map的情况下,竟然却返回了true!

啥原因呢?


我们用最原始的jdbc执行下这个查询看下输出结果是啥:


Connection connection = DriverManager.getConnection(url, user, password);
Statement stmt= connection.createStatement();
ResultSet rs = stmt.executeQuery("select * from user where id = 1");
while(rs.next()){
System.out.println(rs.getInt("status") + "," +rs.getBoolean("status"));
//2,true
}



上面的代码可以看出来,status字段既可以用getInt读出来,也可以用getBoolean读出来,那么问题可能就出在mybatis在在映射ResultSet的时候!如果返回类型是User对象,mybatis是可以明确的知道status的类型是Integer,那么肯定就会调用getInt()了,但是,如果返回类型是Map,根本不知道status的类型,那就只能用数据库字段的默认的类型了,那么默认的类型是啥呢?

while(rs.next()){
ResultSetMetaData metaData = rs.getMetaData();
System.out.println(metaData.getColumnClassName(3));//java.lang.Boolean
System.out.println(metaData.getColumnType(3));//-7
System.out.println(metaData.getColumnTypeName(3));//TINYINT
}


mysql默认的tinyint(1)的类型是Boolean,因此mybatis就会把status当成boolean来处理了,具体是不是这样的呢,我们来看下mybatis的处理:
看一下mybatis的ResultSetWrapper的源码:

public TypeHandler<?> getTypeHandler(Class<?> propertyType, String columnName) {
TypeHandler<?> handler = null;
Map<Class<?>, TypeHandler<?>> columnHandlers = typeHandlerMap.get(columnName);
if (columnHandlers == null) {
columnHandlers = new HashMap<Class<?>, TypeHandler<?>>();
typeHandlerMap.put(columnName, columnHandlers);
} else {
handler = columnHandlers.get(propertyType);
}
if (handler == null) {
//这个是bit
JdbcType jdbcType = getJdbcType(columnName);
//propertyType是Integer或者Object,得到的handler是IntegerTypeHandler或者UnknownTypeHandler
handler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType);
// Replicate logic of UnknownTypeHandler#resolveTypeHandler
// See issue #59 comment 10
// 如果handler是UnknownTypeHandler
if (handler == null || handler instanceof UnknownTypeHandler) {
final int index = columnNames.indexOf(columnName);
//这里会取metaData.getColumnClassName,也就是boolean
final Class<?> javaType = resolveClass(classNames.get(index));
if (javaType != null && jdbcType != null) {
//javaType=boolean jdbcType=bit,得到的handler是BooleanTypeHandler,最终就会调用rs.getBoolean
handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType);
} else if (javaType != null) {
handler = typeHandlerRegistry.getTypeHandler(javaType);
} else if (jdbcType != null) {
handler = typeHandlerRegistry.getTypeHandler(jdbcType);
}
}
if (handler == null || handler instanceof UnknownTypeHandler) {
handler = new ObjectTypeHandler();
}
columnHandlers.put(propertyType, handler);
}
return handler;
  }

如果我们把数据库的字段类型改一下呢:

mysql> alter table user modify status tinyint(4);
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0

此时得到的数据库类型是什么呢:

while(rs.next()){
ResultSetMetaData metaData = rs.getMetaData();
System.out.println(metaData.getColumnClassName(3));//java.lang.Integer
System.out.println(metaData.getColumnType(3));//-6
System.out.println(metaData.getColumnTypeName(3));//TINYINT
}

这个时候无论用User对象还是Map都可以正确返回数据了。

还有一种解决办法是添加url参数tinyInt1isBit=false,同样可以不把tinyint(1)当成是boolean,mysql默认是把它当成boolean的,参考:https://www.jianshu.com/p/6885cad1cb14。

结论

(1)mysql的tinyint(1)和tinyint(4)实际都是占用8个bit位,千万不要设置tinyint(1),除非仅仅用到了0和1。

(2)如果设置了tinyint(1),可以在url中添加tinyInt1isBit=false来获取到实际的值,而非true、false。




文章转载自爪哇优太儿,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论